diff --git a/Analysis_Platform(x64).lnk b/Analysis_Platform(x64).lnk new file mode 100644 index 0000000..4cc22e0 Binary files /dev/null and b/Analysis_Platform(x64).lnk differ diff --git a/Analysis_Platform(x86).lnk b/Analysis_Platform(x86).lnk new file mode 100644 index 0000000..d592df8 Binary files /dev/null and b/Analysis_Platform(x86).lnk differ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..4f0b560 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,2415 @@ +# License of Analysis Platform + DN7 (Digital Native QC 7 Tools) + +The MIT License (MIT) + +Copyright (c) 2019-2021 DENSO Corporation, F-IoT DAC Team & Rainbow7 + Bridge7, Tatsunori Kojo, Genta Kikuchi, Sho Takahashi, Takero Arakawa, Le Sy Khanh Duy, Tran Ngoc Tinh, Nguyen Van Hoai, Ho Hoang Tung, Pham Minh Hoang, Tran Thi Kim Tuyen, Toshikuni Shinohara and DENSO contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +
+
+
+ +# Redistributed Content +The Content includes items that have been sourced from third parties as follows: + +## MIT + +### js/Chart.bundle.min.js +--------------------- + +* Chart.js v2.9.4 +* https://www.chartjs.org +* (c) 2020 Chart.js Contributors + +> licensed under the MIT License + +### js/Chart.min.js +--------------------- + +* Chart.js v3.3.0 +* https://www.chartjs.org +* (c) 2021 Chart.js Contributors + +> licensed under the MIT License + +### css/all.min.css +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + +### js/all.min.js +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + +### css/bootstrap-select.min.css +--------------------- + +* Bootstrap-select v1.12.4 (http://silviomoreto.github.io/bootstrap-select) +* +* Copyright 2013-2017 bootstrap-select +* Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) + +### js/bootstrap-select.min.js +--------------------- + +* Bootstrap-select v1.12.4 (http://silviomoreto.github.io/bootstrap-select) +* +* Copyright 2013-2017 bootstrap-select +* Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) + +> licensed under the MIT License + +### js/bootstrap-table-filter-control.min.js +--------------------- + +* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) +* +* @version v1.16.0 +* @homepage https://bootstrap-table.com +* @author wenzhixin (http://wenzhixin.net.cn/) +* @license MIT + +> licensed under the MIT License + +### js/bootstrap-table-locale-all.min.js +--------------------- + +* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) +* +* @version v1.16.0 +* @homepage https://bootstrap-table.com +* @author wenzhixin (http://wenzhixin.net.cn/) +* @license MIT + +> licensed under the MIT License + +### css/bootstrap-table.min.css +--------------------- + +* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) +* +* @version v1.16.0 +* @homepage https://bootstrap-table.com +* @author wenzhixin (http://wenzhixin.net.cn/) +* @license MIT + +> licensed under the MIT License + +### js/bootstrap-table.min.js +--------------------- + +* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) +* +* @version v1.16.0 +* @homepage https://bootstrap-table.com +* @author wenzhixin (http://wenzhixin.net.cn/) +* @license MIT + +> licensed under the MIT License + +### css/bootstrap-theme.css +--------------------- + +* Bootstrap v3.3.7 (http://getbootstrap.com) +* Copyright 2011-2016 Twitter, Inc. +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + +> licensed under the MIT License + +### css/bootstrap-theme.css.map +--------------------- + +* Bootstrap v3.3.7 (http://getbootstrap.com) +* Copyright 2011-2016 Twitter, Inc. +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + +> licensed under the MIT License + +### css/bootstrap-theme.min.css +--------------------- + +* Bootstrap v3.3.7 (http://getbootstrap.com) +* Copyright 2011-2016 Twitter, Inc. +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + +> licensed under the MIT License + +### css/bootstrap-theme.min.css.map +--------------------- + +* Bootstrap v3.3.7 (http://getbootstrap.com) +* Copyright 2011-2016 Twitter, Inc. +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + +> licensed under the MIT License + +### css/bootstrap.css +--------------------- + +* Bootstrap v3.3.7 (http://getbootstrap.com) +* Copyright 2011-2016 Twitter, Inc. +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) +* +* normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css + +> licensed under the MIT License + +### css/bootstrap.css.map +--------------------- + +* Bootstrap v3.3.7 (http://getbootstrap.com) +* Copyright 2011-2016 Twitter, Inc. +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) +* +* normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css + +> licensed under the MIT License + +### js/bootstrap.js +--------------------- + +* Bootstrap v3.3.7 (http://getbootstrap.com) +* Copyright 2011-2016 Twitter, Inc. + +> licensed under the MIT License + +### css/bootstrap.min.css +--------------------- + +* Bootswatch v4.3.1 +* Homepage: https://bootswatch.com +* Copyright 2012-2019 Thomas Park +* Licensed under MIT +* Based on Bootstrap +* +* Bootstrap v4.3.1 (https://getbootstrap.com/) +* Copyright 2011-2019 The Bootstrap Authors +* Copyright 2011-2019 Twitter, Inc. +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + +### css/bootstrap.min.css.map +--------------------- + +* Bootstrap v3.3.7 (http://getbootstrap.com) +* Copyright 2011-2016 Twitter, Inc. +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) +* +* normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css + +### js/bootstrap.min.js +--------------------- + +* Bootstrap v4.3.1 (https://getbootstrap.com/) +* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + +> licensed under the MIT License + +### js/chartjs-adapter-moment.min.js +--------------------- + +* chartjs-adapter-moment v1.0.0 +* https://www.chartjs.org +* (c) 2021 chartjs-adapter-moment Contributors + +> licensed under the MIT License + +### js/chartjs-plugin-annotation-latest.min.js +--------------------- + +* chartjs-plugin-annotation v1.0.1 +* https://www.chartjs.org/chartjs-plugin-annotation/index +* (c) 2021 chartjs-plugin-annotation Contributors + +> licensed under the MIT License + +### js/chartjs-plugin-annotation.min.js +--------------------- + +* chartjs-plugin-annotation.js +* http://chartjs.org/ +* Version: 0.5.7 +* +* Copyright 2016 Evert Timberg +* https://github.com/chartjs/Chart.Annotation.js/blob/master/LICENSE.md + +> licensed under the MIT License + +### js/clipboard.min.js +--------------------- + +* clipboard.js v2.0.8 +* https://clipboardjs.com/ +* +* Licensed MIT © Zeno Rocha + +### sigmajs/conrad.js +--------------------- + +* conrad.js is a tiny JavaScript jobs scheduler, +* +* Version: 0.1.0 +* Sources: http://github.com/jacomyal/conrad.js +* Doc: http://github.com/jacomyal/conrad.js#readme +* +* License: +* -------- +* Copyright © 2013 Alexis Jacomy, Sciences-Po médialab +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +* +* The Software is provided "as is", without warranty of any kind, express or +implied, including but not limited to the warranties of merchantability, +fitness for a particular purpose and noninfringement. In no event shall the +authors or copyright holders be liable for any claim, damages or other +liability, whether in an action of contract, tort or otherwise, arising +from, out of or in connection with the software or the use or other dealings +in the Software. + +### js/dataTables.fixedHeader.min.js +--------------------- + +* Copyright 2009-2021 SpryMedia Ltd. +* +* This source file is free software, available under the following license: +* MIT license - http://datatables.net/license/mit +* +* This source file is distributed in the hope that it will be useful, but +* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. +* +* For details please refer to: http://www.datatables.net +* FixedHeader 3.1.9 +* ©2009-2021 SpryMedia Ltd - datatables.net/license + +> licensed under the MIT License + +### js/datepicker.js +--------------------- + +* jQuery UI Datepicker 1.12.1 +* http://jqueryui.com +* +* Copyright jQuery Foundation and other contributors +* http://jquery.org/license + +> licensed under the MIT License + + +### date-range-picker/daterangepicker.js + +* copyright: Copyright (c) 2012-2019 Dan Grossman. All rights reserved. +* license: Licensed under the MIT license. +* See http://www.opensource.org/licenses/mit-license.php +* website: http://www.daterangepicker.com/ + +> licensed under the MIT License + +### webfonts/fa-brands-400.svg +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +* Created by FontForge 20190112 at Tue Jun 4 15:16:44 2019 +* By Robert Madole +* Copyright (c) Font Awesome + +### webfonts/fa-regular-400.svg +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +* Created by FontForge 20190112 at Tue Jun 4 15:16:44 2019 +* By Robert Madole +* Copyright (c) Font Awesome + +### webfonts/fa-solid-900.svg +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +* Created by FontForge 20190112 at Tue Jun 4 15:16:44 2019 +* By Robert Madole +* Copyright (c) Font Awesome + +### js/html2canvas.min.js +--------------------- + +* html2canvas 1.3.2 +* Copyright (c) 2021 Niklas von Hertzen +* Released under MIT License +* +* Copyright (c) Microsoft Corporation. +* +* Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +> licensed under the MIT License + +### jquery-ui-timepicker-addon/index.html +--------------------- + +1. jQuery +2. jQueryUI (with datepicker and slider wigits) +3. Timepicker + +* Version 1.6.3 +* Last updated on 2016-04-20 +* jQuery Timepicker Addon is currently available for use in all personal or commercial projects under the MIT license. + +> licensed under the MIT License + +### js/jexcel.js +--------------------- + +* Jspreadsheet v4.7.3 +* +* Website: https://bossanova.uk/jspreadsheet/ +* Description: Create amazing web based spreadsheets. +* +* This software is distribute under MIT License +* +* Copyright and license +* Jspreadsheet is released under the MIT license. +* The software is registered under UK law. Contact contact@jspreadsheet.com + +> licensed under the MIT License + +### jquery-ui-timepicker-addon/jquery-ui-sliderAccess.js +--------------------- + +* jQuery UI Slider Access +* By: Trent Richardson [http://trentrichardson.com] +* Version 0.3 +* Last Modified: 10/20/2012 +* +* Copyright 2011 Trent Richardson +* Dual licensed under the MIT and GPL licenses. +* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt +* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt + +> licensed under the MIT License + +### jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-addon-i18n.js +--------------------- + +* jQuery Timepicker Addon - v1.6.3 - 2016-04-20 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2016 Trent Richardson; Licensed MIT +* +* source: src/i18n/jquery-ui-timepicker-af.js +* Afrikaans translation for the jQuery Timepicker Addon +Written by Deon Heyns + +> licensed under the MIT License + +### jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-addon-i18n.min.js +--------------------- + +* jQuery Timepicker Addon - v1.6.3 - 2016-04-20 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2016 Trent Richardson; Licensed MIT + +> licensed under the MIT License + +### jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.js +--------------------- + +* jQuery Timepicker Addon - v1.6.3 - 2016-04-20 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2016 Trent Richardson; Licensed MIT + +> licensed under the MIT License + +### jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.min.css +--------------------- + +* jQuery Timepicker Addon - v1.6.3 - 2016-04-20 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2016 Trent Richardson; Licensed MIT + +> licensed under the MIT License + +### jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.min.js +--------------------- + +* jQuery Timepicker Addon - v1.6.3 - 2016-04-20 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2016 Trent Richardson; Licensed MIT + +> licensed under the MIT License + +### custom-jquery/jquery-ui.css +--------------------- + +* jQuery UI - v1.12.1 - 2019-07-17 +* http://jqueryui.com +* Includes: core.css, datepicker.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=custom-theme&bgImgOpacityError=&bgImgOpacityHighlight=&bgImgOpacityActive=&bgImgOpacityHover=&bgImgOpacityDefault=&bgImgOpacityContent=&bgImgOpacityHeader=&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=%23666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=%23aaaaaa&iconColorError=%23cc0000&fcError=%235f3f3f&borderColorError=%23f1a899&bgTextureError=flat&bgColorError=%23fddfdf&iconColorHighlight=%23ffffff&fcHighlight=%23ffffff&borderColorHighlight=%23375a7f&bgTextureHighlight=flat&bgColorHighlight=%23375a7f&iconColorActive=%23ffffff&fcActive=%23ffffff&borderColorActive=%23375a7f&bgTextureActive=flat&bgColorActive=%23375a7f&iconColorHover=%23ffffff&fcHover=%23ffffff&borderColorHover=%23375a7f&bgTextureHover=flat&bgColorHover=%23375a7f&iconColorDefault=%23ffffff&fcDefault=%23ffffff&borderColorDefault=%23303030&bgTextureDefault=flat&bgColorDefault=%23303030&iconColorContent=%23ffffff&fcContent=%23ffffff&borderColorContent=%23222222&bgTextureContent=flat&bgColorContent=%23222222&iconColorHeader=%23ffffff&fcHeader=%23fff&borderColorHeader=%23303030&bgTextureHeader=flat&bgColorHeader=%23303030&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif +* Copyright jQuery Foundation and other contributors; Licensed MI + +### css/jquery-ui.css +--------------------- + +* jQuery UI - v1.10.4 - 2014-01-17 +* http://jqueryui.com +* Includes: jquery.ui.core.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande%2CLucida%20Sans%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=5c9ccc&bgTextureHeader=gloss_wave&bgImgOpacityHeader=55&borderColorHeader=4297d7&fcHeader=ffffff&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=inset_hard&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=469bdd&bgColorDefault=dfeffc&bgTextureDefault=glass&bgImgOpacityDefault=85&borderColorDefault=c5dbec&fcDefault=2e6e9e&iconColorDefault=6da8d5&bgColorHover=d0e5f5&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=79b7e7&fcHover=1d5987&iconColorHover=217bc0&bgColorActive=f5f8f9&bgTextureActive=inset_hard&bgImgOpacityActive=100&borderColorActive=79b7e7&fcActive=e17009&iconColorActive=f9bd01&bgColorHighlight=fbec88&bgTextureHighlight=flat&bgImgOpacityHighlight=55&borderColorHighlight=fad42e&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +* Copyright 2014 jQuery Foundation and other contributors; Licensed MI + +### js/jquery-ui.js +--------------------- + +* jQuery UI - v1.12.1 - 2019-07-14 +* http://jqueryui.com +* Includes: widget.js, position.js, data.js, disable-selection.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/draggable.js, widgets/droppable.js, widgets/resizable.js, widgets/selectable.js, widgets/sortable.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/selectmenu.js, widgets/slider.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js +* Copyright jQuery Foundation and other contributors; Licensed MIT +* +* jQuery UI Widget 1.12.1 +* http://jqueryui.com +* +* Copyright jQuery Foundation and other contributors +* http://jquery.org/license + +> licensed under the MIT License + + +### custom-jquery/jquery-ui.js +--------------------- + +* jQuery UI - v1.12.1 - 2019-07-17 +* http://jqueryui.com +* Includes: keycode.js, widgets/datepicker.js +* Copyright jQuery Foundation and other contributors; Licensed MIT +* +* jQuery UI Keycode 1.12.1 +* http://jqueryui.com +* +* Copyright jQuery Foundation and other contributors +* http://jquery.org/license + +> licensed under the MIT License + + +### js/jquery-ui.min.js +--------------------- + +* jQuery UI - v1.12.0 - 2016-08-17 +* http://jqueryui.com +* Includes: widget.js, data.js, scroll-parent.js, widgets/sortable.js, widgets/mouse.js +* Copyright jQuery Foundation and other contributors; Licensed MIT + +> licensed under the MIT License + +### js-datatables/images/Sorting icons.psd + +* Sorting icons.psd +* Copyright (c) 1998 Hewlett-Packard Company + +### js-datatables/lib/jquery.dataTables.min.js +--------------------- + +* Copyright 2008-2019 SpryMedia Ltd. +* +* This source file is free software, available under the following license: +* MIT license - http://datatables.net/license +* +* This source file is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. +* +* For details please refer to: http://www.datatables.net +* DataTables 1.10.20 +* ©2008-2019 SpryMedia Ltd - datatables.net/license + +> licensed under the MIT License + +### js/jquery.dataTables.min.js +--------------------- + +* Copyright 2008-2020 SpryMedia Ltd. +* +* This source file is free software, available under the following license: +* MIT license - http://datatables.net/license +* +* This source file is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. +* +* For details please refer to: http://www.datatables.net +* DataTables 1.10.21 +* ©2008-2020 SpryMedia Ltd - datatables.net/license + +> licensed under the MIT License + +### js/jquery.floatThead.js +--------------------- + +* @preserve jQuery.floatThead 2.0.3 - http://mkoryak.github.io/floatThead/ +* Copyright (c) 2012 - 2017 Misha Koryak +* @license MIT +* +* @author Misha Koryak +* @projectDescription lock a table header in place while scrolling - without breaking styles or events bound to the header +* +* Dependencies: +* jquery 1.9.0 + [required] OR jquery 1.7.0 + jquery UI core +* +* http://mkoryak.github.io/floatThead/ + +> licensed under the MIT License + +### js/jquery.js +--------------------- + +* jQuery JavaScript Library v3.4.1 +* https://jquery.com/ +* +* Includes Sizzle.js +* https://sizzlejs.com/ +* +* Copyright JS Foundation and other contributors +* https://jquery.org/license +* +* Date: 2019-05-01T21:04Z + +> licensed under the MIT License + +### js/jquery.ui.datepicker-ja.min.js +--------------------- + +* jQuery UI - v1.10.4 - 2014-01-17 +* http://jqueryui.com +* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT + +> licensed under the MIT License + +### css/jsuites.css +--------------------- + +* (c) jSuites Javascript Web Components +* +* Website: https://jsuites.net +* Description: Create amazing web based applications. +* +* MIT License +* +* Copyright and license +* This software is distributed as MIT. Contact: contact@jsuites.net + +> licensed under the MIT License + +### js/jsuites.js +--------------------- + +* (c) jSuites Javascript Web Components +* +* Website: https://jsuites.net +* Description: Create amazing web based applications. +* +* MIT License +* +* Copyright and license +* This software is distributed as MIT. Contact: contact@jsuites.net + +> licensed under the MIT License + +### js/pagination.min.js + +* Copyright 2014-2100, superRaytin + +> licensed under the MIT License + +### js/plotly.min.js + +* Copyright 2012-2022, Plotly, Inc. All rights reserved. Licensed under the MIT license +* +* (c) Kyle Simpson MIT License: http://getify.mit-license.org +* +* (c) Sindre Sorhus license MIT +* +* copyright 2016 Sean Connelly (@voidqk), http://syntheti.cc license MIT preserve Project +* Home: https://github.com/voidqk/polybooljs +* +* copyright OpenStreetMap +* https://www.openstreetmap.org/copyright +* +* Copyright (c) 2014-2015, Jon Schlinkert. Licensed under the MIT License. + +> licensed under the MIT License + +### js/plotly-latest.min.js + +* plotly.js v1.58.4 +* Copyright 2012-2020, Plotly, Inc. +* All rights reserved. +* Licensed under the MIT license +* +* @author Feross Aboukhadijeh +* @license MIT + +> licensed under the MIT License + +### popper/umd/popper-utils.js + +* @fileOverview Kickass library to create and place poppers near their reference elements. +* @version 1.14.7 +* @license +* Copyright (c) 2016 Federico Zivolo and contributors + +> licensed under the MIT License + +### popper/umd/popper-utils.min.js +--------------------- + +* Copyright (C) Federico Zivolo 2019 +* Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +> licensed under the MIT License + +### popper/umd/popper.js +--------------------- + +* @fileOverview Kickass library to create and place poppers near their reference elements. +* @version 1.14.7 +* @license +* Copyright (c) 2016 Federico Zivolo and contributors + +> licensed under the MIT License + +### popper/umd/popper.min.js +--------------------- + +* Copyright (C) Federico Zivolo 2019 +* Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +> licensed under the MIT License + +### sigmajs/build/sigma.min.js +--------------------- + +* sigma.js - A JavaScript library dedicated to graph drawing. - Version: 1.2.1 +* Author: Alexis Jacomy, Sciences-Po Médialab - License: MIT + +> licensed under the MIT License + +### sigmajs/utils/sigma.polyfills.js +--------------------- + +* http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +* http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating +* requestAnimationFrame polyfill by Erik Möller. +* fixes from Paul Irish and Tino Zijdel +* MIT license + +> licensed under the MIT License + +### sigmajs/build/sigma.require.js +--------------------- + +* conrad.js is a tiny JavaScript jobs scheduler, +* +* Version: 0.1.0 +* Sources: http://github.com/jacomyal/conrad.js +* Doc: http://github.com/jacomyal/conrad.js#readme +* +* License: +* -------- +* Copyright © 2013 Alexis Jacomy, Sciences-Po médialab +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +* +* The Software is provided "as is", without warranty of any kind, express or +implied, including but not limited to the warranties of merchantability, +fitness for a particular purpose and noninfringement. In no event shall the +authors or copyright holders be liable for any claim, damages or other +liability, whether in an action of contract, tort or otherwise, arising +from, out of or in connection with the software or the use or other dealings +in the Software. +* +* http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +* http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating +* requestAnimationFrame polyfill by Erik Möller. +* fixes from Paul Irish and Tino Zijdel +* MIT license + +> licensed under the MIT License + +### js/socket.io.js +--------------------- + +* Socket.IO v2.2.0 +* (c) 2014-2018 Guillermo Rauch + +> licensed under the MIT License + +### vis/vis-network.min.js +--------------------- + +* vis.js +* https://github.com/almende/vis +* +* A dynamic, browser-based visualization library. +* +* @version 4.19.0 +* @date 2017-03-18 +* +* @license +* Copyright (C) 2011-2017 Almende B.V, http://almende.com +* +* Vis.js is dual licensed under both +* +* * The Apache 2.0 License +* http://www.apache.org/licenses/LICENSE-2.0 +* +* and +* +* * The MIT License +* http://opensource.org/licenses/MIT +* +* Vis.js may be distributed under either license. + +> licensed under the MIT License + +## OFL-1.1 + +### css/all.min.css +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + +### js/all.min.js +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + +> licensed under the MIT License + +### webfonts/fa-brands-400.svg +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +* +* Created by FontForge 20190112 at Tue Jun 4 15:16:44 2019 +* By Robert Madole +* Copyright (c) Font Awesome + +### webfonts/fa-regular-400.svg +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +* +* Created by FontForge 20190112 at Tue Jun 4 15:16:44 2019 +* By Robert Madole +* Copyright (c) Font Awesome + +### webfonts/fa-solid-900.svg +--------------------- + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +* +* Created by FontForge 20190112 at Tue Jun 4 15:16:44 2019 +* By Robert Madole +* Copyright (c) Font Awesome + +### webfonts/fa-brands-400.ttf + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +* Copyright (c) Font AwesomeFont Awesome 5 Brands RegularFont + +### webfonts/fa-regular-400.ttf + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +* Copyright (c) Font AwesomeFont Awesome 5 Brands RegularFont + +### webfonts/fa-solid-900.ttf + +* Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +* Copyright (c) Font AwesomeFont Awesome 5 Brands RegularFont + +## See-URL + +### js/dataTables.bootstrap4.min.js +--------------------- + +* DataTables Bootstrap 4 integration +* ©2011-2017 SpryMedia Ltd - datatables.net/license +* +* DataTables +* DataTables 1.10 and newer is available under the terms MIT license. This places almost no + restrictions on what you can do with DataTables, and you are free to use it in any way (including commercial projects), as long as the copyright header is left intact. Please see the MIT license page for the complete license. +* DataTables 1.9 and earlier +* DataTables 1.9 (and previous versions) were made available under both the GPL v2 license and the BSD (3-point) style license. These licenses still apply to those software releases, but newer versions use the MIT license (see above). + +> licensed under the MIT License + +### js/jquery.min.js +--------------------- + +* jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license +* @ sourceMappingURL=jquery.min.map + +### js/lodash.min.js +--------------------- + +* @license +* Lodash lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE +* Copyright (c) 2009-2021 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors + +> licensed under the MIT License + +### js/moment-with-locales.js +--------------------- + +* Copyright (c) JS Foundation and other contributors + +> licensed under the MIT License + +## Dual-license + +### jquery-ui-timepicker-addon/jquery-ui-sliderAccess.js +--------------------- + +* jQuery UI Slider Access +* By: Trent Richardson [http://trentrichardson.com] +* Version 0.3 +* Last Modified: 10/20/2012 +* +* Copyright 2011 Trent Richardson +* Dual licensed under the MIT and GPL licenses. +* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt +* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt + +> licensed under the MIT License + +### vis/vis-network.min.js +--------------------- + +* vis.js +* https://github.com/almende/vis +* +* A dynamic, browser-based visualization library. +* +* @version 4.19.0 +* @date 2017-03-18 +* +* @license +* Copyright (C) 2011-2017 Almende B.V, http://almende.com +* +* Vis.js is dual licensed under both +* +* * The Apache 2.0 License +* http://www.apache.org/licenses/LICENSE-2.0 +* +* and +* +* * The MIT License +* http://opensource.org/licenses/MIT +* +* Vis.js may be distributed under either license. + +> licensed under the MIT License + +## Apache-2.0 + +### js/gtag.js +--------------------- + +* Copyright 2012 Google Inc. All rights reserved. + +> licensed under the Apache-2.0 License + +### vis/vis-network.min.js +--------------------- + +* vis.js +* https://github.com/almende/vis +* +* A dynamic, browser-based visualization library. +* +* @version 4.19.0 +* @date 2017-03-18 +* +* @license +* Copyright (C) 2011-2017 Almende B.V, http://almende.com +* +* Vis.js is dual licensed under both +* +* * The Apache 2.0 License +* http://www.apache.org/licenses/LICENSE-2.0 +* +* and +* +* * The MIT License +* http://opensource.org/licenses/MIT +* +* Vis.js may be distributed under either license. + +> licensed under the MIT License + +## Public-domain-ref + +### sigmajs/utils/sigma.polyfills.js +--------------------- + +* http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +* http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating +* requestAnimationFrame polyfill by Erik Möller. +* fixes from Paul Irish and Tino Zijdel +* MIT license + +> licensed under the MIT License + +## CC0-1.0 + +### js/sizeof.compressed.js +--------------------- + +* sizeof.js +* +* A function to calculate the approximate memory usage of objects +* +* Created by Kate Morley - http://code.iamkate.com/ - and released under the terms +* of the CC0 1.0 Universal legal code: +* +* http://creativecommons.org/publicdomain/zero/1.0/legalcode + +## 0BSD + +### js/html2canvas.min.js +--------------------- + +* html2canvas 1.3.2 +* Copyright (c) 2021 Niklas von Hertzen +* Released under MIT License +* +* Copyright (c) Microsoft Corporation. +* +* Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +> licensed under the MIT License + +## Python Related + +### APScheduler + +* Version: 3.9.1 +* Copyright (c) 2009 Alex Grönholm + +> licensed under the MIT License + +### Babel + +* Version: 2.10.1 +* https://babel.pocoo.org/en/latest/license.html#babel-license + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### Brotli + +* Version: 1.0.9 +* Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. + +> licensed under the MIT License + +### Flask + +* Version: 1.1.1 +* https://flask.palletsprojects.com/en/1.1.x/license/ +* This license applies to all files in the Flask repository and source distribution. This includes Flask’s source code, the examples, and tests, as well as the documentation. +* Copyright 2010 Pallets +* +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +### Flask-APScheduler + +* Version: 1.11.0 +* Copyright 2015 Vinicius Chiele +* http://www.apache.org/licenses/LICENSE-2.0 + +> licensed under the Apache License Version 2.0 + +### Flask-Babel + +* Version: 1.0.0 +* Copyright (c) 2010 by Armin Ronacher. +* Some rights reserved. +* +* Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: +* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +### Flask-Compress + +* Version: 1.10.1 +* Copyright (c) 2013-2017 William Fagan + +> licensed under the MIT License + +### Flask-Migrate + +* Version: 2.5.2 +* Copyright (c) 2013 Miguel Grinberg + +> licensed under the MIT License + +### Flask-SQLAlchemy + +* Version: 2.4.1 +* Copyright 2010 Pallets + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### Flask-WTF + +* Version: 0.14.3 +* Copyright 2010 WTForms + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### Jinja2 + +* Version: 2.11.1 +* Copyright 2007 Pallets + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### Mako + +* Version: 1.2.0 +* Copyright 2006-2020 the Mako authors and contributors \. + +> licensed under the MIT License + +### MarkupSafe + +* Version: 1.1.1 +* Copyright 2010 Pallets + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### PyMySQL + +* Version: 0.9.3 +* Copyright (c) 2010, 2013 PyMySQL contributors + +> licensed under the MIT License + +### SQLAlchemy + +* Version: 1.3.24 +* Copyright 2005-2021 SQLAlchemy authors and contributors \. + +> licensed under the MIT License + +### WTForms + +* Version: 2.3.3 +* Copyright 2008 WTForms + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### WTForms-JSON + +* Version: 0.3.5 +* Copyright (c) 2012-2014, Konsta Vesterinen +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +### Werkzeug + +* Version: 1.0.0 +* Copyright 2007 Pallets + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### alembic + +* Version: 1.4.3 +* TM & © 2009-2015 Lucasfilm Entertainment Company Ltd. or Lucasfilm Ltd. +* All rights reserved. +* +* Industrial Light & Magic, ILM and the Bulb and Gear design logo are all +registered trademarks or service marks of Lucasfilm Ltd. +* +* © 2009-2015 Sony Pictures Imageworks Inc. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: +* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * Neither the name of Industrial Light & Magic nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* ------------------------------------------------------------------------ +* ALEMBIC ATTACHMENT A — +* REQUIRED NOTICES FOR DISTRIBUTION +* +* The Alembic Software is distributed along with certain third party +components licensed under various open source software licenses ("Open +Source Components"). In addition to the warranty disclaimers contained +in the open source licenses found below, Industrial Light & Magic, a +division of Lucasfilm Entertainment Company Ltd. ("ILM") makes the +following disclaimers regarding the Open Source Components on behalf of +itself, the copyright holders, contributors, and licensors of such Ope +* Source Components: +* TO THE FULLEST EXTENT PERMITTED UNDER APPLICABLE LAW, THE OPEN SOURCE +COMPONENTS ARE PROVIDED BY THE COPYRIGHT HOLDERS, CONTRIBUTORS, +LICENSORS, AND ILM "AS IS" AND ANY REPRESENTATIONS OR WARRANTIES OF ANY +KIND, WHETHER ORAL OR WRITTEN, WHETHER EXPRESS, IMPLIED, OR ARISING BY +STATUTE, CUSTOM, COURSE OF DEALING, OR TRADE USAGE, INCLUDING WITHOUT +LIMITATION THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR +A PARTICULAR PURPOSE, AND NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT +WILL THE COPYRIGHT OWNER, CONTRIBUTORS, LICENSORS, OR ILM AND/OR ITS +AFFILIATES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION), HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE OPEN +SOURCE COMPONENTS, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Boost C++ Libraries +* ------------------------------------------------------------------------ +* Boost Software License – Version 1.0 August 17th, 2003 Permission is +hereby granted, free of charge, to any person or organization obtaining +a copy of the software and accompanying documentation covered by this +license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of +the Software, and to permit third-parties to whom the Software i +furnished to do so, all subject to the following: +* +* The copyright notices in the Software and this entire statement, +including the above license grant, this restriction and the following +disclaimer, must be included in all copies of the Software, in whole or +in part, and all derivative works of the Software, unless such copies or +derivative works are solely in the form of machine-executable object +code generated by a source language processor. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES O +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND +NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE +DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, +WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN TH +SOFTWARE. + +### backports.zoneinfo + +* Version: 0.2.1 +* Copyright (c) 2020, Paul Ganssle (Google) +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. + +> licensed under the Apache License Version 2.0 + +### cffi + +* Version: 1.15.0 +* Copyright (C) 2005-2007, James Bielman + +> licensed under the MIT License + +### chardet + +* Version: 3.0.4 +* Copyright (C) 2006, 2007, 2008 Mark Pilgrim +* +* This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +02110-1301 USA +* +* ----------------------------------------------------------- +* The Original Code is mozilla.org code. +* +* The Initial Developer of the Original Code is +Netscape Communications Corporation. +Portions created by the Initial Developer are Copyright (C) 1998, 2005 +the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* Mark Pilgrim - port to Python +* +* This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +02110-1301 USA +* +* ----------------------------------------------------------- +* The Universal Encoding Detector documentation is copyright (C) 2006-2009 Mark Pilgrim. +* All rights reserved. +* +* Redistribution and use in source (XML DocBook) and "compiled" forms (SGML, +HTML, PDF, PostScript, RTF and so forth) with or without modification, are +permitted provided that the following conditions are met: Redistributions of +source code (XML DocBook) must retain the above copyright notice, this list of +conditions and the following disclaimer unmodified. Redistributions in +compiled form (transformed to other DTDs, converted to PDF, PostScript, RTF and +other formats) must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other +materials provided with the distribution. +* +* THIS DOCUMENTATION IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +### click + +* Version: 8.1.3 +* Copyright 2014 Pallets + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### colorama + +* Version: 0.4.4 +* Copyright (c) 2010 Jonathan Hartley +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * Neither the name of the copyright holders, nor those of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### cryptography + +* Version: 2.8 +* Copyright and licensing of the Python Cryptography Toolkit ("PyCrypto"): +* +* Previously, the copyright and/or licensing status of the Python +Cryptography Toolkit ("PyCrypto") had been somewhat ambiguous. The +original intention of Andrew M. Kuchling and other contributors has +been to dedicate PyCrypto to the public domain, but that intention was +not necessarily made clear in the original disclaimer (see +LEGAL/copy/LICENSE.orig). +* +* Additionally, some files within PyCrypto had specified their own +licenses that differed from the PyCrypto license itself. For example, +the original RIPEMD.c module simply had a copyright statement and +warranty disclaimer, without clearly specifying any license terms. +(An updated version on the author's website came with a license that +contained a GPL-incompatible advertising clause.) +* +* To rectify this situation for PyCrypto 2.1, the following steps have +been taken: +* 1. Obtaining explicit permission from the original contributors to dedicate their contributions to the public domain if they have not already done so. (See the "LEGAL/copy/stmts" directory for contributors' statements.) +* 2. Replacing some modules with clearly-licensed code from other sources (e.g. the DES and DES3 modules were replaced with new ones + based on Tom St. Denis's public-domain LibTomCrypt library.) +* 3. Replacing some modules with code written from scratch (e.g. the RIPEMD and Blowfish modules were re-implemented from their respective algorithm specifications without reference to the old implementations). +* 4. Removing some modules altogether without replacing them. +* +* To the best of our knowledge, with the exceptions noted below or +within the files themselves, the files that constitute PyCrypto are in +the public domain. Most are distributed with the following notice: +* +* The contents of this file are dedicated to the public domain. To +the extent that dedication to the public domain is not available, +everyone is granted a worldwide, perpetual, royalty-free, +non-exclusive license to exercise all rights associated with the +contents of this file for any purpose whatsoever. +No rights are reserved. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +* +* Exceptions: +* +* - Portions of HMAC.py and setup.py are derived from Python 2.2, and are therefore Copyright (c) 2001, 2002, 2003 Python Software Foundation (All Rights Reserved). They are licensed by the PSF under the terms of the Python 2.2 license. (See the file LEGAL/copy/LICENSE.python-2.2 for details.) +* - The various GNU autotools (autoconf, automake, aclocal, etc.) are used during the build process. This includes macros from autoconf-archive, which are located in the m4/ directory. As is customary, some files from the GNU autotools are included in the source tree (in the root directory, and in the build-aux/directory). These files are merely part of the build process, and are not included in binary builds of the software. +* +* EXPORT RESTRICTIONS: +* +* Note that the export or re-export of cryptographic software and/or +source code may be subject to regulation in your jurisdiction. + +### cutlet + +* Version: 0.1.19 +* Copyright (c) 2020 Paul O'Leary McCann + +> licensed under the MIT License + +### cx-Oracle + +* Version: 7.3.0 +* LICENSE AGREEMENT FOR CX_ORACLE +* +* Copyright 2016, 2018, Oracle and/or its affiliates. All rights reserved. +* +* Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. +* +* Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, +Canada. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the disclaimer that follows. +* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. +* 3. Neither the names of the copyright holders nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +*AS IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* Computronix is a registered trademark of Computronix (Canada) Ltd. + +### detect-delimiter + +* Version: 0.1.1 +* Copyright (c) 2015 Tim Ojo + +> licensed under the MIT License + +### flask-marshmallow + +* Version: 0.14.0 +* Copyright 2014-2020 Steven Loria and contributors + +> licensed under the MIT License + +### fugashi + +* Version: 1.1.2 +* Copyright (c) 2019 Paul O'Leary McCann + +> licensed under the MIT License + +### group-lasso + +* Version: 1.5.0 +* Copyright (c) 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +limitations under the License. + +> licensed under the Apache License Version 2.0 + +### importlib-metadata + +* Version: 4.11.4 +* Copyright (c) 2015-2019, conda-forge + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### itsdangerous + +* Version: 1.1.0 +* Copyright 2011 Pallets + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### jaconv + +* Version: 0.2.4 +* Copyright (c) 2014 Yukino Ikegami + +> licensed under the MIT License + +### joblib + +* Version: 1.1.0 +* Copyright (c) 2008-2021, The joblib developers. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### loguru + +* Version: 0.5.1 +* Copyright (c) 2017 + +> licensed under the MIT License + +### markdown2 + +* Version: 2.3.10 +* This implementation of Markdown is licensed under the MIT License: +* +* The MIT License +* +* Copyright (c) 2012 Trent Mick +* Copyright (c) 2010 ActiveState Software Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: +* +* The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +* +* +* All files in a *source package* of markdown2 (i.e. those available on +pypi.python.org and the Google Code project "downloads" page) are under the +MIT license. However, in the *subversion repository* there are some files +(used for performance and testing purposes) that are under different licenses +as follows: +* - perf/recipes.pprint +* - - Python License. This file includes a number of real-world examples of Markdown from the ActiveState Python Cookbook, used for doing some performance testing of markdown2.py. +* - test/php-markdown-cases/... +* - test/php-markdown-extra-cases/... +* - - GPL. These are from the MDTest package announced here: +* - - http://six.pairlist.net/pipermail/markdown-discuss/2007-July/000674.html +* - test/markdown.py +* - - GPL 2 or BSD. A copy (currently old) of Python-Markdown -- the other Python Markdown implementation. +* - test/markdown.php +* - - BSD-style. This is PHP Markdown (http://michelf.com/projects/php-markdown/). +* - test/Markdown.pl: BSD-style +* - - A copy of Perl Markdown (http://daringfireball.net/projects/markdown/). + +> licensed under the MIT License + +### marshmallow + +* Version: 3.9.1 +* Copyright 2021 Steven Loria and contributors + +> licensed under the MIT License + +### marshmallow-sqlalchemy + +* Version: 0.28.0 +* Copyright 2015-2022 Steven Loria and contributors + +> licensed under the MIT License + +### mecab-python3 + +* Version: 1.0.5 +* MeCab is copyrighted free software by Taku Kudo and +Nippon Telegraph and Telephone Corporation, and is released under +any of the GPL (see the file GPL), the LGPL (see the file LGPL), or the +BSD License (see the file BSD). + +### mojimoji + +* Version: 0.0.12 +* Copyright 2013 Studio Ousia +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +limitations under the License. + +> licensed under the Apache License Version 2.0 + +### numpy + +* Version: 1.19.3 +* Copyright (c) 2005-2022, NumPy Developers. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: +* * Redistributions of source code must retain the above copyrightnotice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * Neither the name of the NumPy Developers nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### pandas + +* Version: 1.2.5 +* BSD 3-Clause License +* +* Copyright (c) 2008-2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team +* All rights reserved. +* +* Copyright (c) 2011-2022, Open source contributors. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### psycopg2 + +* Version: 2.8.4 +* psycopg2 and the LGPL +* --------------------- +* psycopg2 is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +* +* psycopg2 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. +* +* In addition, as a special exception, the copyright holders give +permission to link this program with the OpenSSL library (or with +modified versions of OpenSSL that use the same license as OpenSSL), +and distribute linked combinations including the two. +* +* You must obey the GNU Lesser General Public License in all respects for +all of the code used other than OpenSSL. If you modify file(s) with this +exception, you may extend this exception to your version of the file(s), +but you are not obligated to do so. If you do not wish to do so, delete +this exception statement from your version. If you delete this exception +statement from all source files in the program, then also delete it here. +* +* You should have received a copy of the GNU Lesser General Public License +along with psycopg2 (see the doc/ directory.) +* If not, see . +* +* +* Alternative licenses +* -------------------- +* The following BSD-like license applies (at your option) to the files following +the pattern ``psycopg/adapter*.{h,c}`` and ``psycopg/microprotocol*.{h,c}``: +* +* Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. + +### pycparser + +* Version: 2.21 +* pycparser -- A C parser in Python +* +* Copyright (c) 2008-2020, Eli Bendersky +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * Neither the name of Eli Bendersky nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +### pymssql + +* Version: 2.1.4 +* Copyright (C) 1991, 1999 Free Software Foundation, Inc. +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +> licensed under the GNU Lesser General Public License v2.1 + +### python-dateutil + +* Version: 2.8.2 +* Copyright 2017- Paul Ganssle +* Copyright 2017- dateutil contributors (see AUTHORS file) +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +limitations under the License. +* +* The above license applies to all contributions after 2017-12-01, as well as +all contributions that have been re-licensed (see AUTHORS file for the list of +contributors who have re-licensed their code). +* -------------------------------------------------------------------------------- +* dateutil - Extensions to the standard Python datetime module. +* +* Copyright (c) 2003-2011 - Gustavo Niemeyer +* Copyright (c) 2012-2014 - Tomi Pieviläinen +* Copyright (c) 2014-2016 - Yaron de Leeuw +* Copyright (c) 2015- - Paul Ganssle +* Copyright (c) 2015- - dateutil contributors (see AUTHORS file) +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The above BSD License Applies to all code, even that also covered by Apache 2.0. + +### python-editor + +* Version: 1.0.4 +* Copyright 2020 University of Helsinki + +> licensed under the Apache License Version 2.0 + +### pytz + +* Version: 2021.3 +* Copyright (c) 2003-2005 Stuart Bishop + +> licensed under the MIT License + +### pytz-deprecation-shim + +* Version: 0.1.0.post0 +* Apache Software License 2.0 +* +* Copyright (c) 2020, Paul Ganssle (Google) +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +limitations under the License. + +> licensed under the Apache License Version 2.0 + +### ruamel.yaml + +* Version: 0.16.5 +* Copyright (c) 2014-2021 Anthon van der Neut, Ruamel bvba + +> licensed under the MIT License + +### ruamel.yaml.clib + +* Version: 0.2.6 +* Copyright (c) 2019-2021 Anthon van der Neut, Ruamel bvba + +> licensed under the MIT License + +### scikit-learn + +* Version: 0.24.2 +* BSD 3-Clause License +* +* Copyright (c) 2007-2021 The scikit-learn developers. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### scipy + +* Version: 1.4.1 +* Copyright (c) 2001-2002 Enthought, Inc. 2003-2022, SciPy Developers. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +> licensed under the BSD-3-Clause "New" or "Revised" License + +### simplejson + +* Version: 3.17.6 +* simplejson is dual-licensed software. It is available under the terms +of the MIT license, or the Academic Free License version 2.1. The full +text of each license agreement is included below. This code is also +licensed to the Python Software Foundation (PSF) under a Contributor +Agreement. +* +* MIT License +* =========== +* +* Copyright (c) 2006 Bob Ippolito +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +* +* Academic Free License v. 2.1 +* ============================ +* Copyright (c) 2006 Bob Ippolito. All rights reserved. +* +* This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following notice immediately following the copyright notice for the Original Work: +* +* Licensed under the Academic Free License version 2.1 +* +* 1) Grant of Copyright License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license to do the following: +* * * a) to reproduce the Original Work in copies; +* * * b) to prepare derivative works ("Derivative Works") based upon the Original Work; +* * * c) to distribute copies of the Original Work and Derivative Works to the public; +* * * d) to perform the Original Work publicly; and +* * * e) to display the Original Work publicly. +* 2) Grant of Patent License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, to make, use, sell and offer for sale the Original Work and Derivative Works. +* 3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor hereby agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work, and by publishing the address of that information repository in a notice immediately following the copyright notice that applies to the Original Work. +* 4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior written permission of the Licensor. Nothing in this License shall be deemed to grant any rights to trademarks, copyrights, patents, trade secrets or any other intellectual property of Licensor except as expressly stated herein. No patent license is granted to make, use, sell or offer to sell embodiments of any patent claims other than the licensed claims defined in Section 2. No right is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under different terms from this License any Original Work that Licensor otherwise would have a right to license. +* 5) This section intentionally omitted. +* 6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. +* 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately proceeding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to Original Work is granted hereunder except under this disclaimer. +* 8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to any person for any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to liability for death or personal injury resulting from Licensor's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. +* 9) Acceptance and Termination. If You distribute copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. Nothing else but this License (or another written agreement between Licensor and You) grants You permission to create Derivative Works based upon the Original Work or to exercise any of the rights granted in Section 1 herein, and any attempt to do so except under the terms of this License (or another written agreement between Licensor and You) is expressly prohibited by U.S. copyright law, the equivalent laws of other countries, and by international treaty. Therefore, by exercising any of the rights granted to You in Section 1 herein, You indicate Your acceptance of this License and all of its terms and conditions. +* 10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. +* 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et seq., the equivalent laws of other countries, and international treaty. This section shall survive the termination of this License. +* 12) Attorneys Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. +* 13) Miscellaneous. This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. +* 14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. +* 15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. +* +* This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved. Permission is hereby granted to copy and distribute this license without modification. This license may not be modified without the express written permission of its copyright owner. + +> licensed under the MIT License + +### six + +* Version: 1.16.0 +* Copyright (c) 2010-2020 Benjamin Peterson + +> licensed under the MIT License + +### threadpoolctl + +* Version: 3.1.0 +* Copyright (c) 2019, threadpoolctl contributors +* +> licensed under the BSD-3-Clause "New" or "Revised" License + +### typing-extensions + +* Version: 4.2.0 +* A. HISTORY OF THE SOFTWARE +* ========================== +* Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. +* In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. +* In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. +* All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. +``` + Release Derived Year Owner GPL- + from compatible? (1) + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes +``` +* Footnotes: +* (1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. +* (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. +* +* Thanks to the many outside volunteers who have worked under Guido's +* direction to make these releases possible. +* +* +* B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +* =============================================================== +* PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +* -------------------------------------------- +* +* 1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. +* 2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are +retained in Python alone or in any derivative version prepared by Licensee. +* 3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. +* 4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. +* 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +* 6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. +* 7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. +* 8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. +* +* BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +* ------------------------------------------- +* BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 +* +* 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). +* 2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. +* 3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. +* 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +* 5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. +* 6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. +* 7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. +* +* CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +* --------------------------------------- +* +* 1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. +* 2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". +* 3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. +* 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. +* 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +* 6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. +* 7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. +* 8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. +* ACCEPT +* CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +* -------------------------------------------------- +* Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. +* +* Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. +* STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +### tzdata + +* Version: 2022.1 +* Copyright (c) 2014 Lau Taarnskov +* +* Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: +* The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +> licensed under the MIT License + +### tzlocal + +* Version: 4.2 +* Copyright 2011-2017 Lennart Regebro + +> licensed under the MIT License + +### unidic-lite + +* Version: 1.0.8 +* Copyright (c) 2011-2017, The UniDic Consortium +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: +* * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. +* * Neither the name of the UniDic Consortium nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +### win32-setctime + +* Version: 1.1.0 +* Copyright (c) 2019 Delgan + +> licensed under the MIT License + +### zipp + +* Version: 3.8.0 +* Copyright Jason R. Coombs + +> licensed under the MIT License + +

+ +# Reference + +## MIT License : https://opensource.org/licenses/MIT + +## Apache License : http://www.apache.org/licenses/LICENSE-2.0 + +## BSD-3-Clause License : https://opensource.org/licenses/BSD-3-Clause + +## LGPL License : https://www.gnu.org/licenses/licenses.en.html + +## Creative Commons : https://creativecommons.org/publicdomain/zero/1.0/legalcode + +## Chart.js : https://www.chartjs.org/docs/latest/ + +### https://github.com/chartjs/Chart.js/blob/master/LICENSE.md + +The MIT License (MIT) +Copyright (c) 2014-2021 Chart.js Contributors + +> licensed under the MIT License + +## Bootstrap : https://getbootstrap.com/ + +### https://github.com/twbs/bootstrap/blob/main/LICENSE (base, theme) + +The MIT License (MIT) +Copyright (c) 2011-2021 Twitter, Inc. +Copyright (c) 2011-2021 The Bootstrap Authors + +> licensed under the MIT License + +### https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE + +The MIT License (MIT) +Copyright (c) 2013-2015 bootstrap-select + +> licensed under the MIT License + +### https://github.com/wenzhixin/bootstrap-table/blob/develop/LICENSE + +(The MIT License) +Copyright (c) 2012-2019 Zhixin Wen + +> licensed under the MIT License + + + +## jQuery : http://jquery.org/license/ + +Source Code +Projects referencing this document are released under the terms of the MIT license. +The MIT License is simple and easy to understand and it places almost no restrictions on what you can do with the Project. +You are free to use the Project in any other project (even commercial projects) as long as the copyright header is left intact. + +Sample Code +All demos and examples, whether in a Project's repository or displayed on a Project site, are released under the terms of the license as specified in the relevant repository. Many Projects choose to release their sample code under the terms of CC0. +CC0 is even more permissive than the MIT license, allowing you to use the code in any manner you want, without any copyright headers, notices, or other attribution. + +Web Sites +The content on a Project web site referencing this document in its header is released under the terms of the license specified in the website's repository or if not specified, under the MIT license. +The design, layout, and look-and-feel of JS Foundation project web sites are not licensed for use and may not be used on any site, personal or commercial, without prior written consent from the JS Foundation. +For further information regarding JS Foundation licensing and intellectual property, please review the JS Foundation IP Policy. + +> licensed under the MIT License diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1b5d49 --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +# Analysis Platform + +Analysis Platform is an open source web application to import, connect and visualize factory IoT data. It helps to collect, link and integrate data from multiple data sources. +Visualizations include Digital Native QC7 Tools, which is designed especially suitable for data on manufacturing domain. +Let's try data-driven process improvement by utilizing the data sleeping in the field. + + + + + + + + + + + + +
FPPFPPPCP
CHMMSPPCP
+ +## What can we do with Analysis Platform? + +The main activities that user can take with Analysis Platform are: + +* Check behavior of time-series or id-series data, stratify across processes (Full Points Plot) +* Check behavior of fluctuation of the distribution (Ridgeline Plot) +* Check correlation between variables and clusters in distribution (Multiple Scatter Plot) +* Check behavior of each category/group (Stratified Plot) +* Check behavior of related to human activities & process and product behavior (Calendar Heatmap) +* Look for key variables that strongly relates to output (Sankey Diagram) +* Grasp phenomenon that occurs at the same time (Cooccurrence Graph) + +Analysis Platform currently supports following data sources: + +* CSV/TSV/SSV(semicolon) +* SQLite +* MySQL +* PostgreSQL +* SQL Server +* Oracle Database + +## Terms of Use + +On your first access to the application, you must read and agree to [Terms of Use](/about/terms_of_use_en.md) shown on modal screen. +If you are going to use "`oss_start_app.bat`" to run Analysis Platform with +Windows embeddable package, +running the batch file is regarded as you agreed to the Terms of Use. + +## Requirements + +Analysis Platform uses [Flask](https://flask.palletsprojects.com/en/latest/) framework. + +- Python (>=3.6) (Tested with Python 3.7.3) + + +## How can we start using Analysis Platform? + +First, pull this repository to your PC. + +```shell +git clone https://github.com/apdn7/AnalysisPlatform.git +cd AnalysisPlatform +``` + +or you can download zip file and unzip it. + +### For users: Run Analysis Platform with Windows embeddable package + +If you use Windows machine, you can use Windows +embeddable package to run Analysis Platform +without installing python. +To download necessary packages and activate Analysis Platform, +you can run "`oss_start_app.bat`" (beforehand, read [Terms of Use](/about/terms_of_use_en.md) carefully). + +``` +Double click "oss_start_app.bat" +``` + +This batch file will automatically +download: + +* [Windows embeddable package](https://www.python.org/downloads/windows/): To run Analysis Platform without installing Python +* [pip](https://github.com/pypa/pip): To manage Python packages +* [other necessary python packages](requirements/common.txt): To run Analysis Platform +* [Oracle Instant Client](https://www.oracle.com/database/technologies/instant-client.html): To connect Oracle Database + +``` +Note: +If you are connecting internet using proxy, +you might have to edit "oss_start_app.bat" and specify the address unless it is registered in your environmental variable. +Open "oss_start_app.bat" with any text editor, and you will find +proxy setting on row 7-8. Remove REM from those rows and fill in HTTP_PROXY and HTTPS_PROXY. +``` + +Analysis Platform is activated after all downloads are finished. + +Analysis Platform uses Port 6868 by default. +Access below URL to access Analysis Platform. +If another program already uses port 6868, you can change the setting (See Basic Settings). + +``` +http://127.0.0.1:6868/ +``` + +Downloads are only excecuted only if above files were not detected, so you can use use "`oss_start_app.bat`" for the next activation. (Analysis platform detects a folder named `python_embedded`on the same level as `AnalysisPlatform`) + +Analysis Platform can run without internet connection. +If you want to use this application on machine which has no internet connection (for example, intranet), +you can first download all required files on other machine (which has internet connection), +then copy entire files. + +### For developers: Run Analysis Platform with Python installed on your machine + +Install requirements: + +```shell +pip install -r requirements/common.txt +``` +If pip install fails, try using Python 3.7.3. +Analysis Platform is activated by following command + +```bash +python main.py +``` + +Corresponding ODBC driver must be installed to use SQL Server and Oracle Database. + + +## How do we shut down Analysis Platform? + +To shut down Analysis Platform, +press shut down button on bottom of sidebar (this button is only available on host machine), +or you can press `Ctrl + C` on your console. + +## Basic Settings + +Basic settings of Analysis Platform is defined in +`info` field of `histview2/config/basic_config.yml`. +You can open the file with any text editor and set: + +* `port-no`: Port number that Analysis Platform use (default: 6868) +* `language`: Language used in GUI. For example, if you want to use Japanese, set "JA". If empty or invalid value is set, English is used. +See language selectbox on upper right corner of GUI for abbreviations of each language (default: empty) +* `hide-setting-page`: If True, hides link to config page (default: False) + +If you want to initialize the application, remove `instance` folder and `histview2/config/basic_config.yml`. +These files are generated on the next activation. + +## Is there any sample data that we can use? + +By default, Analysis Platform contains sample data and corresponding settings to get an overview of each visualization. +Data is stored as TSV file under the subdirectories in [/sample_data](/sample_data): + +* /assembly: Quality data + * /1_parts_feed + * 20220228.tsv + * /2_inspection + * 20220228.tsv +* /parts_processing: Machine data + * /1_machine_parameter_a + * 20220311.tsv + * /2_machine_parameter_b + * 20220311.tsv + * /3_finishing + * 20220311.tsv +* /alarm_signal_cog: Daily occurence of machine alarms + * 20200401.tsv + +Above data will be automatically imported after activation. +You can call each sample visualization from 'Load' or 'Bookmark' on upper right corner of GUI. + +If you do not need those data, +you can either initialize the application by removing `instance` +folder before the activation, +or you can just remove each data source setting from GUI. + +## License + +Analysis Platform is released under MIT License. +See our [LICENSE](LICENSE.md) for more detail. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..763ab44 --- /dev/null +++ b/VERSION @@ -0,0 +1,5 @@ +v4.0.0.141.7bd44ad7 +1 +OSS + + diff --git a/about/Endroll.md b/about/Endroll.md new file mode 100644 index 0000000..36e2486 --- /dev/null +++ b/about/Endroll.md @@ -0,0 +1,183 @@ + + + + + +- [Project Analysis Platform](#project-analysis-platform) + - [Globalization, i18n](#globalization-i18n) + - [Collaborators FY22](#collaborators-fy22) + - [Collaborators FY21](#collaborators-fy21) + - [Early Bird Collaborators](#early-bird-collaborators) +- [Research & Development Team](#research--development-team) + - [Data Analysis Education, Research & Promotion](#data-analysis-education-research--promotion) + - [Data Analysis Package](#data-analysis-package) + - [Data Analysis Interface](#data-analysis-interface) + - [Data Analysis Platform](#data-analysis-platform) + - [© F-IoT DAC Team & Rainbow7 + Bridge7](#-f-iot-dac-team--rainbow7--bridge7) + + + +
+
+
+ +# Project Analysis Platform + +
+
+ +## Globalization, i18n + +|||| +|--:|:-:|:--| +|Software GUI translation into various languages||Support members| +|English en 英語||Tran Ngoc Tinh チャン ゴク ティン Trần Ngọc Tình FPT Software Japan| +|Japanese ja 日本語||Le Sy Khanh Duy レイシー カイン ユイ Lê Sỹ Khánh Duy FPT Software Japan| +|Vietnamese vi ベトナム語||Ho Hoang Tung ホ ホアン トゥン Hồ Hoàng Tùng FPT Software Japan| +|Italian it イタリア語||| +|Spanish es スペイン語||| +|Czech cs チェコ語||| +|Hungarian hu ハンガリ語||| +|Portuguese pt ポルトガル語||| +|German de ドイツ語||| +|Hindi hi ヒンディ語||| +|Thai th タイ語||| +|Simplified Chinese zh-CN 簡体中国語||| +|Traditional Chinese zh-TW 繁体中国語||| +
+
+ +## Collaborators FY22 + +|||| +|--:|:-:|:--| +|Field Introduction Project Member & Data Preparation||Satoru Fukumori 福森 聖 DNJP Zemmyo & Nishio Injection Components Mfg. Mgt. Div.| +
+
+ +## Collaborators FY21 + +|||| +|--:|:-:|:--| +|Field Introduction Project Leader & Data Preparation||Takeshi Mori 森 剛志 DNJP Nishio Gasoline Injection Mfg. Div.| +|Specification Examination & Practical Testing||Koji Otaka 大鷹 浩二 DNJP Nishio Gasoline Injection Mfg. Div.| +|Testing and Field Introduction||Masakazu Iwata 岩田 雅和 DNJP Nishio Gasoline Injection Mfg. Div.| +|||Tatsunori Uehara 上原 辰徳 DNJP Nishio Gasoline Injection Mfg. Div.| +|||Norikatsu Sengoku 仙石 典克 DNJP Nishio Gasoline Injection Mfg. Div.| +|||Kousaku Nagano 永野 公作 DNJP Nishio Gasoline Injection Mfg. Div.| +|Field Introduction Management||Hisatoshi Tsukahara 塚原 久敏 DNJP Nishio Gasoline Injection Mfg. Div.| +
+
+ +## Early Bird Collaborators + +|||| +|--:|:-:|:--| +|Testing and Field Introduction||Masanobu Kito 鬼頭 雅伸 DNJP Zenmyo Diesel Injection Mfg. Div.| +|Field Introduction Management||Kenichi Niinuma 新沼 賢一 DNJP Monozukuri DX Promotion Div.| +|Specification Examination & R&D Cooperation||Yukinori Orihara 折原 幸宣 DNJP Powertrain Systems Production Eng. R&D Div.| +
+
+
+
+ +# Research & Development Team + +
+
+ +## Data Analysis Education, Research & Promotion + +|||| +|--:|:-:|:--| +|Data Analysis Education Development & Management Leader||Sho Takahashi 髙橋 翔 DNJP Monozukuri DX Promotion Div.| +|Data Analysis Education Development & Management||Takero Arakawa 荒川 毅郎 DNJP Monozukuri DX Promotion Div.| +
+ +## Data Analysis Package + +|||| +|--:|:-:|:--| +|Data Analysis Package Development & Management Leader||Genta Kikuchi 菊池 元太 DNJP Monozukuri DX Promotion Div.| +|Data Analysis Package Development||Sho Takahashi 髙橋 翔 DNJP Monozukuri DX Promotion Div.| +
+ +## Data Analysis Interface + +|||| +|--:|:-:|:--| +|Developer Leader & Bridge SE of Rainbow7 & Bridge7||Le Sy Khanh Duy レイシー カイン ユイ Lê Sỹ Khánh Duy FPT Software Japan| +|Developer of Rainbow7 & Bridge7||Tran Ngoc Tinh チャン ゴク ティン Trần Ngọc Tình FPT Software Japan| +|||Nguyen van Hoai グエン ヴァン ホアイ Nguyễn Văn Hoài FPT Software Japan| +|||Ho Hoang Tung ホ ホアン トゥン Hồ Hoàng Tùng FPT Software Japan| +|||Tran Thi Kim Tuyen チャン ティ キム トゥエン Trần Thị Kim Tuyền FPT Software Japan| +|||Nguyen van Hoai グエン ヴァン ホアイ Nguyễn Văn Hoài| +|Technology Leader of Rainbow7||Masato Yasuda 安田 真人 DNJP Monozukuri DX Promotion Div.| +|Agile Master of Rainbow7 & Bridge7||Yasutomo Kawashima 川島 恭朋 DNJP Monozukuri DX Promotion Div.| +
+ +## Data Analysis Platform + +|||| +|--:|:-:|:--| +|Data Analysis Platform Product Owner FY20-||Tatsunori Kojo 古城 達則 DNJP Monozukuri DX Promotion Div.| +|Technology Leader of Data Analysis & Data Analysis Platform Product Owner FY19||Genta Kikuchi 菊池 元太 DNJP Monozukuri DX Promotion Div.| +|Data Analysis Platform Development||Takero Arakawa 荒川 毅郎 DNJP Monozukuri DX Promotion Div.| +|||Sho Takahashi 髙橋 翔 DNJP Monozukuri DX Promotion Div.| +|Supervisor & Technology Leader of Data Analysis & SQC||Mutsumi Yoshino 吉野 睦 DNJP Monozukuri DX Promotion Div.| +|Supervisor & Senior Manager||Toshikuni Shinohara 篠原 壽邦 DNJP Monozukuri DX Promotion Div.| +
+
+
+ +## © F-IoT DAC Team & Rainbow7 + Bridge7 + +
diff --git a/about/terms_of_use_en.md b/about/terms_of_use_en.md new file mode 100644 index 0000000..9e06c6f --- /dev/null +++ b/about/terms_of_use_en.md @@ -0,0 +1,35 @@ +# Terms of use + +BY CLICKING THE "I ACCEPT" BUTTON, OR USING THE SOFTWARE, YOU ACKNOWLEDGE THAT YOU HAVE REVIEWED AND ACCEPT THIS TERMS OF USE AGREEMENT AND ARE AUTHORIZED TO ACT ON BEHALF OF, AND BIND TO THIS AGREEMENT, THE OWNER OF THIS SOFTWARE. IN CONSIDERATION OF THE FOREGOING, THE PARTIES AGREE AS FOLLOWS: + +IF LICENSEE DOES NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT, LICENSEE SHALL NOT USE THE SOFTWARE. + +UNDER THE MIT LICENSE, THE SOFTWARE IS WITHOUT WARRANTY AND DENSO WILL HAVE NO LIABILITY ARISING OUT OF OR IN CONNECTION WITH THE SOFTWARE. THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. THIS INCLUDES THE INDUSTRIAL QUALITY INFORMATION AND RESULTS OBTAINED WITH THE SOFTWARE. DENSO ALSO DOES NOT PROVIDE SUPPORT. + +Redistribution of the Software should be subject to the terms of the OSS license. Please do not redistribute if you cannot comply with the OSS license. There is a risk of violation of the Copyright Act, Export Administration Regulations, etc. and compensation claims, etc. + +There is a function to acquire data from a user-owned database, but there is a possibility that problems may occur due to an increase in the load on the database, so please check the safety beforehand and use it at your own risk. If you do not check, do not connect directly to the database and limit the use to data acquisition via files such as CSV. + +The Software tracks certain types of information during use of its data analytics to perform web analytics. This information helps us monitor the traffic and improve the performance of the Software that meets the needs of users. This information does not include personal information which is any information relating to an identified or identifiable natural person. The information that we collect includes, but is not limited to: + +* Internet Protocol (IP) address of the device that has contacted the Internet +* Type of browser used +* Type of operating system used +* Date and time of the visit and the analytics execution +* Pages visited and analytics executed +* Referral sources +* Data size of the data analytics +* Calculation time of the data analytics + +DENSO may also use the personal information collected for non-administrative uses, such as audits, evaluations, research, and planning. Personal information collected will not be used for decision making processes about an individual or to profile individual visitors. + +DENSO carries out web analytics (included information of the data analytics) using services from third-party service providers, Google Inc. ("Google") and website hosting services from GitHub Inc. ("GitHub"). + +In carrying out these services, these third parties may have access to information and these providers may be based in the United States of America (“USA”), which means that the personal information collected is transmitted outside of Japan and may be subject to USA laws. In addition, Google and GitHub operate servers in other countries on which the web analytics and user data may be processed. Consequently, the data may be subject to the governing legislation of the country where it is processed. The reason for them having access to information is to permit them to perform the tasks assigned to them on our behalf. + +For more information about the privacy policies of these service providers, please visit their individual websites by using the following links: + +* Google Analytics Terms of Service https://marketingplatform.google.com/about/analytics/terms/us/ +* GitHub Privacy Statement https://help.github.com/en/articles/github-privacy-statement + +To track users for the use of web analytics, Google requires the use of cookies. A cookie is a piece of data sent from a website and the Software and stored on a user's computer by the user’s web browser which the user is browsing and the Software. Cookies are generally used to enhance user’s browsing and user experience. These cookies are encrypted for security purposes. User can choose to set their browser to detect and reject cookies. If user change their browser settings to refuse cookies or disable JavaScript so that user’s visit will not be tracked, no personal information will be collected however, use of the Softwere may be affected in other ways, including making it difficult to access the information on the Softwere. If user use the browser add-ons from Google, their use of the Softwere will not be affected. diff --git a/about/terms_of_use_jp.md b/about/terms_of_use_jp.md new file mode 100644 index 0000000..0b402d7 --- /dev/null +++ b/about/terms_of_use_jp.md @@ -0,0 +1,35 @@ +# 利用規約 + +[同意する]ボタンをクリックするか、本ソフトウェアを使用することにより、ユーザは、本利用条件の内容を確認および同意し、本ソフトウェアの所有者に代わって本条件に拘束されることを了承するものとします。上記を約因として、両当事者は以下のとおり合意するものとします。 + +ライセンシが本条件の条項に同意しない場合、ライセンシは本ソフトウェアを使用しないものとします。 + +MITライセンスに基づき本ソフトウェアは無保証であり、DENSOは本ソフトウェアに起因または関連する一切の責任を負わないものとします。適用される法律で許可されている範囲で、ソフトウェアの保証はありません。書面で別途記載されている場合を除き、著作権所有者および/またはその他の当事者は、明示または黙示を問わず、商品性および特定の目的への適合性の黙示保証を含む(ただしこれらに限定されない)、いかなる種類の保証もなしにプログラムを「現状のまま」提供します。 本ソフトウェアの品質および性能に関するすべてのリスクはユーザにあります。プログラムに欠陥があることが判明した場合は、必要なすべてのサービス、修理または修正の費用を自己負担するものとします。これには本ソフトウェアで得られる工業上の品質情報や得られた結果等も含まれます。またDENSOはサポートも行いません。 + +本ソフトウェアを再配布する際はOSSライセンス規約に基く必要があります。OSSライセンス規約を守らない場合の再配布はお止めください。著作権法/輸出管理規則等への違反・賠償請求等のリスクがあります。 + +ユーザ保有のデータベースからデータを取得する機能がありますが、データベースへの負荷増大によるトラブルが発生する可能性もありますので事前に十分安全を確認したうえ自己責任でご使用ください。確認等を行わない場合はデータベースへの直接接続は避け、CSV等のファイルを介したデータ取得に利用を限定してください。 + +本ソフトウェアは、Web分析を実行するために分析中に特定の情報を追跡します。これらの情報はソフトウェアのトラフィックをモニタしユーザのニーズを満たすパフォーマンスの向上に役立ちます。この情報に個人情報(氏名、住所その他個人を特定できる情報)は含まれません。収集する情報には、次のものが含まれますが、これらに限定されません。 + +* インターネットに接続したデバイスのインターネットプロトコル(IP)アドレス +* 使用するブラウザの種類 +* 使用するオペレーティング・システムの種類 +* 訪問/実行日時 +* 訪問したページ/実行した分析 +* 参照元 +* 分析のデータサイズ +* 分析の計算時間 + +DENSOは、収集した情報を監査・評価・調査・計画などの非管理目的で使用することもあります。収集された情報は、個人に関する意思決定プロセスや個人ユーザのプロファイリングには使用されません。 + +DENSOは、サードパーティのサービスプロバイダであるGoogle Inc.(以下Google)のサービス、およびGitHub Inc.(以下GitHub)のWebサイトホスティングサービスを利用してWeb分析を行っています。 + +これらのサービスを実施するにあたり、第三者がユーザの個人情報にアクセスし、提供者がアメリカ合衆国(USA)に拠点を置く場合があります。これは、収集された個人情報が日本国外に送信されることを意味し、アメリカ合衆国の法律の適用を受ける場合があることを意味します。また、GoogleとGitHubは場合によってWeb分析とユーザデータの処理を他国のサーバで運用しています。従って、データはそれが処理される国の準拠法の対象となる場合があります。彼らがユーザの個人情報にアクセスできるのは、弊社に代わりユーザに割り当てられたタスクを実行するためです。 + +これらのサービスプロバイダのプライバシポリシーの詳細については、次のリンクを使用して各サービスプロバイダのWebサイトにアクセスしてください。 + +Google Analytics サービス利用規約 https://www.google.com/analytics/terms/ +GitHub プライバシに関する声明 https://help.github.com/en/articles/github-privacy-statement + +Web分析でユーザの使用状況を追跡するには、GoogleはCookie(クッキー)を使用する必要があります。Cookieとは、Webサイトおよび本ソフトウェアから送信されユーザが閲覧しているWebブラウザおよび本ソフトウェアによってユーザのコンピュータに保存されるデータのことです。Cookieは、一般的にブラウジングとユーザエクスペリエンスを向上させるために使用されます。これらのCookieは、セキュリティの目的で暗号化されています。ユーザはCookieを検出して拒否するようにブラウザを設定できます。Cookieを拒否するようにブラウザの設定を変更したり、JavaScriptを無効にしてユーザのアクセスが追跡されないようにした場合、情報は収集されませんが、ユーザは本ソフトウェア上の情報へのアクセスが困難になるなど、その他の影響を受ける可能性があります。Googleのブラウザアドオンを使用する場合、本ソフトウェアの使用は影響を受けません。 diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..aced825 --- /dev/null +++ b/babel.cfg @@ -0,0 +1,3 @@ +[python: **.py] +[jinja2: **/templates/**] +extensions=jinja2.ext.autoescape,jinja2.ext.with_ \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..2a38f56 --- /dev/null +++ b/config.py @@ -0,0 +1,132 @@ +import os + +from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor +from loguru import logger + +from histview2.common.common_utils import resource_path + +basedir = os.getcwd() + + +class Config(object): + SECRET_KEY = '736670cb10a600b695a55839ca3a5aa54a7d7356cdef815d2ad6e19a2031182b' + POSTS_PER_PAGE = 10 + PORT = 80 + parent_dir = os.path.dirname(basedir) + os.environ['FLASK_ENV'] = os.environ.get('FLASK_ENV', 'development') + R_PORTABLE = os.environ.get('R-PORTABLE') + if not R_PORTABLE: + R_PORTABLE = os.path.join(parent_dir, 'R-Portable', 'bin') + os.environ['PATH'] = '{};{}'.format(R_PORTABLE, os.environ.get('PATH', '')) + + # R-PORTABLEを設定する。 + os.environ['R-PORTABLE'] = os.path.join(parent_dir, 'R-Portable') + + ORACLE_PATH = os.path.join(parent_dir, 'Oracle-Portable') + os.environ['PATH'] = '{};{}'.format(ORACLE_PATH, os.environ.get('PATH', '')) + + ORACLE_PATH_WITH_VERSION = os.path.join(ORACLE_PATH, 'instantclient_21_3') + os.environ['PATH'] = '{};{}'.format(ORACLE_PATH_WITH_VERSION, os.environ.get('PATH', '')) + + logger.info(os.environ['PATH']) + print(R_PORTABLE) + + BABEL_DEFAULT_LOCALE = "en" + + # yaml config files name + YAML_CONFIG_BASIC = 'basic_config.yml' + YAML_CONFIG_DB = 'db_config.yml' + YAML_CONFIG_PROC = 'proc_config.yml' + YAML_CONFIG_HISTVIEW2 = 'histview2_config.yml' + YAML_TILE_INTERFACE_DN7 = 'tile_interface_dn7.yml' + YAML_TILE_INTERFACE_AP = 'tile_interface_analysis_platform.yml' + + # run `python histview2/script/generate_db_secret_key.py` to generate DB_SECRET_KEY + DB_SECRET_KEY = "4hlAxWLWt8Tyqi5i1zansLPEXvckXR2zrl_pDkxVa-A=" + + # timeout + SQLALCHEMY_ENGINE_OPTIONS = {'connect_args': {'timeout': 30}} + + # APScheduler + SCHEDULER_EXECUTORS = { + 'default': ThreadPoolExecutor(100), + 'processpool': ProcessPoolExecutor(5) + } + + SCHEDULER_JOB_DEFAULTS = { + 'coalesce': True, + 'max_instances': 1, + 'misfire_grace_time': 2 * 60 + } + VERSION_FILE_PATH = resource_path('VERSION') + BASE_DIR = basedir + GA_TRACKING_ID = 'UA-156244372-2' + PARTITION_NUMBER = 100 + + COMPRESS_MIMETYPES = [ + "text/html", + "text/css", + "text/xml", + "text/csv", + "text/tsv", + "application/json", + "application/javascript", + ] + COMPRESS_LEVEL = 6 + COMPRESS_MIN_SIZE = 500 + + +class ProdConfig(Config): + DEBUG = False + SQLALCHEMY_TRACK_MODIFICATIONS = False + SQLITE_CONFIG_DIR = os.path.join(basedir, 'instance') + UNIVERSAL_DB_FILE = os.path.join(SQLITE_CONFIG_DIR, 'universal.sqlite3') + SQLALCHEMY_DATABASE_URI = 'sqlite:///' + UNIVERSAL_DB_FILE + APP_DB_FILE = os.path.join(SQLITE_CONFIG_DIR, 'app.sqlite3') + SQLALCHEMY_DATABASE_APP_URI = 'sqlite:///' + APP_DB_FILE + # have to keep SQLALCHEMY_BINDS before SCHEDULER_JOBSTORES -> avoid overwrite + SQLALCHEMY_BINDS = { + 'app_metadata': SQLALCHEMY_DATABASE_APP_URI + } + # SCHEDULER_JOBSTORES = { + # 'default': SQLAlchemyJobStore(url='sqlite:///' + os.path.join(basedir, 'instance', 'app.sqlite3')) + # } + YAML_CONFIG_DIR = os.path.join(basedir, 'histview2', 'config') + + +class DevConfig(Config): + DEBUG = True + SQLALCHEMY_TRACK_MODIFICATIONS = True + SQLITE_CONFIG_DIR = os.path.join(basedir, 'instance') + UNIVERSAL_DB_FILE = os.path.join(SQLITE_CONFIG_DIR, 'universal.sqlite3') + SQLALCHEMY_DATABASE_URI = 'sqlite:///' + UNIVERSAL_DB_FILE + APP_DB_FILE = os.path.join(SQLITE_CONFIG_DIR, 'app.sqlite3') + SQLALCHEMY_DATABASE_APP_URI = 'sqlite:///' + APP_DB_FILE + # have to keep SQLALCHEMY_BINDS before SCHEDULER_JOBSTORES -> avoid overwrite + SQLALCHEMY_BINDS = { + 'app_metadata': SQLALCHEMY_DATABASE_APP_URI + } + # SCHEDULER_JOBSTORES = { + # 'default': SQLAlchemyJobStore(url='sqlite:///' + os.path.join(basedir, 'instance', 'app.sqlite3')) + # } + YAML_CONFIG_DIR = os.path.join(basedir, 'histview2', 'config') + + +class TestingConfig(Config): + DEBUG = False + TESTING = True + SQLALCHEMY_TRACK_MODIFICATIONS = True + SQLITE_CONFIG_DIR = os.path.join(basedir, 'tests', 'instances') + UNIVERSAL_DB_FILE = os.path.join(SQLITE_CONFIG_DIR, 'universal.sqlite3') + SQLALCHEMY_DATABASE_URI = 'sqlite:///' + UNIVERSAL_DB_FILE + APP_DB_FILE = os.path.join(SQLITE_CONFIG_DIR, 'app.sqlite3') + SQLALCHEMY_DATABASE_APP_URI = 'sqlite:///' + APP_DB_FILE + + SQLALCHEMY_BINDS = { + 'app_metadata': SQLALCHEMY_DATABASE_APP_URI + } + # SCHEDULER_JOBSTORES = { + # 'default': SQLAlchemyJobStore(url='sqlite:///' + os.path.join(basedir, 'tests', 'instances', 'app.sqlite3')) + # } + YAML_CONFIG_DIR = os.path.join(basedir, 'tests', 'histview2', 'config') + PARTITION_NUMBER = 2 diff --git a/histview2/__init__.py b/histview2/__init__.py new file mode 100644 index 0000000..cb1902b --- /dev/null +++ b/histview2/__init__.py @@ -0,0 +1,375 @@ +import atexit +import os +import time +from datetime import datetime + +import wtforms_json +from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore +from flask import Flask, render_template, Response, g, json +from flask_apscheduler import APScheduler, STATE_STOPPED +from flask_babel import Babel +from flask_compress import Compress +from flask_marshmallow import Marshmallow +from flask_migrate import Migrate +from flask_sqlalchemy import SQLAlchemy +from loguru import logger +from sqlalchemy import create_engine, event +from sqlalchemy.orm import scoped_session, create_session + +from histview2.common.common_utils import check_exist, make_dir, find_babel_locale +from histview2.common.common_utils import set_sqlite_params +from histview2.common.constants import FlaskGKey, SQLITE_CONFIG_DIR, PARTITION_NUMBER, UNIVERSAL_DB_FILE, APP_DB_FILE, \ + TESTING +from histview2.common.logger import log_execution +from histview2.common.services.request_time_out_handler import requestTimeOutAPI +from histview2.common.trace_data_log import get_log_attr, TraceErrKey + +db = SQLAlchemy() +migrate = Migrate() +scheduler = APScheduler() +ma = Marshmallow() +wtforms_json.init() + +background_jobs = {} + +LOG_IGNORE_CONTENTS = ('.html', '.js', '.css', '.ico', '.png') +# yaml config files +dic_yaml_config_file = dict(basic=None, db=None, proc=None, histview2=None, version='0', ti_dn7=None, + ti_analysis_platform=None) +dic_config = {'db_secret_key': None, SQLITE_CONFIG_DIR: None, PARTITION_NUMBER: None, APP_DB_FILE: None, + UNIVERSAL_DB_FILE: None, TESTING: None} + +# last request time +dic_request_info = {'last_request_time': datetime.utcnow()} + +# ############## init application metadata db ############### +db_engine = None + + +def init_engine(app, uri, **kwargs): + global db_engine + # By default, sqlalchemy does not overwrite table. Then no need to manually check file exists neither table exists. + # https://docs.sqlalchemy.org/en/14/core/metadata.html?highlight=create_all#sqlalchemy.schema.MetaData.create_all + db.create_all(app=app) + db_engine = create_engine(uri, **kwargs) + + @event.listens_for(db_engine, 'connect') + def do_connect(dbapi_conn, connection_record): + set_sqlite_params(dbapi_conn) + + return db_engine + + +Session = scoped_session(lambda: create_session( + bind=db_engine, + autoflush=True, + autocommit=False, + expire_on_commit=True +)) + + +def close_sessions(): + # close universal db session + try: + db.session.rollback() + db.session.close() + except Exception: + pass + + # close app db session + try: + session = g.get(FlaskGKey.APP_DB_SESSION) + if session: + session.rollback() + session.close() + except Exception: + pass + + # Flask g + try: + g.pop(FlaskGKey.APP_DB_SESSION) + except Exception: + pass + + +# ########################################################## + + +def create_app(object_name=None): + """Create and configure an instance of the Flask application.""" + + from .api import create_module as api_create_module + from .tile_interface import create_module as tile_interface_create_module + from .setting_module import create_module as setting_create_module + from .trace_data import create_module as trace_data_create_module + from .analyze import create_module as analyze_create_module + from .table_viewer import create_module as table_viewer_create_module + from .scatter_plot import create_module as scatter_plot_create_module + from .heatmap import create_module as heatmap_create_module + from .categorical_plot import create_module as categorical_create_module + from .ridgeline_plot import create_module as ridgeline_create_module + from .parallel_plot import create_module as parallel_create_module + from .sankey_plot import create_module as sankey_create_module + from .co_occurrence import create_module as co_occurrence_create_module + from .multiple_scatter_plot import create_module as multiple_scatter_create_module + from .common.logger import bind_user_info + from flask import request + + app = Flask(__name__) + app.config.from_object(object_name) + + app.config.update( + SCHEDULER_JOBSTORES={ + 'default': SQLAlchemyJobStore( + url=app.config['SQLALCHEMY_DATABASE_APP_URI']) + }, + ) + # table partition number + dic_config[PARTITION_NUMBER] = app.config[PARTITION_NUMBER] + + # db directory + dic_config[SQLITE_CONFIG_DIR] = app.config[SQLITE_CONFIG_DIR] + + # db files + dic_config[APP_DB_FILE] = app.config[APP_DB_FILE] + dic_config[UNIVERSAL_DB_FILE] = app.config[UNIVERSAL_DB_FILE] + + # testing param + dic_config[TESTING] = app.config.get(TESTING, None) + + # check and create instance folder before run db init + if not check_exist(dic_config[SQLITE_CONFIG_DIR]): + make_dir(dic_config[SQLITE_CONFIG_DIR]) + + should_reset_import_history = False + if not check_exist(app.config['UNIVERSAL_DB_FILE']): + should_reset_import_history = True + + db.init_app(app) + migrate.init_app(app, db) + ma.init_app(app) + init_engine(app, app.config['SQLALCHEMY_DATABASE_APP_URI']) + + # reset import history when no universal db + if should_reset_import_history: + from histview2.script.hot_fix.fix_db_issues import reset_import_history + reset_import_history(app) + + # yaml files path + yaml_config_dir = app.config.get('YAML_CONFIG_DIR') + dic_yaml_config_file['basic'] = os.path.join(yaml_config_dir, app.config['YAML_CONFIG_BASIC']) + dic_yaml_config_file['db'] = os.path.join(yaml_config_dir, app.config['YAML_CONFIG_DB']) + dic_yaml_config_file['proc'] = os.path.join(yaml_config_dir, app.config['YAML_CONFIG_PROC']) + dic_yaml_config_file['histview2'] = os.path.join(yaml_config_dir, app.config['YAML_CONFIG_HISTVIEW2']) + dic_yaml_config_file['ti_dn7'] = os.path.join(yaml_config_dir, app.config['YAML_TILE_INTERFACE_DN7']) + dic_yaml_config_file['ti_analysis_platform'] = os.path.join(yaml_config_dir, app.config['YAML_TILE_INTERFACE_AP']) + + # db secret key + dic_config['DB_SECRET_KEY'] = app.config['DB_SECRET_KEY'] + + # sqlalchemy echo flag + app.config['SQLALCHEMY_ECHO'] = app.config.get('DEBUG') + + babel = Babel(app) + Compress(app) + + api_create_module(app) + scatter_plot_create_module(app) + heatmap_create_module(app) + setting_create_module(app) + trace_data_create_module(app) + analyze_create_module(app) + table_viewer_create_module(app) + categorical_create_module(app) + ridgeline_create_module(app) + parallel_create_module(app) + sankey_create_module(app) + co_occurrence_create_module(app) + multiple_scatter_create_module(app) + tile_interface_create_module(app) + app.add_url_rule('/', endpoint='tile_interface.tile_interface') + + from histview2.common.yaml_utils import BasicConfigYaml + basic_config_yaml = BasicConfigYaml() + basic_config = basic_config_yaml.dic_config + hide_setting_page = BasicConfigYaml.get_node(basic_config, ['info', 'hide-setting-page'], False) + lang = BasicConfigYaml.get_node(basic_config, ['info', 'language'], False) + lang = find_babel_locale(lang) + lang = lang or app.config["BABEL_DEFAULT_LOCALE"] + + @babel.localeselector + def get_locale(): + return request.cookies.get('locale') or lang + + # get app version + version_file = app.config.get('VERSION_FILE_PATH') or os.path.join(os.getcwd(), 'VERSION') + with open(version_file) as f: + rows = f.readlines() + app_ver = rows[0] + if '%%VERSION%%' in app_ver: + app_ver = 'v00.00.000.00000000' + + yaml_ver = rows[1] if len(rows) > 1 else '0' + dic_yaml_config_file['version'] = yaml_ver + + app_location = rows[2] if len(rows) > 2 else 'DN' + app_location = str(app_location).strip('\n') + app_location = app_location if app_location != '' else 'DN' + + # start scheduler (Notice: start scheduler at the end , because it may run job before above setting info was set) + if scheduler.state != STATE_STOPPED: + scheduler.shutdown(wait=False) + + scheduler.init_app(app) + scheduler.start() + + # Shut down the scheduler when exiting the app + atexit.register(lambda: scheduler.shutdown() if scheduler.state != + STATE_STOPPED else print('Scheduler is already shutdown')) + + @app.before_request + def before_request_callback(): + g.request_start_time = time.time() + # get the last time user request + global dic_request_info + + resource_type = request.base_url or '' + is_ignore_content = any(resource_type.endswith(extension) for extension in LOG_IGNORE_CONTENTS) + if not is_ignore_content: + dic_request_info['last_request_time'] = datetime.utcnow() + req_logger = bind_user_info(request) + req_logger.info("REQUEST ") + browser_info = request.user_agent.browser or 'chrome' + print("user's browser:", browser_info) + browser_info = str(browser_info).lower() + is_good_browser = any(name in browser_info in browser_info for name in ('chrome', 'edge')) + # if request.headers.environ.get('HTTP_SEC_CH_UA') and 'chrome' not in request.user_agent.browser.lower(): + if not dic_config.get(TESTING) and not is_good_browser: + return render_template('none.html', **{ + "title": "お使いのブラウザーはサポートされていません。", + "message": "現在のバージョンはChromeブラウザのみをサポートしています!", + "action": "Chrome を今すぐダウンロード: ", + "url": "https://www.google.com/chrome/" + }) + + @app.after_request + def after_request_callback(response: Response): + if 'event-stream' in str(request.accept_mimetypes): + return response + + # In case of text/html request, add information of disk capacity to show up on UI. + if 'text/html' in str(request.accept_mimetypes) or 'text/html' in str(response.headers): + from histview2.common.disk_usage import get_disk_capacity_to_load_UI, add_disk_capacity_into_response + dict_capacity = get_disk_capacity_to_load_UI() + add_disk_capacity_into_response(response, dict_capacity) + if not request.cookies.get('locale'): + response.set_cookie('locale', lang) + + # close app db session + close_sessions() + + response.cache_control.public = True + + # better performance + if not request.content_type: + response.cache_control.max_age = 60 * 5 + response.cache_control.must_revalidate = True + + # check everytime (acceptable performance) + # response.cache_control.no_cache = True + + response.add_etag() + response.make_conditional(request) + if response.status_code == 304: + return response + + resource_type = request.base_url or '' + is_ignore_content = any(resource_type.endswith(extension) for extension in LOG_IGNORE_CONTENTS) + if not is_ignore_content: + res_logger = bind_user_info(request, response) + res_logger.info("RESPONSE") + response.set_cookie('hide_setting_page', str(hide_setting_page)) + response.set_cookie('app_version', str(app_ver).strip('\n')) + response.set_cookie('app_location', str(app_location).strip('\n')) + + return response + + @app.errorhandler(404) + def page_not_found(e): + # note that we set the 404 status explicitly + return render_template('404.html'), 404 + + @app.errorhandler(500) + def internal_server_error(e): + # close app db session + close_sessions() + logger.exception(e) + + response = json.dumps({ + "code": e.code, + "message": str(e), + "dataset_id": get_log_attr(TraceErrKey.DATASET) + }) + return Response(response=response, status=500) + # return render_template('500.html'), 500 + + # @app.errorhandler(Exception) + # def unhandled_exception(e): + # # close app db session + # close_sessions() + # logger.exception(e) + # + # response = json.dumps({ + # "code": e.status_code, + # "message": e.message, + # "dataset_id": get_log_attr(TraceErrKey.DATASET) + # }) + # return Response(response=response) + + @app.errorhandler(requestTimeOutAPI) + def request_timeout_api_error(e): + """Return JSON instead of HTML for HTTP errors.""" + # close app db session + close_sessions() + + # logger.error(e) + + # start with the correct headers and status code from the error + # replace the body with JSON + response = json.dumps({ + "code": e.status_code, + "message": e.message, + }) + return Response(response=response, status=408) + + @app.teardown_appcontext + def shutdown_session(exception=None): + # close app db session + close_sessions() + Session.remove() + + return app + + +@log_execution() +def init_db(app): + """ + init db with some parameter + :return: + """ + from histview2.common.common_utils import sql_regexp, set_sqlite_params + from sqlalchemy import event + + db.create_all(app=app) + # Universal DB init + # if not universal_db_exists(): + + universal_engine = db.get_engine(app) + + @event.listens_for(universal_engine, 'connect') + def do_connect(dbapi_conn, connection_record): + set_sqlite_params(dbapi_conn) + + @event.listens_for(universal_engine, "begin") + def do_begin(dbapi_conn): + dbapi_conn.connection.create_function('REGEXP', 2, sql_regexp) diff --git a/histview2/analyze/__init__.py b/histview2/analyze/__init__.py new file mode 100644 index 0000000..0b6aa06 --- /dev/null +++ b/histview2/analyze/__init__.py @@ -0,0 +1,4 @@ + +def create_module(app, **kwargs): + from .controllers import analyze_blueprint + app.register_blueprint(analyze_blueprint) diff --git a/histview2/analyze/controllers.py b/histview2/analyze/controllers.py new file mode 100644 index 0000000..02c0068 --- /dev/null +++ b/histview2/analyze/controllers.py @@ -0,0 +1,27 @@ +from flask import Blueprint, render_template +from flask_babel import gettext as _ + +from histview2.common.services.form_env import get_common_config_data +from histview2.common.yaml_utils import * + +analyze_blueprint = Blueprint( + 'analyze', + __name__, + template_folder=os.path.join('..', 'templates', 'analyze'), + static_folder=os.path.join('..', 'static', 'analyze'), + url_prefix='/histview2/analyze' +) + +local_params = { + "config_yaml_fname_proc": dic_yaml_config_file[YAML_CONFIG_PROC], + "config_yaml_fname_histview2": dic_yaml_config_file[YAML_CONFIG_HISTVIEW2], + "config_yaml_fname_db": dic_yaml_config_file[YAML_CONFIG_DB] +} + + +@analyze_blueprint.route('/anomaly_detection/pca') +def pca(): + output_dict = get_common_config_data() + output_dict['sensor_list'] = [] + output_dict['page_title'] = _('Principle Component Analysis') + return render_template("hotelling_tsquare.html", **output_dict) diff --git a/histview2/analyze/services/__init__.py b/histview2/analyze/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/analyze/services/sensor_list.py b/histview2/analyze/services/sensor_list.py new file mode 100644 index 0000000..f7adea1 --- /dev/null +++ b/histview2/analyze/services/sensor_list.py @@ -0,0 +1,180 @@ +from functools import lru_cache + +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.services.sse import background_announcer, AnnounceEvent +from histview2.common.sigificant_digit import signify_digit_pca_vector +from histview2.setting_module.models import CfgProcess +from histview2.trace_data.models import Sensor, find_sensor_class + +NUM_SENSOR = 11 + + +def get_checked_cols(): + """get all checked columns + + Yields: + [type] -- [description] + """ + procs: [CfgProcess] = CfgProcess.get_all_order_by_id() + for proc in procs: + checked_cols = proc.columns or [] + for col in checked_cols: + yield dict(proc_id=proc.id, proc_name=proc.name, col_id=col.id, + col_name=col.column_name, col_type=col.data_type) + + +def filter_data(data, filter_func): + """filter data func + + Arguments: + data {[type]} -- [description] + filter_func {[type]} -- [description] + + Yields: + [type] -- [description] + """ + for row in data: + if filter_func(row): + yield row + + +def filter_data_type(dic_row): + """filter only real and integer columns + + Arguments: + dic_row {[type]} -- [description] + + Returns: + [type] -- [description] + """ + if not dic_row: + return False + + data_type = dic_row.get('col_type', None) + + if not data_type: + return False + + return data_type in (DataType.INTEGER.name, DataType.REAL.name) + + +def produce_sample_value_str(sensor_vals=[], effective_length=29, max_length=32): + """ + Produce list of sample values of sensor for PCA page + :param sensor_vals: + :param effective_length: + :param max_length: + :return: + """ + sensor_vals = list(map(lambda x: str(x), sensor_vals)) + sensor_vals_str = ", ".join(sensor_vals) + len_vals = len(sensor_vals_str) + if len_vals > effective_length: + sensor_vals_str = sensor_vals_str[0:effective_length] + sensor_vals_str = sensor_vals_str.ljust(max_length, ".") + else: + return sensor_vals_str + return sensor_vals_str + + +def produce_tool_tip_data(col_name='', lst_sensor_vals=[], num_head_tail=10): + tooltip = [{'pos': '{col}:'.format(col=col_name), 'val': ''}] + head = [{'pos': idx + 1, 'val': sample} for idx, sample in enumerate(lst_sensor_vals[0:num_head_tail])] + tooltip.extend(head) + + mid = [{'pos': '.', 'val': '.'}, {'pos': '.', 'val': '.'}, {'pos': '.', 'val': '.'}] + tooltip.extend(mid) + + num_sample = len(lst_sensor_vals) + if num_sample > num_head_tail + 3: + tail_sensors = lst_sensor_vals[num_head_tail + 3:][-num_head_tail:] + len_tail = len(tail_sensors) + + tail = [{'pos': num_sample + idx - len_tail + 1, 'val': sample} + for idx, sample in enumerate(tail_sensors)] + tooltip.extend(tail) + + return tooltip + + +@log_execution_time() +def get_sample_data(columns, limit=None): + """get sample data from database + + Arguments: + data {[type]} -- [description] + + Keyword Arguments: + limit {[type]} -- [description] (default: {None}) + """ + samples = [] + count = 1 + for col in columns: + proc_id = col.get('proc_id') + cfg_col_id = col.get('col_id') + cfg_col_name = col.get('col_name') + sensor = Sensor.get_sensor_by_col_name(proc_id, cfg_col_name) + if not sensor: + continue + + sensor_id = sensor.id + sensor_type = sensor.type + sensor_vals = get_sensor_first_records(cfg_col_id, cfg_col_name, sensor_id, sensor_type, limit) + + signified_sensor_vals = signify_digit_pca_vector(sensor_vals, sig_dig=4) + sensor_vals_str = produce_sample_value_str(signified_sensor_vals[0:11]) + col['sample'] = sensor_vals_str + + # produce tooltip + col['tooltip'] = produce_tool_tip_data(col_name=cfg_col_name, lst_sensor_vals=signified_sensor_vals) + samples.append(col) + if count % 60 == 0: + background_announcer.announce(samples, AnnounceEvent.PCA_SENSOR.name) + samples = [] + + if count > 3000: + break + count += 1 + + if samples: + background_announcer.announce(samples, AnnounceEvent.PCA_SENSOR.name) + + +@lru_cache(2000) +def get_sensor_first_records(cfg_col_id, cfg_col_name, sensor_id, sensor_type, limit=100): + data_type = DataType(sensor_type) + sensor_val_cls = find_sensor_class(sensor_id, data_type) + sensor_val = sensor_val_cls.coef(cfg_col_id) + sensor_vals = sensor_val_cls.get_first_records(cfg_col_name, limit=limit, coef_col=sensor_val) + sensor_vals = [sensor_val[0] for sensor_val in sensor_vals] + + return sensor_vals + + +# def get_sensors(num_sensor=NUM_SENSOR): +# """get sensors with filtered +# +# Returns: +# [type] -- [description] +# """ +# data = get_checked_cols() +# data = filter_data(data, filter_data_type) +# data = get_sample_data(data, limit=100) +# samples = [] +# try: +# [samples.append(next(data)) for i in range(num_sensor)] +# except StopIteration: +# pass +# return samples + + +def get_sensors_incrementally(): + """get sensors with filtered + + Returns: + [type] -- [description] + """ + data = get_checked_cols() + columns = filter_data(data, filter_data_type) + get_sample_data(columns, limit=100) diff --git a/histview2/analyze/services/utils.py b/histview2/analyze/services/utils.py new file mode 100644 index 0000000..ae4627d --- /dev/null +++ b/histview2/analyze/services/utils.py @@ -0,0 +1,71 @@ +import json +import math + +import numpy as np +import pandas as pd +from scipy.stats import multivariate_normal as mn + + +def get_valid_procs(procs): + """ + Get valid process to show on selectbox 起点 + Arguments: + procs {dict} + + Returns: + dict -- valid process on 起点 + """ + proc_list = {} + filter_info = procs['filter_info'] + proc_master = procs['proc_master'] + + for key, value in filter_info.items(): + if len(filter_info[key]) > 0: + filter_time = False + for item in filter_info[key]: + if item.get('item_info', {}) \ + and item['item_info'].get('type') \ + and item['item_info']['type'] == 'datehour-range': + filter_time = True + if filter_time: + proc_list.update({key: proc_master[key]}) + + return proc_list + + +def get_multivariate_normal(num_samples=500): + cov = [[1, 0], [0, 1]] # Covariance + mean = [0, 0] + dt = np.zeros([num_samples]) + dt = mn.rvs(mean=mean, cov=cov, size=num_samples, random_state=35) + radius_1 = 1 + radius_2 = 2 + radius_3 = 3 + radius_4 = 3.73 + + # df = pd.DataFrame(dt, columns=["x", "y"]) + # get line from normal distribution of x/y + # sns.distplot(df['x'], fit=norm, kde=False).get_lines()[0].get_data() + # get histogram distribution bar from x/y + # [h.get_height() for h in sns.distplot(df['x'], fit=norm, kde=False).patches] + return dt + + +def generateCircum(r, n=720): + pi = math.pi + dt = [] + for x in range(0, n + 1): + dt.append({'x': math.cos(2 * pi / n * x) * r, 'y': math.sin(2 * pi / n * x) * r}) + + return dt + + +class JEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, np.ndarray): + return obj.tolist() + if isinstance(obj, pd.DataFrame): + return obj.to_dict('list') + if isinstance(obj, pd.Series): + return obj.tolist() + return json.JSONEncoder.default(self, obj) diff --git a/histview2/api/__init__.py b/histview2/api/__init__.py new file mode 100644 index 0000000..0259172 --- /dev/null +++ b/histview2/api/__init__.py @@ -0,0 +1,25 @@ +def create_module(app, **kwargs): + from .setting_module.controllers import api_setting_module_blueprint + from .trace_data.controllers import api_trace_data_blueprint + from .table_viewer.controllers import api_table_viewer_blueprint + from .scatter_plot.controllers import api_scatter_blueprint + from .multi_scatter_plot.controllers import api_multi_scatter_blueprint + from .sankey_plot.controllers import api_sankey_plot_blueprint + from .co_occurrence.controllers import api_co_occurrence_blueprint + from .categorical_plot.controllers import api_categorical_plot_blueprint + from .analyze.controllers import api_analyze_module_blueprint + from .ridgeline_plot.controllers import api_ridgeline_plot_blueprint + from .heatmap.controllers import api_heatmap_blueprint + from .parallel_plot.controllers import api_paracords_blueprint + app.register_blueprint(api_setting_module_blueprint) + app.register_blueprint(api_trace_data_blueprint) + app.register_blueprint(api_table_viewer_blueprint) + app.register_blueprint(api_scatter_blueprint) + app.register_blueprint(api_multi_scatter_blueprint) + app.register_blueprint(api_sankey_plot_blueprint) + app.register_blueprint(api_co_occurrence_blueprint) + app.register_blueprint(api_categorical_plot_blueprint) + app.register_blueprint(api_analyze_module_blueprint) + app.register_blueprint(api_ridgeline_plot_blueprint) + app.register_blueprint(api_heatmap_blueprint) + app.register_blueprint(api_paracords_blueprint) diff --git a/histview2/api/analyze/__init__.py b/histview2/api/analyze/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/api/analyze/controllers.py b/histview2/api/analyze/controllers.py new file mode 100644 index 0000000..c33b9d2 --- /dev/null +++ b/histview2/api/analyze/controllers.py @@ -0,0 +1,90 @@ +import timeit + +import simplejson +from flask import Blueprint, request, jsonify + +from histview2.analyze.services.sensor_list import get_sensors_incrementally +from histview2.api.analyze.services.pca import run_pca, calculate_data_size +from histview2.common.constants import * +from histview2.common.services.form_env import parse_multi_filter_into_one +from histview2.common.services.http_content import json_serial +from histview2.common.services.import_export_config_n_data import get_dic_form_from_debug_info, \ + set_export_dataset_id_to_dic_param +from histview2.common.trace_data_log import is_send_google_analytics, save_input_data_to_file, EventType + +api_analyze_module_blueprint = Blueprint( + 'api_analyze_module', + __name__, + url_prefix='/histview2/api/analyze' +) + + +@api_analyze_module_blueprint.route('/pca', methods=['POST']) +def pca_modelling(): + start = timeit.default_timer() + dic_form = request.form.to_dict(flat=False) + + if not dic_form.get(START_PROC, None): + if dic_form.get('end_proc1'): + dic_form[START_PROC] = dic_form.get('end_proc1') + else: + return + + sample_no = dic_form.get('sampleNo') + if sample_no: + sample_no = int(sample_no[0]) - 1 + else: + sample_no = 0 + + # save dic_form to pickle (for future debug) + save_input_data_to_file(dic_form, EventType.PCA) + + dic_param = parse_multi_filter_into_one(dic_form) + + # check if we run debug mode (import mode) + dic_param = get_dic_form_from_debug_info(dic_param) + + # run PCA script + orig_send_ga_flg = is_send_google_analytics + dic_data, errors = run_pca(dic_param, sample_no) + + if errors: + output = simplejson.dumps(dict(json_errors=errors), ensure_ascii=False, default=json_serial) + return jsonify(output), 400 + + plotly_jsons = dic_data[PLOTLY_JSON] + data_point_info = dic_data[DATAPOINT_INFO] + output_dict = plotly_jsons + + output_dict.update({ + DATAPOINT_INFO: data_point_info, + SHORT_NAMES: dic_data.get(SHORT_NAMES), + IS_RES_LIMITED_TRAIN: dic_data.get(IS_RES_LIMITED_TRAIN), + IS_RES_LIMITED_TEST: dic_data.get(IS_RES_LIMITED_TEST), + ACTUAL_RECORD_NUMBER_TRAIN: dic_data.get(ACTUAL_RECORD_NUMBER_TRAIN), + ACTUAL_RECORD_NUMBER_TEST: dic_data.get(ACTUAL_RECORD_NUMBER_TEST), + REMOVED_OUTLIER_NAN_TRAIN: dic_data.get(REMOVED_OUTLIER_NAN_TRAIN), + REMOVED_OUTLIER_NAN_TEST: dic_data.get(REMOVED_OUTLIER_NAN_TEST), + }) + + # send google analytics changed flag + if orig_send_ga_flg and not is_send_google_analytics: + output_dict.update({'is_send_ga_off': True}) + + calculate_data_size(output_dict) + + stop = timeit.default_timer() + output_dict['backend_time'] = stop - start + + # export mode ( output for export mode ) + set_export_dataset_id_to_dic_param(dic_param) + + output_dict = simplejson.dumps(output_dict, ensure_ascii=False, default=json_serial, ignore_nan=True) + return output_dict, 200 + + +@api_analyze_module_blueprint.route('/sensor', methods=['GET']) +def pca(): + get_sensors_incrementally() + output_dict = simplejson.dumps({}, ensure_ascii=False, default=json_serial, ignore_nan=True) + return output_dict, 200 diff --git a/histview2/api/analyze/services/__init__.py b/histview2/api/analyze/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/api/analyze/services/pca.py b/histview2/api/analyze/services/pca.py new file mode 100644 index 0000000..f386c68 --- /dev/null +++ b/histview2/api/analyze/services/pca.py @@ -0,0 +1,693 @@ +from typing import List + +import numpy as np +import pandas as pd +from pandas import DataFrame +from sklearn.preprocessing import StandardScaler + +from histview2.api.trace_data.services.csv_export import to_csv +from histview2.api.trace_data.services.time_series_chart import get_data_from_db, get_procs_in_dic_param +from histview2.common.common_utils import gen_sql_label, gen_abbr_name, zero_variance +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.memoize import memoize +from histview2.common.pysize import get_size +from histview2.common.services.form_env import bind_dic_param_to_class +from histview2.common.services.sse import notify_progress +from histview2.common.trace_data_log import set_log_attr, TraceErrKey, save_trace_log_db, trace_log, \ + EventAction, EventType, Target +from histview2.setting_module.models import CfgProcess +# ------------------------------------START TRACING DATA TO SHOW ON GRAPH----------------------------- +from histview2.trace_data.schemas import DicParam + + +@log_execution_time('[PCA]') +@notify_progress(75) +def run_pca(dic_param, sample_no=0): + """run pca package to get graph jsons""" + + dic_output, dic_biplot, dic_t2q_lrn, dic_t2q_tst, errors = gen_base_object(dic_param) + if errors: + return None, errors + + plotly_jsons, errors = get_sample_no_data(dic_biplot, dic_t2q_lrn, dic_t2q_tst, sample_no) + if errors: + return None, errors + + # get sample no info + graph_param, dic_proc_cfgs, dic_serials, dic_get_date = pca_bind_dic_param_to_class(dic_param) + data_point_info = get_data_point_info(sample_no, dic_output.get('df'), graph_param, dic_proc_cfgs, dic_serials, + dic_get_date) + return {PLOTLY_JSON: plotly_jsons, DATAPOINT_INFO: data_point_info, + IS_RES_LIMITED_TRAIN: dic_output.get(IS_RES_LIMITED_TRAIN), + IS_RES_LIMITED_TEST: dic_output.get(IS_RES_LIMITED_TEST), + ACTUAL_RECORD_NUMBER_TRAIN: dic_output.get(ACTUAL_RECORD_NUMBER_TRAIN), + ACTUAL_RECORD_NUMBER_TEST: dic_output.get(ACTUAL_RECORD_NUMBER_TEST), + REMOVED_OUTLIER_NAN_TRAIN: dic_output.get(REMOVED_OUTLIER_NAN_TRAIN), + REMOVED_OUTLIER_NAN_TEST: dic_output.get(REMOVED_OUTLIER_NAN_TEST), + SHORT_NAMES: dic_output.get(SHORT_NAMES) + }, None + + +@log_execution_time() +@memoize(is_save_file=True) +def gen_base_object(dic_param): + dic_biplot, dic_t2q_lrn, dic_t2q_tst, errors = None, None, None, None + dic_output, dict_data, dict_train_data, errors = get_test_n_train_data(dic_param) + if not errors: + # call pca function + dic_sensor_headers = dict_train_data[DIC_SENSOR_HEADER] + x_train = dict_train_data['df'][dic_sensor_headers].rename(columns=dic_sensor_headers) + x_test = dict_data['df'][dic_sensor_headers].rename(columns=dic_sensor_headers) + var_names = x_train.columns.values + dic_biplot, dic_t2q_lrn, dic_t2q_tst = run_pca_and_calc_t2q(x_train, x_test, var_names) + + return dic_output, dic_biplot, dic_t2q_lrn, dic_t2q_tst, errors + + +@log_execution_time() +def get_test_n_train_data(dic_param): + dic_output, dict_data, dict_train_data, errors = None, None, None, None + # is remove outlier + is_remove_outlier = int(dic_param[COMMON][IS_REMOVE_OUTLIER]) + + # bind dic_param + orig_graph_param = bind_dic_param_to_class(dic_param) + graph_param, dic_proc_cfgs, dic_serials, dic_get_date = pca_bind_dic_param_to_class(dic_param) + train_graph_param, *_ = pca_bind_dic_param_to_class(dic_param, dic_proc_cfgs, is_train_data=True) + + dict_train_data = gen_trace_data(dic_proc_cfgs, train_graph_param, orig_graph_param, + training_data=True, is_remove_outlier=is_remove_outlier) + + errors = dict_train_data.get('errors') + if errors: + return dic_output, dict_data, dict_train_data, errors + + dict_data = gen_trace_data(dic_proc_cfgs, graph_param, orig_graph_param) + + errors = dict_data.get('errors') + if errors: + return dic_output, dict_data, dict_train_data, errors + + # count removed outlier, nan + dic_output = {IS_RES_LIMITED_TRAIN: dict_train_data.get(IS_RES_LIMITED), + IS_RES_LIMITED_TEST: dict_data.get(IS_RES_LIMITED), + REMOVED_OUTLIER_NAN_TRAIN: int(dict_train_data[ACTUAL_RECORD_NUMBER]) - len(dict_train_data['df']), + ACTUAL_RECORD_NUMBER_TEST: dict_data.get(ACTUAL_RECORD_NUMBER), + REMOVED_OUTLIER_NAN_TEST: int(dict_data[ACTUAL_RECORD_NUMBER]) - len(dict_data['df']), + 'df': dict_data['df'], + SHORT_NAMES: dict_data[SHORT_NAMES]} + + return dic_output, dict_data, dict_train_data, errors + + +@log_execution_time() +def get_sample_no_data(dic_biplot, dic_t2q_lrn, dic_t2q_tst, sample_no=0): + """ + clicked sample no data + :param dic_biplot: + :param dic_t2q_lrn: + :param dic_t2q_tst: + :param sample_no: + :return: + """ + plotly_jsons = _gen_jsons_for_plotly(dic_biplot, dic_t2q_lrn, dic_t2q_tst, sample_no) + # check R script error + if plotly_jsons: + if isinstance(plotly_jsons, str): + errors = [plotly_jsons] + else: + errors = plotly_jsons.get('err') + + if errors: + set_log_attr(TraceErrKey.MSG, str(errors)) + save_trace_log_db() + return None, errors + else: + return None, ['No output from R'] + + return plotly_jsons, None + + +@log_execution_time() +def gen_trace_data(dic_proc_cfgs, graph_param, orig_graph_param, training_data=False, is_remove_outlier=False): + """tracing data to show graph + 1 start point x n end point + filter by condition points that between start point and end_point + """ + + # get sensor cols + dic_sensor_headers, short_names = gen_sensor_headers(orig_graph_param) + + # get data from database + df, actual_record_number, is_res_limited = get_trace_data(dic_proc_cfgs, graph_param) + + if not actual_record_number: + return dict(errors=[ErrorMsg.E_ALL_NA.name]) + + # sensor headers + cols = list(dic_sensor_headers) + + # replace inf -inf to NaN , so we can dropNA later + df.loc[:, cols] = df[cols].replace(dict.fromkeys([np.inf, -np.inf, np.nan], np.nan)) + + # sensors + df_sensors: DataFrame = df[dic_sensor_headers] + + # if training_data and int(dic_param[COMMON][IS_REMOVE_OUTLIER]): + if training_data and is_remove_outlier: + df_sensors = remove_outlier(df_sensors, threshold=0.05) + if df_sensors is None or not df_sensors.size: + return dict(errors=[ErrorMsg.E_ALL_NA.name]) + + df[cols] = df_sensors[cols].to_numpy() + + # remove NaN row + df.dropna(subset=cols, inplace=True) + + # zero variance check + if zero_variance(df[df_sensors.columns]): + return dict(errors=[ErrorMsg.E_ZERO_VARIANCE.name]) + + # if there is no data + if not df.size: + return dict(errors=[ErrorMsg.E_ALL_NA.name]) + + return {'df': df, 'dic_sensor_headers': dic_sensor_headers, IS_RES_LIMITED: is_res_limited, + ACTUAL_RECORD_NUMBER: actual_record_number, SHORT_NAMES: short_names} + + +@log_execution_time() +def remove_outlier(df: pd.DataFrame, threshold=0.05): + if df is None: + return None + + cols = list(df.columns) + total = df.index.size + for col in cols: + num_nan = df[col].isna().sum() + num_numeric = total - num_nan + if num_numeric < 20: # when n=19, p1=0, p9=19 -> remove nothing -> skip + continue + p1 = np.floor(num_numeric * threshold) + p9 = num_numeric - p1 + df['rank_{}'.format(col)] = df[col].replace(dict.fromkeys([np.inf, -np.inf, np.nan], np.nan)).rank( + method='first') + df[col] = np.where((df['rank_{}'.format(col)] > p9) | (df['rank_{}'.format(col)] < p1), np.nan, df[col]) + + return df[cols] + + +@log_execution_time() +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.PCA, EventAction.READ, Target.DATABASE), send_ga=True) +def get_trace_data(dic_proc_cfgs, graph_param): + """get data from universal db + + Arguments: + trace {Trace} -- [DataFrame Trace] + dic_param {dictionary} -- parameter form client + + Returns: + [type] -- data join from start to end by global_id + """ + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + + return df, actual_record_number, is_res_limited + + +@log_execution_time() +@trace_log((TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventAction.SAVE, Target.TSV), + output_key=TraceErrKey.DUMPFILE, send_ga=True) +def write_csv(dfs, dic_proc_cfgs, graph_param, file_paths, dic_headers): + for df, file_path in zip(dfs, file_paths): + to_csv(df, dic_proc_cfgs, graph_param, delimiter=CsvDelimiter.TSV.value, + output_path=file_path, output_col_ids=dic_headers, len_of_col_name=10) + + return file_paths[0] + + +@log_execution_time() +def change_path(file_path): + return file_path.replace('dat_', 'ret_').replace('tsv', 'pickle') + + +@log_execution_time() +def get_data_point_info(sample_no, df: DataFrame, graph_param: DicParam, dic_proc_cfgs, dic_serials, dic_get_date): + row = df.iloc[sample_no] + proc_cnt = -1 + col_infos = [] + for proc in graph_param.array_formval: + proc_cnt += 1 + proc_cfg = dic_proc_cfgs[proc.proc_id] + for col_id, col_name, show_name in zip(proc.col_ids, proc.col_names, proc.col_show_names): + col_name = gen_sql_label(col_id, col_name) + if col_name not in df.columns: + continue + + if col_name in dic_serials.values(): + order = f'{proc_cnt:03}_1' + elif col_name in dic_get_date.values(): + order = f'{proc_cnt:03}_2' + else: + order = f'{proc_cnt:03}_3' + + col_infos.append((proc_cfg.name, show_name, row[col_name], order)) + + return col_infos + + +@log_execution_time() +def calculate_data_size(output_dict): + """ + Calculate data size for each chart. + """ + dtsize_pca_score_train = get_size(output_dict.get('json_pca_score_train')) + output_dict['dtsize_pca_score_train'] = dtsize_pca_score_train + + dtsize_pca_score_test = get_size(output_dict.get('json_pca_score_test')) + output_dict['dtsize_pca_score_test'] = dtsize_pca_score_test + + dtsize_t2_time_series = get_size(output_dict.get('json_t2_time_series')) + output_dict['dtsize_t2_time_series'] = dtsize_t2_time_series + + dtsize_q_time_series = get_size(output_dict.get('json_q_time_series')) + output_dict['dtsize_q_time_series'] = dtsize_q_time_series + + dtsize_t2_contribution = get_size(output_dict.get('json_t2_contribution')) + output_dict['dtsize_t2_contribution'] = dtsize_t2_contribution + + dtsize_q_contribution = get_size(output_dict.get('json_q_contribution')) + output_dict['dtsize_q_contribution'] = dtsize_q_contribution + + dtsize_pca_biplot = get_size(output_dict.get('json_pca_biplot')) + output_dict['dtsize_pca_biplot'] = dtsize_pca_biplot + + +@log_execution_time() +def pca_bind_dic_param_to_class(dic_param, dic_proc_cfgs: List[CfgProcess] = None, is_train_data=False): + # bind dic_param + graph_param = bind_dic_param_to_class(dic_param) + + # move start proc to first + graph_param.add_start_proc_to_array_formval() + + # add condition procs + graph_param.add_cond_procs_to_array_formval() + + if dic_proc_cfgs is None: + dic_proc_cfgs = get_procs_in_dic_param(graph_param) # add start proc + + # get serials and get_date + dic_serials = {} + dic_get_dates = {} + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + serials = proc_cfg.get_serials(column_name_only=False) + serial_ids = [serial.id for serial in serials] + get_date = proc_cfg.get_date_col(column_name_only=False) + get_date_id = get_date.id + text_cols = [col.id for col in proc_cfg.get_cols_by_data_type(DataType.TEXT, column_name_only=False)] + proc.add_cols(text_cols, append_first=True) + proc.add_cols(get_date_id, append_first=True) + proc.add_cols(serial_ids, append_first=True) + + dic_serials.update({col.id: gen_sql_label(col.id, col.column_name) for col in serials}) + dic_get_dates[get_date_id] = gen_sql_label(get_date.id, get_date.column_name) + + if is_train_data: + time_idx = 0 + else: + time_idx = 1 + + graph_param.common.start_date = graph_param.common.start_date[time_idx] + graph_param.common.start_time = graph_param.common.start_time[time_idx] + graph_param.common.end_date = graph_param.common.end_date[time_idx] + graph_param.common.end_time = graph_param.common.end_time[time_idx] + + return graph_param, dic_proc_cfgs, dic_serials, dic_get_dates + + +def gen_sensor_headers(orig_graph_param): + dic_labels = {} + short_names = {} + used_names = set() + for proc in orig_graph_param.array_formval: + for col_id, col_name in zip(proc.col_ids, proc.col_names): + name = gen_sql_label(col_id, col_name) + dic_labels[name] = col_name + + # gen short name + new_name = gen_abbr_name(col_name) + i = 1 + while new_name in used_names: + new_name = f'{new_name[0:-3]}({i})' + i += 1 + + short_names[name] = new_name + + return dic_labels, short_names + + +# ------------------------------------------------------ + +# run_pca_and_calc_t2q() +# - _calc_biplot_data() +# - _calc_biplot_circle_radius() +# - _gen_biplot_circles_dataframe() +# - _calc_biplot_arrows() +# - _gen_biplot_axislabs() +# +# - _calc_mspc_t2q() +# +# - _gen_jsons_for_plotly() +# - _extract_clicked_sample() +# - _convert_df_circles_to_dict() + + +@log_execution_time() +def run_pca_and_calc_t2q(X_train, X_test, varnames: list) -> dict: + ''' Run PCA and Calculate T2/Q Statistics/Contributions + + X_train and X_test must have same number of columns. + Data must all be integer or float, and NA/NaNs must be removed beforehand. + Number of rows can not be 0, and columns with constant value is not allowed. + + Inputs + ---------- + X_train: dataframe or 2d ndarray + (ntrain x p) pd.DataFrame of train data. + X_test: dataframe or 2d ndarray + (ntest x p) pd.DataFrame of test data. Must be X_test.shape[1] == X_train.shape[1] + varnames: list + (p) Column names. Must be len(varnames) == X_train.shape[1] + Returns + ---------- + output_dict: dict + A dictionary of jsons (dictionaries) to draw Biplot and T2/Q chart with plotly.js + json_pca_score_test + json_pca_score_train + json_t2_time_series + json_q_time_series + json_t2_contribution + json_q_contribution + json_pca_biplot + + ''' + pca = PCA() + pca.fit(X_train) + pca.x = pca.transform(X_train) + pca.newx = pca.transform(X_test) + dic_biplot = _calc_biplot_data(pca, varnames) + + threshold = 80 + num_pc = np.where(pca.cum_explained >= threshold)[0][0] + 1 + dic_t2q_lrn = _calc_mspc_t2q(pca, X_train, num_pc) + dic_t2q_tst = _calc_mspc_t2q(pca, X_test, num_pc) + + # move getting sample no data out of this function to cache base object. + # output_dict = _gen_jsons_for_plotly(dic_biplot, dic_t2q_lrn, dic_t2q_tst, clicked_sample_no) + # return output_dict + + return dic_biplot, dic_t2q_lrn, dic_t2q_tst + + +class PCA: + ''' Principle Component Analysis with sklearn interface + + Note that sklearn's PCA function does not return rotation matrix. + + Attributes + ---------- + sdev: ndarray + 1D array containing standard deviation of principle components (calculated by eigen values). + rotation: ndarray + 2D array of loadings. + var_explained: ndarray + 1D array of ratio[%] of variance explained in each principle components. + cum_explained: ndarray + 1D array of ratio[%] of cumulative variance explained. + scale: bool + If True, scale date to zero mean unit variance. + scaler: StandardScaler instance + Scaler fitted with data given to fit(). + ''' + + def __init__(self, scale=True): + self.sdev = None + self.rotation = None + self.var_explained = None + self.cum_explained = None + self.scale = scale + self.scaler = None + self.x = None + self.newx = None + + def fit(self, X): + if self.scale: + self.scaler = StandardScaler().fit(X) + X = self.scaler.transform(X) + covmat = np.cov(X.T) + + # note that eig() does not return eigen values in descending order + eig_vals, eig_vecs = np.linalg.eig(covmat) + idx_desc = np.argsort(eig_vals)[::-1] + eig_vals = eig_vals[idx_desc] + eig_vecs = eig_vecs[:, idx_desc] + + self.rotation = eig_vecs + self.sdev = np.sqrt(eig_vals) + self.var_explained = eig_vals / np.sum(eig_vals) * 100 + self.cum_explained = np.cumsum(self.var_explained) + + def transform(self, X): + if self.scale: + X = self.scaler.transform(X) + return X.dot(self.rotation) + + +# --------------------------- +# Biplot (PCA) +# --------------------------- + +def _calc_biplot_data(pca: dict, varnames: list, tgt_pc=[1, 2]) -> dict: + ''' Generate a set of data for biplot + Data for scatter plot, arrows, circles (and axis labels) + ''' + dic_radius = _calc_biplot_circle_radius(pca.x) + dic_arrows = _calc_biplot_arrows(rotation=pca.rotation, sdev=pca.sdev, max_train=dic_radius['max']) + df_circles = _gen_biplot_circles_dataframe(dic_radius) + axislabs = _gen_biplot_axislabs(pca.var_explained) + idx_tgt_pc = [x - 1 for x in tgt_pc] + + res = {'pca_obj': pca, + 'varnames': varnames, + 'arr_biplot_lrn': pca.x[:, idx_tgt_pc], + 'arr_biplot_tst': pca.newx[:, idx_tgt_pc], + 'dic_arrows': dic_arrows, + 'dic_radius': dic_radius, + 'df_circles': df_circles, + 'axislab': axislabs} + return res + + +def _calc_biplot_circle_radius(pca_score_train, prob_manual=85) -> dict: + ''' Calculate radius for circles in biplot + ''' + # standardized to unit variance + score_sqsums = pca_score_train[:, 0] ** 2 + pca_score_train[:, 1] ** 2 + + dic_radius = {'sigma': 1, + '2sigma': 2, + '3sigma': 3, + 'max': np.sqrt(np.max(score_sqsums)), + # 'train': np.sqrt(np.percentile(score_sqsums, 99.5)), + 'train': np.sqrt(np.max(score_sqsums)), + 'percentile': np.sqrt(np.percentile(score_sqsums, prob_manual)), + 'prob_manual': str(prob_manual)} + return dic_radius + + +def _gen_biplot_circles_dataframe(dic_radius: dict): + ''' Generate a dataframe with x,y values to plot circles in biplot + ''' + theta = np.concatenate([np.linspace(-np.pi, np.pi, 50), np.linspace(np.pi, -np.pi, 50)]) + px = np.cos(theta) + py = np.sin(theta) + df_1sigma = pd.DataFrame({'pc1.x': dic_radius['sigma'] * px, + 'pc2.y': dic_radius['sigma'] * py, + 'border': 'Sigma'}) + df_2sigma = pd.DataFrame({'pc1.x': dic_radius['2sigma'] * px, + 'pc2.y': dic_radius['2sigma'] * py, + 'border': '2Sigma'}) + df_3sigma = pd.DataFrame({'pc1.x': dic_radius['3sigma'] * px, + 'pc2.y': dic_radius['3sigma'] * py, + 'border': '3Sigma'}) + df_maxval = pd.DataFrame({'pc1.x': dic_radius['max'] * px, + 'pc2.y': dic_radius['max'] * py, + 'border': 'Outlier'}) + df_normal = pd.DataFrame({'pc1.x': dic_radius['train'] * px, + 'pc2.y': dic_radius['train'] * py, + 'border': 'Range'}) + df_percen = pd.DataFrame({'pc1.x': dic_radius['percentile'] * px, + 'pc2.y': dic_radius['percentile'] * py, + 'border': 'Percentile' + dic_radius['prob_manual']}) + df_circles = pd.concat([df_1sigma, df_2sigma, df_3sigma, df_maxval, df_normal, df_percen]) + return df_circles + + +def _calc_biplot_arrows(rotation, sdev, max_train, var_name_adjust=1.5, tgt_pc=[1, 2]) -> dict: + ''' Calculate direction of arrows (loadings) for biplot + ''' + dic_arrows = {'xval': None, 'yval': None, 'angle': None, 'hjust': None, 'varname': None} + + # x,y direction for arrows (length corresponds to eigen values) + scaled_eig_vecs = rotation * sdev + max_len = np.sqrt(np.max(np.sum(scaled_eig_vecs ** 2, axis=1))) + + idx_tgt_pc = [x - 1 for x in tgt_pc] + direc = scaled_eig_vecs[:, idx_tgt_pc] * (max_train / max_len) + dic_arrows['xval'] = direc[:, 0].copy() + dic_arrows['yval'] = direc[:, 1].copy() + + # angles and hjust for labels + angle = (180 / np.pi) * np.arctan(direc[:, 1], direc[:, 0]) + hjust = (1 - var_name_adjust * np.sign(direc[:, 0])) / 2.0 + dic_arrows['angle'] = angle + dic_arrows['hjust'] = hjust + return dic_arrows + + +def _gen_biplot_axislabs(var_explained, tgt_pc=[1, 2]) -> list: + ''' Generate axis labels for biplot + ''' + axislabs = ['PC{}({:.1f} [%] explained Var.)'.format(x, var_explained[x - 1]) for x in tgt_pc] + return axislabs + + +# --------------------------- +# PCA-MSPC +# --------------------------- + +def _calc_mspc_t2q(pca, X, num_pc=2) -> dict: + ''' PCA-MSPC: Calculate T2/Q statics and contributions + ''' + dic_t2q = dict(stats=None, contr_t2=None, contr_q=None) + + # calculate T2 stats and contributions + pc_score = pca.transform(X) + sigma = np.std(pc_score, axis=0) + t2_stats = np.sum((pc_score[:, :num_pc] ** 2) / sigma[:num_pc] ** 2, axis=1) + t2_contr = (pc_score / sigma) @ pca.rotation.T + + # calculate Q stats and contributions + xstd = pca.scaler.transform(X) + xhat = xstd @ pca.rotation[:, :num_pc] @ pca.rotation[:, :num_pc].T + q_contr = (xstd - xhat) ** 2 + q_stats = np.sum(q_contr, axis=1) + + dic_t2q['dic_stats'] = {'t2': t2_stats, 'q': q_stats} + dic_t2q['contr_t2'] = t2_contr + dic_t2q['contr_q'] = (q_contr.T / q_stats).T + return dic_t2q + + +# --------------------------- +# Utilities +# --------------------------- + +def _gen_jsons_for_plotly(dic_biplot: dict, dic_t2q_lrn: dict, dic_t2q_tst: dict, sample_no=0) -> dict: + """ + Generate a json to pass to plotly (Biplot and T2/Q Chart) + :param dic_biplot: + :param dic_t2q_lrn: + :param dic_t2q_tst: + :param sample_no: + :return: + sample_no: int + An integer specifing sample no of clicked data point. + For example, clicked_sample_no=1 is the first data point of X_test. + clicked_sample_no=-1 is the last data point of X_train. + """ + + # Biplots (scatter, circles) + dic_circles = _convert_df_circles_to_dict(dic_biplot['df_circles']) + json_pca_score_train = { + 'scatter': { + 'x': dic_biplot['arr_biplot_lrn'][:, 0], + 'y': dic_biplot['arr_biplot_lrn'][:, 1], + }, + 'circles': dic_circles, + 'axislab': dic_biplot['axislab'], + 'r': dic_biplot['dic_radius'] + } + + json_pca_score_test = { + 'scatter': { + 'x': dic_biplot['arr_biplot_tst'][:, 0], + 'y': dic_biplot['arr_biplot_tst'][:, 1], + }, + 'circles': dic_circles, + 'axislab': dic_biplot['axislab'], + 'r': dic_biplot['dic_radius'] + } + + # Biplots (arrows): same graph for train and test + score_clicked = _extract_clicked_sample(dic_biplot['arr_biplot_lrn'], dic_biplot['arr_biplot_tst'], sample_no) + json_pca_biplot = { + 'x': dic_biplot['dic_arrows'].get('xval'), + 'y': dic_biplot['dic_arrows'].get('yval'), + 'varname': dic_biplot['varnames'], + 'angle': dic_biplot['dic_arrows'].get('angle'), + 'hjust': dic_biplot['dic_arrows'].get('hjust'), + 'r': dic_biplot['dic_radius'], + 'clicked_point': { + "x": score_clicked[0], + "y": score_clicked[1], + } + } + + # PCA-MSPC + json_t2_time_series = {'train': dic_t2q_lrn['dic_stats']['t2'], 'test': dic_t2q_tst['dic_stats']['t2']} + json_q_time_series = {'SPE': dic_t2q_lrn['dic_stats']['q'], 'test': dic_t2q_tst['dic_stats']['q']} + t2_contr = _extract_clicked_sample(dic_t2q_lrn['contr_t2'], dic_t2q_tst['contr_t2'], sample_no) + q_contr = _extract_clicked_sample(dic_t2q_lrn['contr_q'], dic_t2q_tst['contr_q'], sample_no) + + df_t2_contr = pd.DataFrame({'Var': dic_biplot['varnames'], 'Ratio': t2_contr / np.sum(np.abs(t2_contr))}) + df_q_contr = pd.DataFrame({'Var': dic_biplot['varnames'], 'Ratio': q_contr / np.sum(np.abs(q_contr))}) + df_t2_contr = df_t2_contr.sort_values('Ratio', ascending=False, key=abs).reset_index() + df_q_contr = df_q_contr.sort_values('Ratio', ascending=False, key=abs).reset_index() + + output_dict = { + 'json_pca_score_test': json_pca_score_test, + 'json_pca_score_train': json_pca_score_train, + 'json_t2_time_series': json_t2_time_series, + 'json_q_time_series': json_q_time_series, + 'json_t2_contribution': df_t2_contr, + 'json_q_contribution': df_q_contr, + 'json_pca_biplot': json_pca_biplot} + return output_dict + + +def _convert_df_circles_to_dict(df_circles) -> dict: + ''' Convert dataframe of circles to dictionary, to pass it to plotly + ''' + dic_circles = {} + for label in df_circles['border'].unique(): + dic_circles[label] = {'x': [], 'y': []} + idx = np.where(df_circles['border'].values == label)[0] + dic_circles[label]['x'] = df_circles['pc1.x'].values[idx] + dic_circles[label]['y'] = df_circles['pc2.y'].values[idx] + return dic_circles + + +def _extract_clicked_sample(df_train, df_test, sample_no=0): + ''' Extract a row from train/test data, according to given sample_no + ''' + + if sample_no >= 0: + return df_test[sample_no, :] + else: + return df_train[sample_no + df_train.shape[0], :] diff --git a/histview2/api/categorical_plot/controllers.py b/histview2/api/categorical_plot/controllers.py new file mode 100644 index 0000000..e5860e9 --- /dev/null +++ b/histview2/api/categorical_plot/controllers.py @@ -0,0 +1,84 @@ +import timeit + +import simplejson +from flask import Blueprint, request, send_from_directory + +from histview2.api.categorical_plot.services \ + import gen_trace_data_by_categorical_var, customize_dict_param, \ + gen_trace_data_by_term, convert_end_cols_to_array, \ + gen_trace_data_by_cyclic +from histview2.common.common_utils import resource_path +from histview2.common.logger import logger +from histview2.common.services import http_content +from histview2.common.services.form_env import parse_multi_filter_into_one +from histview2.common.services.import_export_config_n_data import get_dic_form_from_debug_info, \ + set_export_dataset_id_to_dic_param +from histview2.common.trace_data_log import save_input_data_to_file, EventType +from histview2.common.yaml_utils import * + +api_categorical_plot_blueprint = Blueprint( + 'api_categorical_plot', + __name__, + url_prefix='/histview2/api/stp' +) + +# ローカルパラメータの設定 +local_params = { + "config_yaml_fname_proc": dic_yaml_config_file[YAML_PROC], + "config_yaml_fname_histview2": dic_yaml_config_file[YAML_CONFIG_HISTVIEW2], + "config_yaml_fname_db": dic_yaml_config_file[YAML_CONFIG_DB], +} + +MAX_GRAPH_PER_TAB = 32 + + +@api_categorical_plot_blueprint.route('/index', methods=['POST']) +def trace_data(): + """ + Trace Data API + return dictionary + """ + + start = timeit.default_timer() + dic_form = request.form.to_dict(flat=False) + save_input_data_to_file(dic_form, EventType.STP) + + dic_param = parse_multi_filter_into_one(dic_form) + + # check if we run debug mode (import mode) + dic_param = get_dic_form_from_debug_info(dic_param) + + customize_dict_param(dic_param) + + proc_name = dic_param.get(COMMON).get(END_PROC) + time_conds = dic_param.get(TIME_CONDS) + compare_type = dic_param.get(COMMON).get(COMPARE_TYPE) + + if not proc_name or not time_conds: + return {}, 200 + + if compare_type == CATEGORICAL: + convert_end_cols_to_array(dic_param) + dic_param = gen_trace_data_by_categorical_var(dic_param, MAX_GRAPH_PER_TAB) + elif compare_type == RL_CYCLIC_TERM: + dic_param = gen_trace_data_by_cyclic(dic_param, MAX_GRAPH_PER_TAB) + else: + dic_param = gen_trace_data_by_term(dic_param, MAX_GRAPH_PER_TAB) + + stop = timeit.default_timer() + dic_param['backend_time'] = stop - start + + # export mode ( output for export mode ) + set_export_dataset_id_to_dic_param(dic_param) + + # trace_data.htmlをもとにHTML生成 + out_dict = simplejson.dumps(dic_param, ensure_ascii=False, default=http_content.json_serial, ignore_nan=True) + return out_dict, 200 + + +@api_categorical_plot_blueprint.route('/image/') +def download_file(filename): + dir_data_view = resource_path('data', 'view', level=AbsPath.SHOW) + logger.info('dir_data_view: ', dir_data_view, '; filename', filename) + + return send_from_directory(dir_data_view, filename) diff --git a/histview2/api/categorical_plot/services.py b/histview2/api/categorical_plot/services.py new file mode 100644 index 0000000..dfef764 --- /dev/null +++ b/histview2/api/categorical_plot/services.py @@ -0,0 +1,868 @@ +import traceback +from collections import defaultdict +from copy import deepcopy +from datetime import datetime, timedelta + +import pandas as pd +from dateutil import tz +from pandas import DataFrame + +from histview2.api.efa.services.etl import FILE as FILE_ETL_SPRAY_SHAPE +from histview2.api.efa.services.etl import call_com_view +from histview2.api.trace_data.services.time_series_chart import (get_data_from_db, get_chart_infos, + get_procs_in_dic_param, + gen_dic_data_from_df, get_min_max_of_all_chart_infos, + get_chart_infos_by_stp_var, + get_chart_infos_by_stp_value, build_regex_index, + apply_coef_text, + main_check_filter_detail_match_graph_data, + set_chart_infos_to_plotdata, calc_raw_common_scale_y, + calc_scale_info, get_cfg_proc_col_info) +from histview2.common.common_utils import (start_of_minute, end_of_minute, create_file_path, + get_view_path, get_basename, gen_sql_label, + make_dir_from_file_path, + any_not_none_in_dict, DATE_FORMAT_STR, TIME_FORMAT, DATE_FORMAT, + RL_DATETIME_FORMAT, convert_time) +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.memoize import memoize +from histview2.common.services.ana_inf_data import calculate_kde_trace_data +from histview2.common.services.form_env import bind_dic_param_to_class +from histview2.common.services.sse import notify_progress +from histview2.common.services.statistics import calc_summaries_cate_var, calc_summaries +from histview2.common.trace_data_log import EventType, trace_log, TraceErrKey, EventAction, Target +from histview2.setting_module.models import CfgProcess, CfgDataSource, CfgProcessColumn +from histview2.trace_data.models import Cycle + + +@log_execution_time() +def gen_graph_param(dic_param): + # bind dic_param + graph_param = category_bind_dic_param_to_class(dic_param) + + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + # TODO: check start proc cols( difference to time series) + # add start proc + graph_param.add_start_proc_to_array_formval() + + # add category + graph_param.add_cate_procs_to_array_formval() + + # add condition procs + graph_param.add_cond_procs_to_array_formval() + + # add cat exp + graph_param.add_cat_exp_to_array_formval() + + proc_cfg = dic_proc_cfgs[graph_param.common.start_proc] + + non_sensor_cols = [] + if use_etl_spray_shape(proc_cfg): + # get all checked cols + non_sensor_cols = [column.id for column in proc_cfg.columns if not column.data_type == DataType.REAL.name] + + # get serials + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + proc_sensor_ids = proc.col_sensor_only_ids + proc_col_ids = proc.col_ids.copy() + serial_ids = [serial.id for serial in proc_cfg.get_serials(column_name_only=False)] + proc.add_cols(serial_ids + non_sensor_cols) + if len(proc_sensor_ids) != len(proc_col_ids): + proc.add_sensor_col_ids(proc_col_ids) + + return graph_param, dic_proc_cfgs + + +@log_execution_time() +@notify_progress(50) +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.STP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_trace_data_by_categorical_var(dic_param, max_graph=None): + """tracing data to show graph + 1 start point x n end point + filter by condition points that between start point and end_point + """ + + # gen graph_param + graph_param, dic_proc_cfgs = gen_graph_param(dic_param) + + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = main_check_filter_detail_match_graph_data( + graph_param, df) + + # apply coef for text + df = apply_coef_text(df, graph_param, dic_proc_cfgs) + + # convert proc to cols dic + # transform raw data to graph data + # create output data + graph_param_with_cate = category_bind_dic_param_to_class(dic_param) + graph_param_with_cate.add_cate_procs_to_array_formval() + graph_param_with_cate.add_cat_exp_to_array_formval() + dic_data = gen_dic_data_from_df(df, graph_param_with_cate) + orig_graph_param = category_bind_dic_param_to_class(dic_param) + dic_data, is_graph_limited = split_data_by_condition(dic_data, orig_graph_param, max_graph) + dic_plots = gen_plotdata_for_var(dic_data) + for col_id, plots in dic_plots.items(): + if max_graph and max_graph < len(plots): + is_graph_limited = True + dic_plots[col_id] = plots[:max_graph] + + dic_param[ARRAY_PLOTDATA] = dic_plots + dic_param[IS_GRAPH_LIMITED] = is_graph_limited + dic_param[ACTUAL_RECORD_NUMBER] = actual_record_number + + # flag to show that trace result was limited + dic_param[IS_RES_LIMITED] = is_res_limited + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] = matched_filter_ids + dic_param[UNMATCHED_FILTER_IDS] = unmatched_filter_ids + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = not_exact_match_filter_ids + + # get visualization setting + add_threshold_configs(dic_param, orig_graph_param) + + # calculating the summaries information + calc_summaries_cate_var(dic_param) + + # calc common scale y min max + for end_col, plotdatas in dic_param[ARRAY_PLOTDATA].items(): + min_max_list, all_graph_min, all_graph_max = calc_raw_common_scale_y(plotdatas) + calc_scale_info(plotdatas, min_max_list, all_graph_min, all_graph_max) + + # generate kde for each trace output array + dic_param = gen_kde_data_cate_var(dic_param) + + remove_array_x_y_cyclic(dic_param) + + # images + img_files = dump_img_files(df, graph_param, dic_proc_cfgs) + dic_param['images'] = img_files + + return dic_param + + +@log_execution_time() +def add_threshold_configs(dic_param, orig_graph_param): + try: + chart_infos_by_cond_procs, chart_infos_org = get_chart_infos(orig_graph_param, no_convert=True) + chart_infos_by_stp_var = get_chart_infos_by_stp_var(orig_graph_param) + var_col_id = orig_graph_param.get_cate_var_col_id() + dic_filter_detail_2_regex = build_regex_index(var_col_id) + if chart_infos_by_cond_procs: + for end_col, plotdatas in dic_param[ARRAY_PLOTDATA].items(): + # TODO proc_id, col_id are str vs int + chart_info_cond_proc \ + = chart_infos_by_cond_procs[int(dic_param[COMMON][END_PROC][end_col])].get(int(end_col)) or {} + chart_info_cond_proc_org \ + = chart_infos_org[int(dic_param[COMMON][END_PROC][end_col])].get(int(end_col)) or {} + for plotdata in plotdatas: + stp_value = plotdata[RL_CATE_NAME] + chart_info_stp_value = get_chart_infos_by_stp_value( + stp_value, + end_col, + dic_filter_detail_2_regex, + chart_infos_by_stp_var, + ) + # selected_chart_infos = chart_info_stp_value or chart_info_cond_proc # OR for now, may union + if any_not_none_in_dict(chart_info_stp_value): + selected_chart_infos = chart_info_stp_value + chart_info_cond_proc_org = chart_info_stp_value + else: + selected_chart_infos = chart_info_cond_proc + + y_min, y_max = get_min_max_of_all_chart_infos(selected_chart_infos) + plotdata[CHART_INFOS] = selected_chart_infos + plotdata[CHART_INFOS_ORG] = chart_info_cond_proc_org + plotdata[Y_MIN] = y_min + plotdata[Y_MAX] = y_max + except Exception: + traceback.print_exc() + + +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.STP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_trace_data_by_cyclic(dic_param, max_graph=None): + dic_param = gen_trace_data_by_cyclic_common(dic_param, max_graph) + + dic_plotdata = defaultdict(list) + for plotdata in dic_param[ARRAY_PLOTDATA]: + dic_plotdata[plotdata['end_col']].append(plotdata) + + dic_param[ARRAY_PLOTDATA] = dic_plotdata + + # calculating the summaries information + calc_summaries_cate_var(dic_param) + + # calc common scale y min max + for end_col, plotdatas in dic_param[ARRAY_PLOTDATA].items(): + min_max_list, all_graph_min, all_graph_max = calc_raw_common_scale_y(plotdatas) + calc_scale_info(plotdatas, min_max_list, all_graph_min, all_graph_max) + + # generate kde for each trace output array + dic_param = gen_kde_data_cate_var(dic_param) + + # kde + remove_array_x_y_cyclic(dic_param) + + return dic_param + + +@log_execution_time() +@notify_progress(75) +def gen_trace_data_by_cyclic_common(dic_param, max_graph=None): + """tracing data to show graph + filter by condition points that between start point and end_point + """ + + produce_cyclic_terms(dic_param) + terms = gen_dic_param_terms(dic_param) + + dic_param = gen_graph_cyclic(dic_param, terms, max_graph) + dic_param[TIME_CONDS] = terms + return dic_param + + +def gen_dic_param_terms(dic_param): + terms = dic_param[COMMON].get(CYCLIC_TERMS) or [] + terms = [{START_DATE: convert_time(start_dt, DATE_FORMAT), + START_TM: convert_time(start_dt, TIME_FORMAT), + START_DT: start_dt, + END_DATE: convert_time(end_dt, DATE_FORMAT), + END_TM: convert_time(end_dt, TIME_FORMAT), + END_DT: end_dt} for start_dt, end_dt in terms] + return terms + + +@log_execution_time() +@notify_progress(75) +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.STP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_trace_data_by_term(dic_param, max_graph=None): + """tracing data to show graph + filter by condition points that between start point and end_point + """ + is_graph_limited = False + terms = dic_param.get(TIME_CONDS) or [] + dic_param[ARRAY_PLOTDATA] = [] + dic_param[MATCHED_FILTER_IDS] = [] + dic_param[UNMATCHED_FILTER_IDS] = [] + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = [] + dic_param[ACTUAL_RECORD_NUMBER] = 0 + + if max_graph and len(terms) > max_graph: + terms = terms[:max_graph] + is_graph_limited = True + + for term_id, term in enumerate(terms): + # create dic_param for each term from original dic_param + term_dic_param = deepcopy(dic_param) + term_dic_param[TIME_CONDS] = [term] + term_dic_param[COMMON][START_DATE] = term[START_DATE] + term_dic_param[COMMON][START_TM] = term[START_TM] + term_dic_param[COMMON][END_DATE] = term[END_DATE] + term_dic_param[COMMON][END_TM] = term[END_TM] + term_dic_param['term_id'] = term_id + + # get data from database + visual setting from yaml + term_result = gen_graph_term(term_dic_param, max_graph) + if term_result.get(IS_GRAPH_LIMITED): + is_graph_limited = True + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] += term_result.get(MATCHED_FILTER_IDS, []) + dic_param[UNMATCHED_FILTER_IDS] += term_result.get(UNMATCHED_FILTER_IDS, []) + dic_param[NOT_EXACT_MATCH_FILTER_IDS] += term_result.get(NOT_EXACT_MATCH_FILTER_IDS, []) + dic_param[ACTUAL_RECORD_NUMBER] += term_result.get(ACTUAL_RECORD_NUMBER, 0) + + # update term data to original dic_param + dic_param[ARRAY_PLOTDATA].extend(term_result.get(ARRAY_PLOTDATA)) + + dic_param[ARRAY_PLOTDATA], is_graph_limited_second = limit_graph_per_tab(dic_param[ARRAY_PLOTDATA], max_graph) + dic_param[IS_GRAPH_LIMITED] = is_graph_limited or is_graph_limited_second + + # calculating the summaries information + calc_summaries(dic_param) + + # calc common scale y min max + min_max_list, all_graph_min, all_graph_max = calc_raw_common_scale_y(dic_param[ARRAY_PLOTDATA]) + calc_scale_info(dic_param[ARRAY_PLOTDATA], min_max_list, all_graph_min, all_graph_max) + + # generate kde for each trace output array + dic_param = gen_kde_data(dic_param) + + remove_array_x_y(dic_param) + + return dic_param + + +@log_execution_time() +def customize_dict_param(dic_param): + """ Combine start_time, end_time, start_date, end_date into one object + + Arguments: + dic_form {[type]} -- [description] + """ + # end_proc + dic_end_procs = customize_dict_param_common(dic_param) + dic_param[COMMON][END_PROC] = dic_end_procs + dic_param[COMMON][GET02_VALS_SELECT] = list(dic_end_procs) + + # time + dic_param[TIME_CONDS] = gen_time_conditions(dic_param) + + +def gen_time_conditions(dic_param): + start_dates = dic_param.get(COMMON).get(START_DATE) + start_times = dic_param.get(COMMON).get(START_TM) + end_dates = dic_param.get(COMMON).get(END_DATE) + end_times = dic_param.get(COMMON).get(END_TM) + # if type(start_dates) is not list and type(start_dates) is not tuple: + if not isinstance(start_dates, (list, tuple)): + start_dates = [start_dates] + start_times = [start_times] + end_dates = [end_dates] + end_times = [end_times] + + lst_datetimes = [] + if start_dates and start_times and end_dates and end_times and len(start_dates) == len(start_times) == len( + end_dates) == len(end_times): + names = [START_DATE, START_TM, END_DATE, END_TM] + lst_datetimes = [dict(zip(names, row)) for row in zip(start_dates, start_times, end_dates, end_times)] + for idx, time_cond in enumerate(lst_datetimes): + start_dt = start_of_minute(time_cond.get(START_DATE), time_cond.get(START_TM)) + end_dt = end_of_minute(time_cond.get(END_DATE), time_cond.get(END_TM)) + lst_datetimes[idx][START_DT] = start_dt + lst_datetimes[idx][END_DT] = end_dt + + return lst_datetimes + + +@log_execution_time() +def convert_end_cols_to_array(dic_param): + end_col_alias = dic_param[COMMON][GET02_VALS_SELECT] + if type(end_col_alias) == str: + dic_param[COMMON][GET02_VALS_SELECT] = [end_col_alias] + + from_end_col_alias = dic_param[ARRAY_FORMVAL][0][GET02_VALS_SELECT] + if type(from_end_col_alias) == str: + dic_param[ARRAY_FORMVAL][0][GET02_VALS_SELECT] = [from_end_col_alias] + + +@log_execution_time() +def gen_kde_data(dic_param, dic_array_full=None): + array_plotdata = dic_param.get(ARRAY_PLOTDATA) + for num, plotdata in enumerate(array_plotdata): + full_array_y = dic_array_full[num] if dic_array_full else None + kde_list = calculate_kde_trace_data(plotdata, full_array_y=full_array_y) + plotdata[SCALE_SETTING][KDE_DATA], plotdata[SCALE_COMMON][KDE_DATA], plotdata[SCALE_THRESHOLD][KDE_DATA], \ + plotdata[SCALE_AUTO][KDE_DATA], plotdata[SCALE_FULL][KDE_DATA] = kde_list + + return dic_param + + +@log_execution_time() +def gen_kde_data_cate_var(dic_param, dic_array_full=None): + array_plotdatas = dic_param.get(ARRAY_PLOTDATA) + for end_col, array_plotdata in array_plotdatas.items(): + for num, plotdata in enumerate(array_plotdata): + full_array_y = dic_array_full[num] if dic_array_full else None + kde_list = calculate_kde_trace_data(plotdata, full_array_y=full_array_y) + plotdata[SCALE_SETTING][KDE_DATA], plotdata[SCALE_COMMON][KDE_DATA], plotdata[SCALE_THRESHOLD][KDE_DATA], \ + plotdata[SCALE_AUTO][KDE_DATA], plotdata[SCALE_FULL][KDE_DATA] = kde_list + + return dic_param + + +@log_execution_time() +def split_data_by_condition(dic_data, graph_param, max_graph=None): + """split data by condition + + Arguments: + data {[type]} -- [description] + + Returns: + [type] -- [description] + """ + is_graph_limited = False + dic_output = {} + for proc in graph_param.array_formval: + proc_id = proc.proc_id + cat_exp_cols = graph_param.common.cat_exp + + end_cols = proc.col_ids + dic_data_for_df = {Cycle.time.key: dic_data[proc_id][Cycle.time.key], + **{end_col: dic_data[proc_id][end_col] for end_col in end_cols} + } + group_by_cols = [] + for cat_exp_col in cat_exp_cols or []: + group_by_cols.append(cat_exp_col) + if cat_exp_col not in dic_data_for_df: + for dic_col in dic_data.values(): + vals = dic_col.get(cat_exp_col) + if vals: + dic_data_for_df[cat_exp_col] = vals + break + + df = pd.DataFrame(dic_data_for_df) + if not len(df): + continue + + df = df.convert_dtypes() + + if group_by_cols: + dic_col, is_graph_limited = gen_plotdata_with_group_by(df, end_cols, group_by_cols, max_graph) + else: + dic_col = gen_plotdata_without_group_by(df, end_cols) + + dic_output.update(dic_col) + + return dic_output, is_graph_limited + + +def gen_plotdata_without_group_by(df, end_cols): + dic_output = {} + array_x = df[Cycle.time.key].to_list() + for end_col in end_cols: + dic_cate = defaultdict(dict) + dic_output[end_col] = dic_cate + dic_cate[None] = {ARRAY_X: array_x, ARRAY_Y: df[end_col].to_list()} + + return dic_output + + +def gen_plotdata_with_group_by(df, end_cols, group_by_cols, max_graph=None): + is_graph_limit = False + dic_output = {} + df_group = df.groupby(group_by_cols) + limit_cols = end_cols + if max_graph and max_graph < len(end_cols): + is_graph_limit = True + limit_cols = end_cols[:max_graph] + + for end_col in limit_cols: + dic_cate = defaultdict(dict) + dic_output[end_col] = dic_cate + for group_name, idxs in df_group.groups.items(): + if isinstance(group_name, (list, tuple)): + group_name = ' | '.join([str(NA_STR if pd.isna(val) else val) for val in group_name]) + + rows = df.loc[idxs, end_col] + if len(rows.dropna()) == 0: + continue + + dic_cate[group_name] = {ARRAY_X: df.loc[idxs, Cycle.time.key].to_list(), ARRAY_Y: rows.to_list()} + + return dic_output, is_graph_limit + + +def gen_plotdata_for_var(dic_data): + plotdatas = {} + col_ids = list(dic_data.keys()) + dic_procs, dic_cols = get_cfg_proc_col_info(col_ids) + for end_col, cat_exp_data in dic_data.items(): + plotdatas[end_col] = [] + cfg_col: CfgProcessColumn = dic_cols[end_col] + cfg_proc: CfgProcess = dic_procs[cfg_col.process_id] + for cat_exp_name, data in cat_exp_data.items(): + if not data: + continue + + plotdata = {ARRAY_Y: data[ARRAY_Y], ARRAY_X: data[ARRAY_X], + END_PROC_ID: cfg_col.process_id, END_PROC_NAME: cfg_proc.name, + END_COL: end_col, END_COL_NAME: cfg_col.name, CAT_EXP_BOX: cat_exp_name} + plotdatas[end_col].append(plotdata) + + return plotdatas + + +def gen_plotdata_one_proc(dic_data): + plotdatas = [] + col_ids = list(dic_data.keys()) + dic_procs, dic_cols = get_cfg_proc_col_info(col_ids) + for end_col, cat_exp_data in dic_data.items(): + cfg_col: CfgProcessColumn = dic_cols[end_col] + cfg_proc: CfgProcess = dic_procs[cfg_col.process_id] + for cat_exp_name, data in cat_exp_data.items(): + plotdata = {ARRAY_Y: data[ARRAY_Y], ARRAY_X: data[ARRAY_X], + END_PROC_ID: cfg_col.process_id, END_PROC_NAME: cfg_proc.name, + END_COL: end_col, END_COL_NAME: cfg_col.name, CAT_EXP_BOX: cat_exp_name} + plotdatas.append(plotdata) + + return plotdatas + + +@log_execution_time() +def save_input_data_to_gen_images(df: DataFrame, graph_param): + dic_rename_columns = {} + for proc in graph_param.array_formval: + col_ids_names = sorted(zip(proc.col_ids, proc.col_names)) + for col_id, col_name in col_ids_names: + sql_label = gen_sql_label(col_id, col_name) + if sql_label in df.columns: + dic_rename_columns[sql_label] = col_name + + file_path = create_file_path('dat_' + EventType.STP.value + '_image') + + make_dir_from_file_path(file_path) + df.rename(columns=dic_rename_columns).to_csv(file_path, sep=CsvDelimiter.TSV.value, index=False, + columns=list(dic_rename_columns.values())) + return file_path + + +def get_checked_cols(trace, dic_param): + dic_header = {} + for proc in dic_param[ARRAY_FORMVAL]: + proc_name = proc[END_PROC] + end_cols = proc[GET02_VALS_SELECT] + if isinstance(end_cols, str): + end_cols = [end_cols] + + checked_cols = trace.proc_yaml.get_checked_columns(proc_name) + cols = [] + for col, col_detail in checked_cols.items(): + data_type = col_detail[YAML_DATA_TYPES] + # alias_name = col_detail[YAML_ALIASES] + if data_type == DataType.REAL.name or col in end_cols: + continue + + cols.append(col) + + dic_header[proc_name] = cols + return dic_header + + +@log_execution_time() +def use_etl_spray_shape(proc: CfgProcess): + data_source: CfgDataSource = proc.data_source + if data_source.type.lower() == DBType.CSV.name.lower(): + etl_func = data_source.csv_detail.etl_func + if etl_func == FILE_ETL_SPRAY_SHAPE: + return True + return False + + +@log_execution_time() +def category_bind_dic_param_to_class(dic_param): + graph_param = bind_dic_param_to_class(dic_param) + if dic_param[COMMON].get(CYCLIC_TERMS): + graph_param.cyclic_terms += dic_param[COMMON][CYCLIC_TERMS] + + return graph_param + + +@log_execution_time() +def gen_graph_cyclic(dic_param, terms, max_graph=None): + """tracing data to show graph + 1 start point x n end point + filter by condition point + https://files.slack.com/files-pri/TJHPR9BN3-F01GG67J84C/image.pngnts that between start point and end_point + """ + # bind dic_param + orig_graph_param = bind_dic_param_to_class(dic_param) + + graph_param_with_cat_exp = bind_dic_param_to_class(dic_param) + graph_param_with_cat_exp.add_cat_exp_to_array_formval() + + graph_param = bind_dic_param_to_class(dic_param) + + # add start proc + graph_param.add_start_proc_to_array_formval() + + # add condition procs + graph_param.add_cond_procs_to_array_formval() + + # add cat exp (use for category page) + graph_param.add_cat_exp_to_array_formval() + + # get serials + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + serial_ids = [serial.id for serial in proc_cfg.get_serials(column_name_only=False)] + proc.add_cols(serial_ids) + + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = main_check_filter_detail_match_graph_data( + graph_param, df) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] = matched_filter_ids + dic_param[UNMATCHED_FILTER_IDS] = unmatched_filter_ids + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = not_exact_match_filter_ids + + # apply coef for text + df = apply_coef_text(df, orig_graph_param, dic_proc_cfgs) + + # flag to show that trace result was limited + dic_param[DATA_SIZE] = df.memory_usage(deep=True).sum() + dic_param[IS_RES_LIMITED] = is_res_limited + + dic_param[ACTUAL_RECORD_NUMBER] = actual_record_number + + # create output dataJOIN + dic_param[ARRAY_PLOTDATA] = [] + end_procs = orig_graph_param.array_formval + df.set_index(Cycle.time.key, inplace=True, drop=False) + all_plots = [] + is_graph_limited = False + for term_id, term in enumerate(terms): + df_chunk = df[(df.index >= term['start_dt']) & (df.index < term['end_dt'])] + if not len(df_chunk): + continue + + dic_data = gen_dic_data_from_df(df_chunk, graph_param_with_cat_exp) + dic_data, _is_graph_limited = split_data_by_condition(dic_data, orig_graph_param, max_graph) + if _is_graph_limited: + is_graph_limited = True + + plots = gen_plotdata_one_proc(dic_data) + # get graph configs + times = df_chunk[Cycle.time.key].tolist() or [] + dic_data_for_graph_configs = {} + for end_proc in end_procs: + time_col_alias = f'{Cycle.time.key}_{end_proc.proc_id}' + end_col_time = df_chunk[time_col_alias].to_list() + dic_data_for_graph_configs[end_proc.proc_id] = {Cycle.time.key: end_col_time} + + chart_infos, original_graph_configs = get_chart_infos(orig_graph_param, dic_data_for_graph_configs, times) + for plot in plots: + plot['term_id'] = term_id + set_chart_infos_to_plotdata(plot[END_COL], chart_infos, original_graph_configs, plot) + + all_plots += plots + + dic_param[ARRAY_PLOTDATA], dic_param[IS_GRAPH_LIMITED] = limit_graph_per_tab(all_plots, max_graph) + + if is_graph_limited: + dic_param[IS_GRAPH_LIMITED] = True + + return dic_param + + +def gen_graph_term(dic_param, max_graph=None): + """tracing data to show graph + 1 start point x n end point + filter by condition point + https://files.slack.com/files-pri/TJHPR9BN3-F01GG67J84C/image.pngnts that between start point and end_point + """ + # bind dic_param + orig_graph_param = bind_dic_param_to_class(dic_param) + + graph_param_with_cat_exp = bind_dic_param_to_class(dic_param) + graph_param_with_cat_exp.add_cat_exp_to_array_formval() + + graph_param = bind_dic_param_to_class(dic_param) + + # add start proc + graph_param.add_start_proc_to_array_formval() + + # add condition procs + graph_param.add_cond_procs_to_array_formval() + + # add cat exp (use for category page) + graph_param.add_cat_exp_to_array_formval() + + # get serials + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + serial_ids = [serial.id for serial in proc_cfg.get_serials(column_name_only=False)] + proc.add_cols(serial_ids) + + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = main_check_filter_detail_match_graph_data( + graph_param, df) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] = matched_filter_ids + dic_param[UNMATCHED_FILTER_IDS] = unmatched_filter_ids + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = not_exact_match_filter_ids + + # apply coef for text + df = apply_coef_text(df, orig_graph_param, dic_proc_cfgs) + + # flag to show that trace result was limited + dic_param[DATA_SIZE] = df.memory_usage(deep=True).sum() + dic_param[IS_RES_LIMITED] = is_res_limited + + # create output data + dic_data = gen_dic_data_from_df(df, graph_param_with_cat_exp) + dic_data, is_graph_limited = split_data_by_condition(dic_data, orig_graph_param, max_graph) + dic_param[IS_GRAPH_LIMITED] = is_graph_limited + dic_param[ARRAY_PLOTDATA] = gen_plotdata_one_proc(dic_data) + + # get graph configs + times = df[Cycle.time.key].tolist() or [] + end_procs = orig_graph_param.array_formval + dic_data_for_graph_configs = {} + for end_proc in end_procs: + if not len(df): + continue + time_col_alias = f'{Cycle.time.key}_{end_proc.proc_id}' + end_col_time = df[time_col_alias].to_list() + dic_data_for_graph_configs[end_proc.proc_id] = {Cycle.time.key: end_col_time} + + chart_infos, original_graph_configs = get_chart_infos(orig_graph_param, dic_data_for_graph_configs, times) + + for plot in dic_param[ARRAY_PLOTDATA]: + plot['term_id'] = dic_param['term_id'] + set_chart_infos_to_plotdata(plot[END_COL], chart_infos, original_graph_configs, plot) + + dic_param[ACTUAL_RECORD_NUMBER] = actual_record_number + + return dic_param + + +@log_execution_time() +def produce_cyclic_terms(dic_param): # TODO reverse when interval is negative + num_ridge_lines = int(dic_param[COMMON][CYCLIC_DIV_NUM]) + interval = float(dic_param[COMMON][CYCLIC_INTERVAL]) + window_len = float(dic_param[COMMON][CYCLIC_WINDOW_LEN]) + start_date = dic_param[COMMON][START_DATE] + start_time = dic_param[COMMON][START_TM] + start_datetime = '{}T{}'.format(start_date, start_time) # '2020/11/01T00:00' + + cyclic_terms = [] + prev_start = datetime.strptime(start_datetime, RL_DATETIME_FORMAT) + end = prev_start + timedelta(hours=window_len) + start_utc_str = datetime.strftime(prev_start.replace(tzinfo=tz.tzutc()), DATE_FORMAT_STR) + end_utc_str = datetime.strftime(end.replace(tzinfo=tz.tzutc()), DATE_FORMAT_STR) + cyclic_terms.append((start_utc_str, end_utc_str)) + + for i in range(1, num_ridge_lines): + start = prev_start + timedelta(hours=interval) + end = start + timedelta(hours=window_len) + start_utc_str = datetime.strftime(start.replace(tzinfo=tz.tzutc()), DATE_FORMAT_STR) + end_utc_str = datetime.strftime(end.replace(tzinfo=tz.tzutc()), DATE_FORMAT_STR) + cyclic_terms.append((start_utc_str, end_utc_str)) + prev_start = start + + # get new start/end datetime + last_cyclic_term_end = cyclic_terms[-1][1] + end_dt = datetime.strptime(last_cyclic_term_end, DATE_FORMAT_STR) + end_date = datetime.strftime(end_dt, DATE_FORMAT) + end_time = datetime.strftime(end_dt, TIME_FORMAT) + + if interval < 0: # exchange start time and end time when interval is negative + dic_param[COMMON][END_DATE] = start_date + dic_param[COMMON][END_TM] = start_time + dic_param[COMMON][START_DATE] = end_date + dic_param[COMMON][START_TM] = end_time + dic_param[TIME_CONDS] = { + END_DATE: start_date, + END_TM: start_time, + END_DT: end_of_minute(start_date, start_time), + START_DATE: end_date, + START_TM: end_time, + START_DT: start_of_minute(end_date, end_time), + } + else: + # set END date/time + dic_param[COMMON][END_DATE] = end_date + dic_param[COMMON][END_TM] = end_time + if dic_param.get(TIME_CONDS): + time_cond = dic_param[TIME_CONDS][0] + time_cond[END_DATE] = end_date + time_cond[END_TM] = end_time + time_cond[END_DT] = end_of_minute(end_date, end_time) + + dic_param[COMMON][CYCLIC_TERMS] = cyclic_terms + + +def remove_array_x_y(dic_param): + for plot in dic_param[ARRAY_PLOTDATA]: + if not plot: + continue + + del plot[ARRAY_X] + del plot[ARRAY_Y] + return True + + +def remove_array_x_y_cyclic(dic_param): + for plots in dic_param[ARRAY_PLOTDATA].values(): + for plot in plots: + if not plot: + continue + + del plot[ARRAY_X] + del plot[ARRAY_Y] + + return True + + +@log_execution_time() +@notify_progress(75) +def dump_img_files(df, graph_param, dic_proc_cfgs): + # TODO: minor trick to resolve nested-trace_log problem + img_files = [] + if not df.index.size: + return img_files + + # make input tsv file + tsv_file = save_input_data_to_gen_images(df, graph_param) + + if use_etl_spray_shape(dic_proc_cfgs[graph_param.common.start_proc]): + img_files = call_com_view(tsv_file, get_view_path()) + + # strip folder + if img_files is not None and not isinstance(img_files, Exception): + if isinstance(img_files, str): + img_files = [img_files] + img_files = [get_basename(img) for img in img_files] + + return img_files + + +def customize_dict_param_common(dic_param): + dic_end_procs = {} + end_procs = dic_param.get(ARRAY_FORMVAL) + for end_proc in end_procs: + proc_id = end_proc.get(END_PROC) + if isinstance(proc_id, list): + proc_id = proc_id[0] + + col_ids = end_proc.get(GET02_VALS_SELECT) + if not isinstance(col_ids, list): + col_ids = [col_ids] + + for col_id in col_ids: + dic_end_procs[int(col_id)] = int(proc_id) + + return dic_end_procs + + +@log_execution_time() +def limit_graph_per_tab(plots, max_graph=None): + is_limited = False + if max_graph is None: + return plots, is_limited + + dic_count = defaultdict(int) + limit_plots = [] + for plot in plots: + col_id = plot[END_COL] + dic_count[col_id] += 1 + if dic_count[col_id] > max_graph: + is_limited = True + continue + + limit_plots.append(plot) + + return limit_plots, is_limited diff --git a/histview2/api/co_occurrence/controllers.py b/histview2/api/co_occurrence/controllers.py new file mode 100644 index 0000000..21d7825 --- /dev/null +++ b/histview2/api/co_occurrence/controllers.py @@ -0,0 +1,77 @@ +import json + +from flask import Blueprint, request, jsonify + +from histview2.api.co_occurrence.services import validate_csv_data, calc_csv_graph_data, add_node_coordinate, \ + filter_edge_by_threshold, calc_pareto +from histview2.api.setting_module.services.csv_import import csv_to_df +from histview2.common.common_utils import get_csv_delimiter +from histview2.common.services import http_content +from histview2.common.yaml_utils import * + +api_co_occurrence_blueprint = Blueprint( + 'api_co_occurrence', + __name__, + url_prefix='/histview2/api/cog' +) + + +@api_co_occurrence_blueprint.route('/check_file', methods=['POST']) +def check_file(): + try: + data = request.json.get('url') + return jsonify({ + 'status': 200, + 'url': data, + 'is_exists': os.path.isfile(data) and os.path.exists(data), + 'dir': os.path.dirname(data) + }) + except Exception: + # raise + return jsonify({ + 'status': 500, + }) + + +@api_co_occurrence_blueprint.route('/show_graph', methods=['POST']) +def show_graph(): + from_file = False + file = request.files.get('file') + file_path = request.form.get('url') + if file: + file_path = file.read() + from_file = True + + delimiter = request.form.get('delimiter') + aggregate_by = request.form.get('aggregate_by') + threshold = request.form.get('threshold') or 100 + layout = request.form.get('layout') + aggregate_by = AggregateBy(aggregate_by) + + data_src: CfgDataSourceCSV = CfgDataSourceCSV() + data_src.delimiter = delimiter + + # csv delimiter + csv_delimiter = get_csv_delimiter(delimiter) + + # read csv file + data_first_row = 1 + skip_row = 0 + df = csv_to_df(file_path, data_src, [], data_first_row, skip_row, + csv_delimiter, default_csv_param={}, from_file=from_file) + + validate_result = validate_csv_data(df) + if isinstance(validate_result, Exception): + return validate_result, 200 + + # calc pareto data + pareto = calc_pareto(df) + + # calc_data + nodes, edges = calc_csv_graph_data(df, aggregate_by, pareto) + nodes = add_node_coordinate(nodes, layout=layout) + edges = filter_edge_by_threshold(edges, threshold) + + result = dict(nodes=nodes, edges=edges, pareto=pareto) + out_dict = json.dumps(result, ensure_ascii=False, default=http_content.json_serial) + return out_dict, 200 diff --git a/histview2/api/co_occurrence/services.py b/histview2/api/co_occurrence/services.py new file mode 100644 index 0000000..441c89e --- /dev/null +++ b/histview2/api/co_occurrence/services.py @@ -0,0 +1,196 @@ +import itertools + +import numpy as np +import pandas as pd +from flask_babel import gettext as _ + +from histview2.common.constants import * +from histview2.common.logger import logger +from histview2.common.services.sse import notify_progress +from histview2.common.trace_data_log import * +from histview2.common.yaml_utils import YamlConfig + +dic_colors = { + "bar_highlight": "#729e44", # green, same as other charts + "bar_normal": "#8d8b8b", + "line_80": "#a9a9a9", + "chart_title": "#3385b7", # blue, same as other charts +} + + +def validate_csv_data(df: DataFrame): + if df is None or df.size == 0: + return Exception('There is no data') + + cols = df.columns.tolist() + date_time_col = cols[0] + data_cols = cols[1:] + + # fill na + df.dropna(how='all', inplace=True) + df.fillna(0, inplace=True) + + # convert time + try: + df[date_time_col] = pd.to_datetime(df[date_time_col]) + except Exception as e: + logger.exception(e) + return Exception('There are some none datetime values in Datetime column (First column)') + + # check numeric + try: + for col in data_cols: + df[col] = pd.to_numeric(df[col]) + except Exception as e: + logger.exception(e) + return Exception('There are some none integer values in data columns') + + # check is int data type + for col in data_cols: + try: + df[col] = pd.to_numeric(df[col]) + except Exception as e: + logger.exception(e) + return Exception('There are some none integer values in data columns') + + # convert float to int + df[col] = df[col].convert_dtypes() + + # check < zero + if (df[col] < 0).any(): + return Exception('There are some values < 0 in data columns') + + # check not integer ( float ) + if df.select_dtypes(include=['integer']).columns.size < len(data_cols): + return Exception('There are some float values in data columns') + + return True + + +@notify_progress(60) +def calc_csv_graph_data(df: DataFrame, aggregate_by: AggregateBy, pareto=None): + if pareto is None: + pareto = {} + cols = df.columns.tolist() + date_time_col = cols[0] + data_cols = cols[1:] + + df.set_index(date_time_col, inplace=True) + + if aggregate_by is AggregateBy.HOUR: + freq = 'H' + else: + freq = 'D' + + df_sum = df.groupby(pd.Grouper(freq=freq)).sum() + nodes_cum_rate_80 = YamlConfig.get_node(pareto, ['bar', 'highlight_bars'], set()) or set() + nodes = [] + for key, val in df_sum.sum().to_dict().items(): + color = dic_colors["bar_highlight"] if key in nodes_cum_rate_80 else '' + nodes.append(dict(id=key, label=key, size=int(val), color=color)) + + edges = [] + for source, target in itertools.combinations(data_cols, 2): + edge = [source, target] + size = int(df_sum[edge].min(axis=1).sum()) + edge_id = '-'.join([str(node_id) for node_id in edge]) + dic_edge = dict(id=edge_id, label=size, source=source, target=target) + edges.append(dic_edge) + + return nodes, edges + + +def add_node_coordinate(nodes, layout='CIRCLE'): + if layout == 'CIRCLE': + nodes = gen_circular_coordinate(nodes) + elif layout == 'FORCE_ATLAS_2': + nodes = gen_random_coordinate(nodes) + return nodes + + +def gen_circular_coordinate(nodes): + if not nodes: + return [] + radius = 200 # TODO + num_nodes = len(nodes) # number of sensors (what user selected) + # note that 0 and 2*pi indicate same position + theta = np.linspace(0.5 * np.pi, 2.5 * np.pi, num_nodes + 1) + theta = theta[:num_nodes] + + pos_x = np.cos(theta) * radius + pos_y = np.sin(theta) * radius + for idx, node in enumerate(nodes): + node['x'] = -pos_x[idx] + node['y'] = -pos_y[idx] + return nodes + + +def gen_random_coordinate(nodes): + if not nodes: + return [] + import random + for idx, node in enumerate(nodes): + node['x'] = random.randint(1, 5) + node['y'] = random.randint(1, 5) + return nodes + + +def filter_edge_by_threshold(edges, threshold=100): + if not edges: + return [] + edges = sorted(edges, key=lambda x: x.get('label') or -1, reverse=True) + limit = round(int(threshold) * len(edges) / 100) + edges = edges[0:limit] + return edges + + +@notify_progress(30) +def calc_pareto(df: pd.DataFrame): + drop_col = df.columns.values[0] + + # ----- summarize ----- + # sort with descending order and take cumsum for pareto chart + total_occurrences = df.drop(drop_col, axis="columns").sum(axis="index").sort_values(ascending=False) + cum_occurrences_ratio = total_occurrences.cumsum() / total_occurrences.sum() + alarm_names = total_occurrences.index.values + print("alarm names: ", alarm_names[:5], "...") + print("total number of alarms: ", total_occurrences.values[:5], "...") + print("cumulative ratio of number of alarms [%]: ", cum_occurrences_ratio.values[:5], "...") + + # change color of bar (highlight cumulative ratio <= 80%) + # note that this list is not in the original column order. + # bar_colors = [dic_colors["bar_highlight"] if x <= 0.8 else dic_colors["bar_normal"] for x in cum_occurrences_ratio] + highlight_bars = set() + bar_colors = [] + for alarm_name, cum_ratio_value in list(zip(cum_occurrences_ratio.index, cum_occurrences_ratio)): + if cum_ratio_value <= 0.8: + color = dic_colors["bar_highlight"] + highlight_bars.add(alarm_name) + else: + color = dic_colors["bar_normal"] + bar_colors.append(color) + + return { + 'title': _('Pareto Chart'), + 'bar': { + 'y': alarm_names, + 'x': total_occurrences, + 'name': _('Total Occurrences'), + 'orientation': "h", + 'marker_color': bar_colors, + 'text': total_occurrences.values, + 'highlight_bars': highlight_bars, + }, + 'line_cum_ratio': { + 'x': cum_occurrences_ratio * total_occurrences.max(), + 'name': _('Cumulative Ratio [%]'), + 'text': cum_occurrences_ratio.values * 100, + 'mode': 'lines+markers', + }, + 'line_80_percent': { + 'x': np.repeat(0.8, len(alarm_names)) * total_occurrences.max(), + 'name': "80 [%]", + 'marker_color': dic_colors["line_80"], + 'mode': "lines", + } + } diff --git a/histview2/api/efa/services/__init__.py b/histview2/api/efa/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/api/efa/services/etl.py b/histview2/api/efa/services/etl.py new file mode 100644 index 0000000..a7f21d6 --- /dev/null +++ b/histview2/api/efa/services/etl.py @@ -0,0 +1,156 @@ +from histview2.common.common_utils import detect_encoding +from histview2.common.common_utils import get_temp_path, get_wrapr_path, get_etl_path, get_base_dir, make_dir +from histview2.common.constants import CfgConstantType, CsvDelimiter +from histview2.common.logger import logger, log_execution_time +from histview2.common.services.sse import notify_progress +from histview2.script.r_scripts.wrapr import wrapr_utils +from histview2.setting_module.models import CfgConstant + +FILE = 'etl_spray_shape.R' + +UNKNOWN_ERROR_MESSAGE = 'NO OUTPUT FROM R SCRIPT' +NO_DATA_ERROR = 'NoDataError' + + +@log_execution_time() +def preview_data(fname): + """ + transform data , output will be put in temp folder + """ + output_fname = call_com_read(fname, get_temp_path()) + return output_fname + + +@log_execution_time() +def csv_transform(proc_id, fname): + """transform to standard csv + + """ + out_dir = get_base_dir(fname) + etl_dir = get_etl_path(str(proc_id), out_dir) + make_dir(etl_dir) + + output_fname = call_com_read(fname, etl_dir) + return output_fname + + +@log_execution_time() +def call_com_read(fname, out_dir): + """call com read func to transform data + + Args: + fname ([type]): [description] + out_dir ([type]): [description] + + Returns: + [type]: [description] + """ + target_func = 'com_read' + + # define parameters + dic_data = {} # filecheckr does not need input data + dic_task = dict(func=target_func, file=FILE, fpath=fname) + + # define and run pipeline + try: + pipe = wrapr_utils.RPipeline(get_wrapr_path(), out_dir, use_pkl=False, verbose=True) + out = pipe.run(dic_data, [dic_task]) + except Exception as e: + logger.error(e) + return e + + if out: + error = out.get('err', None) + error_type = out.get('err_type', None) + if error: + if error_type == NO_DATA_ERROR: + return None + + logger.error(error) + return Exception(error) + + # save latest json string + json_str = out['results']['pass'] + save_etl_json(FILE, json_str) + + # return + return out['results']['fname_out'] + + return Exception(UNKNOWN_ERROR_MESSAGE) + + +@log_execution_time() +@notify_progress(60) +def call_com_view(fname, out_dir): + """call com view func to export image + + Args: + fname ([type]): [description] + out_dir ([type]): [description] + + Returns: + [type]: [description] + """ + target_func = 'com_view' + + # get json string + json_str = load_etl_json(FILE) + + # define parameters + dic_data = {} + dic_task = {'func': target_func, 'file': FILE, 'fpath': fname, 'pass': json_str} + + # define and run pipeline + try: + pipe = wrapr_utils.RPipeline(get_wrapr_path(), out_dir, use_pkl=False, verbose=True) + out = pipe.run(dic_data, [dic_task]) + except Exception as e: + logger.error(e) + return e + + if out: + error = out.get('err', None) + if error: + logger.error(error) + return Exception(error) + + return out['results']['fname_out'] + + return Exception(UNKNOWN_ERROR_MESSAGE) + + +@log_execution_time() +def save_etl_json(script_fname, json_str): + CfgConstant.create_or_update_by_type(const_type=CfgConstantType.ETL_JSON.name, + const_value=json_str, const_name=script_fname) + + +@log_execution_time() +def load_etl_json(script_fname): + # get json string + json_str = CfgConstant.get_value_by_type_name(CfgConstantType.ETL_JSON.name, script_fname, str) + return json_str + + +@log_execution_time() +def detect_file_delimiter(file_path, default_delimiter): + white_list = [CsvDelimiter.CSV.value, CsvDelimiter.TSV.value, CsvDelimiter.SMC.value] + encoding = detect_encoding(file_path) + candidates = [] + with open(file_path, "r", encoding=encoding) as f: + for i in range(200): + try: + line = f.readline() + except StopIteration: + break + + if line: + _, row_delimiter = max([(len(line.split(split_char)), split_char) for split_char in white_list]) + candidates.append(row_delimiter) + + if candidates: + good_delimiter = max(candidates, key=candidates.count) + if good_delimiter is not None: + return good_delimiter + + return default_delimiter diff --git a/histview2/api/heatmap/__init__.py b/histview2/api/heatmap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/api/heatmap/controllers.py b/histview2/api/heatmap/controllers.py new file mode 100644 index 0000000..2826615 --- /dev/null +++ b/histview2/api/heatmap/controllers.py @@ -0,0 +1,56 @@ +import timeit + +import simplejson +from flask import Blueprint, request + +from histview2.api.categorical_plot.services import customize_dict_param +from histview2.api.heatmap.services import gen_heatmap_data +from histview2.common.constants import COMMON, START_PROC, ARRAY_FORMVAL, END_PROC +from histview2.common.services import http_content +from histview2.common.services.form_env import parse_multi_filter_into_one +from histview2.common.services.import_export_config_n_data import get_dic_form_from_debug_info, \ + set_export_dataset_id_to_dic_param +from histview2.common.trace_data_log import save_input_data_to_file, EventType + +api_heatmap_blueprint = Blueprint( + 'api_heatmap', + __name__, + url_prefix='/histview2/api/chm' +) + + +@api_heatmap_blueprint.route('/plot', methods=['POST']) +def generate_heatmap(): + """ [summary] + Returns: + [type] -- [description] + """ + start = timeit.default_timer() + dic_form = request.form.to_dict(flat=False) + mode = dic_form.get('mode') or [] + if mode and '1' in set(mode): # 1 for daily and 7 for weekly + dic_form['step'] = dic_form['step_minute'] + else: + dic_form['step'] = dic_form['step_hour'] + + # save dic_form to pickle (for future debug) + save_input_data_to_file(dic_form, EventType.CHM) + + dic_param = parse_multi_filter_into_one(dic_form) + + # check if we run debug mode (import mode) + dic_param = get_dic_form_from_debug_info(dic_param) + + start_proc = dic_param[COMMON].get(START_PROC) + dic_param[COMMON][START_PROC] = start_proc if start_proc else dic_param[ARRAY_FORMVAL][0][END_PROC] + + customize_dict_param(dic_param) + dic_param = gen_heatmap_data(dic_param) + + stop = timeit.default_timer() + dic_param['backend_time'] = stop - start + + # export mode ( output for export mode ) + set_export_dataset_id_to_dic_param(dic_param) + + return simplejson.dumps(dic_param, ensure_ascii=False, default=http_content.json_serial, ignore_nan=True) diff --git a/histview2/api/heatmap/services.py b/histview2/api/heatmap/services.py new file mode 100644 index 0000000..2e469ab --- /dev/null +++ b/histview2/api/heatmap/services.py @@ -0,0 +1,660 @@ +import math +from datetime import timedelta, datetime +from typing import Dict, List + +import numpy as np +import pandas as pd +from dateutil import parser +from dateutil.tz import tz +from flask_babel import gettext as _ +from scipy.stats import iqr + +from histview2.api.categorical_plot.services import gen_graph_param +from histview2.api.trace_data.services.time_series_chart import validate_data, graph_one_proc, \ + main_check_filter_detail_match_graph_data +from histview2.common.common_utils import start_of_minute, end_of_minute, DATE_FORMAT_QUERY, gen_sql_label, \ + reformat_dt_str +from histview2.common.constants import TIME_COL, CELL_SUFFIX, AGG_COL, ARRAY_PLOTDATA, HMFunction, DataType, \ + MATCHED_FILTER_IDS, UNMATCHED_FILTER_IDS, NOT_EXACT_MATCH_FILTER_IDS, ACTUAL_RECORD_NUMBER, IS_RES_LIMITED, \ + AGG_FUNC, CATE_VAL, END_COL, X_TICKTEXT, X_TICKVAL, Y_TICKTEXT, Y_TICKVAL, ACT_CELLS, MAX_TICKS, NA_STR +from histview2.common.logger import log_execution_time +from histview2.common.memoize import memoize +from histview2.common.services.sse import notify_progress, background_announcer, AnnounceEvent +from histview2.common.sigificant_digit import signify_digit2 +from histview2.common.trace_data_log import TraceErrKey, EventType, EventAction, Target, trace_log +from histview2.setting_module.models import CfgProcess +from histview2.trace_data.schemas import DicParam + + +@log_execution_time() +@notify_progress(75) +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.CHM, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_heatmap_data(dic_param): + # gen graph_param + graph_param, dic_proc_cfgs = gen_graph_param(dic_param) + + hm_mode = int(graph_param.common.hm_mode) + hm_step = int(graph_param.common.hm_step) + # start proc + start_tm = start_of_minute(graph_param.common.start_date, graph_param.common.start_time) + end_tm = end_of_minute(graph_param.common.end_date, graph_param.common.end_time) + client_timezone = graph_param.get_client_timezone() + # client_tz = pytz.timezone(client_timezone) if client_timezone else tz.tzlocal() + client_tz = tz.gettz(client_timezone or None) or tz.tzlocal() + + # generate all cells + cells = gen_cells(start_tm, end_tm, hm_mode, hm_step) + df_cells = pd.DataFrame({TIME_COL: cells}) + # time_delta = calc_time_delta(hm_mode, hm_step, start_tm) + offset = get_utc_offset(client_tz) + df_cells = convert_cell_tz(df_cells, offset) + df_cells = gen_agg_col(df_cells, hm_mode, hm_step) + + # limit to 10000 cells + dic_param.update({ACT_CELLS: df_cells.index.size}) + df_cells, end_tm, is_res_limited = limit_num_cells(df_cells, end_tm, offset) + dic_param.update({IS_RES_LIMITED: is_res_limited}) + + # generate x, y, x_label, y_label + df_cells = gen_x_y(df_cells, hm_mode, hm_step, start_tm, end_tm, client_tz) + + # build dic col->function + dic_col_func = build_dic_col_func(dic_proc_cfgs, graph_param) + + # get stratified variable + var_col_id = graph_param.get_cate_var_col_id() + var_agg_col = None + if var_col_id: + var_col_name = graph_param.get_cate_var_col_name() + var_agg_col = gen_sql_label(var_col_id, var_col_name) + + dic_df_proc = dict() + num_proc = len(dic_proc_cfgs.keys()) or 1 + total_actual_record_number = 0 + for idx, (proc_id, proc_config) in enumerate(dic_proc_cfgs.items()): + if graph_param.is_end_proc(proc_id): + pct_start = (idx + 1) * 50 / num_proc # to report progress + dic_df_proc[proc_id], dic_filter_results, actual_record_number = graph_heatmap_data_one_proc( + proc_config, graph_param, start_tm, end_tm, offset, dic_col_func, var_agg_col, pct_start) + total_actual_record_number += actual_record_number + + # fill empty cells + dic_df_proc = fill_empty_cells(df_cells, dic_df_proc, var_agg_col) + + # gen plotly data + gen array_plotdata from here + dic_param = gen_plotly_data(dic_param, dic_df_proc, hm_mode, hm_step, dic_col_func, df_cells, var_agg_col) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param.update(dic_filter_results) + + # real records + dic_param[ACTUAL_RECORD_NUMBER] = total_actual_record_number + + return dic_param + + +def get_utc_offset(time_zone): + """ + get utc time offset + :param time_zone: str, timezone object + :return: timedelta(seconds) + """ + if isinstance(time_zone, str): + time_zone = tz.gettz(time_zone) + + time_in_tz = datetime.now(tz=time_zone) + time_offset = time_in_tz.utcoffset().seconds + time_offset = timedelta(seconds=time_offset) + + return time_offset + + +def limit_num_cells(df_cells: pd.DataFrame, end_tm, offset, limit=10000): + """ Limit number of cells to 10k including empty cells """ + is_res_limited = df_cells.index.size > limit + + print("///// is_res_limited: ", is_res_limited, ': ', df_cells.index.size) + df_cells: pd.DataFrame = df_cells.loc[:limit] + + # update new end_time to 10000 cells + last_cell_time = list(df_cells.tail(1)[TIME_COL])[0] + end_tm_tz = pd.Timestamp(end_tm) + offset + new_end_time = np.minimum(end_tm_tz, last_cell_time) + new_end_tm = new_end_time.strftime(DATE_FORMAT_QUERY) + + return df_cells, new_end_tm, is_res_limited + + +@log_execution_time() +def gen_cells(start_tm, end_tm, hm_mode, hm_step): + """ Generate cells of heatmap """ + floor_start_tm = pd.Timestamp(start_tm) + floor_end_tm = pd.Timestamp(end_tm).replace(microsecond=0) + cells = [floor_start_tm] + prev = floor_start_tm + while prev < floor_end_tm: + if hm_mode == 1: + next_cell = prev + pd.Timedelta(minutes=hm_step) + else: + next_cell = prev + pd.Timedelta(hours=hm_step) + cells.append(next_cell) + prev = next_cell + + return cells[:-1] + + +@log_execution_time() +def fill_empty_cells(df_cells: pd.DataFrame, dic_df_proc, var_agg_col=None): + """ Some cells don't have data -> need to fill """ + for proc_id, proc_data in dic_df_proc.items(): + for end_col in proc_data: + df_sensor: pd.DataFrame = dic_df_proc[proc_id][end_col].set_index(AGG_COL) + if var_agg_col: + dic_df_proc[proc_id][end_col] = dict() + dic_df_cate = {cate_value: df_cate for cate_value, df_cate in df_sensor.groupby(var_agg_col)} + df_cates = list(dic_df_cate.items())[:30] + for cate_value, df_cate in df_cates: + dic_df_proc[proc_id][end_col][cate_value] = df_cells.set_index(AGG_COL) \ + .join(df_cate, how="left", lsuffix=CELL_SUFFIX).replace({np.nan: None}) + else: + dic_df_proc[proc_id][end_col] = df_cells.set_index(AGG_COL) \ + .join(df_sensor, how="left", lsuffix=CELL_SUFFIX).replace({np.nan: None}) + return dic_df_proc + + +@log_execution_time() +def gen_y_ticks(hm_mode, hm_step): + """ Generate ticks of y-axis """ + if hm_mode == 7: + ticktext = ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', 'Sun'] + row_per_day = int(24 / hm_step) + tickvals = [(i + 1) * row_per_day for i in range(len(ticktext))] + else: + ticktext = ['24:00', '22:00', '20:00', '18:00', '16:00', '14:00', '12:00', '10:00', ' 8:00', ' 6:00', ' 4:00', + ' 2:00', ' 0:00'] + row_per_2hour = 120 / hm_step + tickvals = [i * row_per_2hour for i in range(len(ticktext))] + + return ticktext, tickvals + + +@log_execution_time() +def get_x_ticks(df: pd.DataFrame): + """ Generate ticks of x-axis """ + df_ticks = df.drop_duplicates('x', keep='last') + df_ticks = df_ticks.drop_duplicates('x_label', keep='first') + size = df_ticks.index.size + if size <= MAX_TICKS: + return df_ticks['x'].tolist(), df_ticks['x_label'].tolist() + + step = math.ceil(size / MAX_TICKS) + indices = np.array(range(size)) + selected_indices = indices[0:-1:step] + df_ticks = df_ticks.reset_index() + df_ticks = df_ticks.loc[df_ticks.index.intersection(selected_indices)] + return df_ticks['x'].tolist(), df_ticks['x_label'].tolist() + + +def build_hover(df, end_col, hm_function): + df['hover'] = df['from'].astype(str) + \ + df['to'].astype(str) + \ + hm_function + ': ' + df[end_col].astype(str).str.replace('None', NA_STR) + '
' + return df['hover'].to_list() + + +def build_plot_data(df, end_col, hm_function): + """ Build data for heatmap trace of Plotly """ + df = df.sort_values(by=['x', 'y']) + df = df.replace(dict.fromkeys([np.inf, -np.inf, np.nan], np.nan)) + df = df.where(pd.notnull(df), None) + + x = df['x'].to_list() + y = df['y'].to_list() + z = df[end_col].to_list() + + # build color scale + z_min = df[end_col].dropna().min() + if np.isnan(z_min): + z_min = None + z_max = df[end_col].dropna().max() + if np.isnan(z_max): + z_max = None + + hover_texts = build_hover(df, end_col, hm_function) + + return { + 'x': x, + 'y': y, + 'z': z, + 'z_min': z_min, + 'z_max': z_max, + 'hover': hover_texts, + } + + +def get_function_i18n(hm_function): # TODO better. can be moved to frontend + """ Generate i18n aggregate function name """ + return _('CHM' + hm_function.replace('_', ' ').title().replace(' ', '')) + + +@log_execution_time() +@notify_progress(60) +def gen_plotly_data(dic_param: dict(), dic_df_proc: dict(), hm_mode, hm_step, dic_col_func: dict(), df_cells, + var_agg_col=None): + dic_param[ARRAY_PLOTDATA] = dict() + + # gen x-axis ticks: ticktexts + tickvals daily, weekly, monthly, yearly + x_tickvals, x_ticktext = get_x_ticks(df_cells) + + # gen y-axis ticks: ticktexts + tickvals + y_ticktext, y_tickvals = gen_y_ticks(hm_mode, hm_step) + plot_count = 0 + for proc_id, proc_data in dic_df_proc.items(): + dic_param[ARRAY_PLOTDATA][proc_id] = [] + for end_col, end_col_data in proc_data.items(): + hm_function = get_function_i18n(dic_col_func[proc_id][end_col].name) + if var_agg_col: + for cate_value, df_cate in end_col_data.items(): + df_sensor_agg: pd.DataFrame = df_cate + plotdata: dict = build_plot_data(df_sensor_agg, end_col, hm_function) + plotdata.update({ + AGG_FUNC: hm_function, + CATE_VAL: cate_value, + END_COL: end_col, + X_TICKTEXT: x_ticktext, + X_TICKVAL: x_tickvals, + Y_TICKTEXT: y_ticktext, + Y_TICKVAL: y_tickvals, + }) + dic_param[ARRAY_PLOTDATA][proc_id].append(plotdata) + else: + df_sensor_agg: pd.DataFrame = end_col_data + plotdata: dict = build_plot_data(df_sensor_agg, end_col, hm_function) + plotdata.update({ + AGG_FUNC: hm_function, + END_COL: end_col, + X_TICKTEXT: x_ticktext, + X_TICKVAL: x_tickvals, + Y_TICKTEXT: y_ticktext, + Y_TICKVAL: y_tickvals, + }) + dic_param[ARRAY_PLOTDATA][proc_id].append(plotdata) + + plot_count += len(dic_param[ARRAY_PLOTDATA][proc_id]) + + # limit to show only 30 graphs + if plot_count > 30: + remain = 30 + for proc_id, plot_datas in dic_param[ARRAY_PLOTDATA].items(): + num_plot = len(plot_datas) + keep = min(remain, num_plot) + dic_param[ARRAY_PLOTDATA][proc_id] = plot_datas[:keep] + remain -= keep + remain = max(0, remain) + + return dic_param + + +@log_execution_time() +def gen_agg_col(df: pd.DataFrame, hm_mode, hm_step): + """ Aggregate data by time """ + pd_step = convert_to_pandas_step(hm_step, hm_mode) + print(df.index.size) + if hm_mode == 7: + # .astype(str).str[:13] or 16 sometimes doesn't work as expected + df[AGG_COL] = df[TIME_COL].dt.floor(pd_step).dt.strftime('%Y-%m-%d %H') + else: + df[AGG_COL] = df[TIME_COL].dt.floor(pd_step).dt.strftime('%Y-%m-%d %H:%M') + return df + + +def gen_weekly_ticks(df: pd.DataFrame): + # tick weekly, first day of week, sunday + df['x_label'] = df[TIME_COL] - ((df[TIME_COL].dt.weekday + 1) % 7) * np.timedelta64(1, 'D') + df['x_label'] = get_year_week_in_df_column(df['x_label']) \ + + "
" + df['x_label'].dt.month.astype(str).str.pad(2, fillchar='0') \ + + "-" + df['x_label'].dt.day.astype(str).str.pad(2, fillchar='0') + return df['x_label'] + + +def get_year_week_in_df_column(column: pd.DataFrame.columns): + """ get year and week with format 'yy,w' -> '22, 20' """ + return column.dt.year.astype(str).str[-2:] + ", " \ + + (column.dt.strftime('%U').astype(int) + 1).astype(str).str.pad(2, fillchar='0') + + +def convert_cell_tz(df: pd.DataFrame, offset): + df[TIME_COL] = df[TIME_COL] + offset + return df + + +@log_execution_time() +def gen_x_y(df: pd.DataFrame, hm_mode, hm_step, start_tm, end_tm, client_tz=tz.tzlocal()): + """ Generate x, y values and text labels of x and y axes """ + start_dt = parser.parse(start_tm) + end_dt = parser.parse(end_tm) + diff: timedelta = end_dt - start_dt + num_days = diff.days + + if hm_mode == 7: + # gen y + row_per_day = int(24 / hm_step) + df['dayofweek'] = df[TIME_COL].dt.day_name().astype(str).str[:3] + df['newdayofweek'] = (12 - df[TIME_COL].dt.dayofweek) % 7 # sat,fri,...,mon,sun + df['y'] = int(24 / hm_step) - (df[TIME_COL].dt.hour / hm_step).astype(int) + df[ + 'newdayofweek'] * row_per_day + + # gen x + df['year'] = df[TIME_COL].dt.year + min_year = df['year'].min() + df['x'] = df[TIME_COL].dt.strftime('%U').astype(int) + 1 + (df['year'] % min_year) * 53 + + # x_label + if num_days <= 140: + df['x_label'] = gen_weekly_ticks(df) + elif num_days <= 365 * 2: + # tick monthly + df['x_label'] = get_year_week_in_df_column(df[TIME_COL]) + '
' \ + + df[TIME_COL].dt.month.astype(str).str.pad(2, fillchar='0') + '-01' + else: + # tick yearly + df['x_label'] = get_year_week_in_df_column(df[TIME_COL]) + '
01-01' + else: + # gen y + num_rows = int(1440 / hm_step) + row_per_hour = 60 / hm_step + df['dayofweek'] = df[TIME_COL].dt.day_name().astype(str).str[:3] + if hm_step > 60: + df['y'] = num_rows - ( + ((df[TIME_COL].dt.minute + df[TIME_COL].dt.hour * 60) / hm_step).astype(float)) + else: + df['y'] = num_rows - ( + (df[TIME_COL].dt.minute / hm_step).astype(int) + (df[TIME_COL].dt.hour * row_per_hour).astype(int)) + + # gen x + df['year'] = df[TIME_COL].dt.year + min_year = df['year'].min() + df['x'] = df[TIME_COL].dt.dayofyear + 366 * (df['year'] % min_year) + + # x_label + if num_days <= 21: + # tick daily + df['x_label'] = get_year_week_in_df_column(df[TIME_COL]) \ + + "
" + df[TIME_COL].dt.date.astype(str).str[5:] + elif num_days <= 140: + df['x_label'] = gen_weekly_ticks(df) + elif num_days <= 365 * 2: + # tick monthly + df['x_label'] = get_year_week_in_df_column(df[TIME_COL]) + '
' \ + + df[TIME_COL].dt.month.astype(str).str.pad(2, fillchar='0') + '-01' + else: + # tick yearly + df['x_label'] = get_year_week_in_df_column(df[TIME_COL]) + '
01-01' + + df['from'] = 'From: ' + df[TIME_COL].astype(str).str[:16] + '
' + unit = 'min' if hm_mode == 1 else 'h' + df['to_temp'] = df[TIME_COL] + pd.to_timedelta(hm_step, unit=unit) + df.loc[df['to_temp'].astype(str).str[11:16] == '00:00', 'to'] = \ + df['to_temp'].astype(str).str[:8] + df[TIME_COL].astype(str).str[8:11] + '24:00' + df.loc[df['to_temp'].astype(str).str[11:16] != '00:00', 'to'] = df['to_temp'].astype(str).str[:16] + df['to'] = 'To : ' + df['to'] + '
' + + return df + + +@log_execution_time() +def build_dic_col_func(dic_proc_cfgs: Dict[int, CfgProcess], graph_param: DicParam): + """ Each column needs an aggregate function """ + dic_col_func = dict() + for proc_id, proc_config in dic_proc_cfgs.items(): + if graph_param.is_end_proc(proc_id): + dic_col_func[proc_id] = dict() + end_col_ids = graph_param.get_sensor_cols(proc_id) + end_cols = proc_config.get_cols(end_col_ids) + for end_col in end_cols: + if DataType[end_col.data_type] is DataType.REAL: + hm_function = HMFunction[graph_param.common.hm_function_real] + else: + hm_function = HMFunction[graph_param.common.hm_function_cate] + dic_col_func[proc_id][end_col.id] = hm_function + return dic_col_func + + +@log_execution_time() +def apply_significant_digit(dic_df_col): + for end_col, df_end in dic_df_col.items(): + df_end[end_col] = df_end[end_col].apply(signify_digit2) + + return dic_df_col + + +def append_result(dic_df_col, df_end_col, end_col_id): + """ Append result of each batch to the whole result """ + if dic_df_col.get(end_col_id) is None: # no need if use default dict + dic_df_col[end_col_id] = df_end_col + else: + dic_df_col[end_col_id] = dic_df_col[end_col_id].append(df_end_col) + + +def get_batch_data(proc_cfg: CfgProcess, graph_param: DicParam, start_tm, end_tm, sql_limit=None) -> pd.DataFrame: + """ Query data for each batch. """ + batches = generate_batches(start_tm, end_tm, batch_size=7) + num_batches = len(batches) + + for idx, (batch_start_tm, batch_end_tm) in enumerate(batches): + df_batch = graph_one_proc(proc_cfg.id, batch_start_tm, batch_end_tm, + graph_param.common.cond_procs, graph_param.array_formval, sql_limit, + same_proc_only=True) + df_batch = validate_data(df_batch) + + # to report progress + progress = idx / num_batches + + yield progress, df_batch + + +def gen_empty_df(end_col_id, var_agg_col, ): + if var_agg_col: + return pd.DataFrame({TIME_COL: [], AGG_COL: [], end_col_id: [], var_agg_col: []}) + else: + return pd.DataFrame({TIME_COL: [], AGG_COL: [], end_col_id: []}) + + +def gen_df_end_col(df_batch, end_col, var_agg_col): + """ Use separate data frame for each column """ + end_col_label = gen_sql_label(end_col.id, end_col.column_name) + if var_agg_col: + df_end_col = df_batch[[TIME_COL, AGG_COL, var_agg_col, end_col_label]] + df_end_col[var_agg_col] = df_end_col[var_agg_col].astype(str) + else: + df_end_col = df_batch[[TIME_COL, AGG_COL, end_col_label]] + + if end_col_label == var_agg_col: + c_id = list(df_end_col.columns).index(end_col_label) + df_end_col.columns.values[c_id] = end_col.id + else: + df_end_col = df_end_col.rename({end_col_label: end_col.id}, axis=1) + return df_end_col + + +def gen_agg_col_names(var_agg_col): + """ If use stratify variable -> aggregate by [stratify variable, time], otherwise, aggregate by time. """ + if var_agg_col: + return [var_agg_col, AGG_COL] + else: + return [AGG_COL] + + +@log_execution_time() +def graph_heatmap_data_one_proc(proc_cfg: CfgProcess, graph_param: DicParam, start_tm, end_tm, offset, + dic_col_func, var_agg_col=None, pct_start=0.0): + """ Build heatmap data for all columns of each process """ + + # start proc + proc_id = proc_cfg.id + + # get end cols + end_col_ids = graph_param.get_sensor_cols(proc_id) + end_cols = proc_cfg.get_cols(end_col_ids) + hm_mode = int(graph_param.common.hm_mode) + hm_step = int(graph_param.common.hm_step) + + num_rows = 0 + dic_df_col = dict() + dic_filter_results = {MATCHED_FILTER_IDS: [], UNMATCHED_FILTER_IDS: [], NOT_EXACT_MATCH_FILTER_IDS: []} + for (progress, df_batch) in get_batch_data(proc_cfg, graph_param, start_tm, end_tm): + percent = pct_start + progress * 10 # to report progress + background_announcer.announce(percent, AnnounceEvent.SHOW_GRAPH.name) + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = \ + main_check_filter_detail_match_graph_data(graph_param, df_batch) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_filter_results[MATCHED_FILTER_IDS] += matched_filter_ids + dic_filter_results[UNMATCHED_FILTER_IDS] += unmatched_filter_ids + dic_filter_results[NOT_EXACT_MATCH_FILTER_IDS] += not_exact_match_filter_ids + + if df_batch is None or df_batch.empty: + for end_col in end_cols: + df_end_col = gen_empty_df(end_col.id, var_agg_col) + append_result(dic_df_col, df_end_col, end_col.id) + else: + num_rows += df_batch.index.size + + # gen aggregate endcol + pd_step = convert_to_pandas_step(hm_step, hm_mode) + df_batch: pd.DataFrame = create_agg_column(df_batch, pd_step, AGG_COL, hm_mode, offset) + + # transform + aggregate + for end_col in end_cols: + agg_cols = gen_agg_col_names(var_agg_col) + df_end_col = gen_df_end_col(df_batch, end_col, var_agg_col) + hm_function = dic_col_func[proc_id][end_col.id] + + df_end_col: pd.DataFrame \ + = gen_heat_map_cell_value(df_end_col, graph_param, agg_cols, end_col.id, hm_function) + + append_result(dic_df_col, df_end_col, end_col.id) + + print('/////// proc_id: {}, num_rows: '.format(proc_id), num_rows) + dic_df_col = apply_significant_digit(dic_df_col) + + return dic_df_col, dic_filter_results, num_rows + + +@log_execution_time() +def trim_data(df: pd.DataFrame, agg_cols: List, end_col): + """ Trim first 5% biggest and 5% smallest. Alternative, rank by pct. """ + + df = df.replace(dict.fromkeys([np.inf, -np.inf, np.nan], np.nan)).dropna() + df['rank'] = df.groupby(agg_cols)[end_col].rank(method='first') + df['group_size'] = df.groupby(agg_cols)['rank'].transform(np.size) + df['p1'] = (df['group_size'] * 0.05).transform(np.floor) + df['p9'] = df['group_size'] - df['p1'] + return df[(df['p1'] <= df['rank']) & (df['rank'] <= df['p9'])].reset_index() + + +def range_func(x): + return np.max(x) - np.min(x) + + +def convert_to_pandas_step(hm_step, hm_mode): + """ Pandas steps are: 4h, 15min, ... """ + if hm_mode == 7: + return '{}h'.format(hm_step) + return '{}min'.format(hm_step) + + +@log_execution_time() +def create_agg_column(df, pd_step='4h', agg_col=AGG_COL, hm_mode=7, offset=timedelta(0)): + """ Create aggregate column data """ + if hm_mode == 7: + length = 13 + else: + length = 16 + temp = pd.to_datetime(df[TIME_COL], format='%Y-%m-%dT%H:%M') + offset + df[agg_col] = temp.dt.floor(pd_step).astype(str).str[:length] + return df + + +@log_execution_time() +def groupby_and_aggregate(df: pd.DataFrame, hm_function: HMFunction, hm_mode, hm_step, agg_cols, end_col): + """ Group by time and calculate aggregates """ + if hm_function is HMFunction.count_per_hour: + agg_params = {end_col: HMFunction.count.name, TIME_COL: HMFunction.first.name} + df = df.groupby(agg_cols).agg(agg_params).reset_index() + if hm_mode == 7: + df[end_col] = df[end_col].div(hm_step) + else: + df[end_col] = df[end_col].div(hm_step / 60) + elif hm_function is HMFunction.count_per_min: + agg_params = {end_col: HMFunction.count.name, TIME_COL: HMFunction.first.name} + df = df.groupby(agg_cols).agg(agg_params).reset_index() + if hm_mode == 7: + df[end_col] = df[end_col].div(hm_step * 60) + else: + df[end_col] = df[end_col].div(hm_step) + elif hm_function is HMFunction.range: + agg_params = {end_col: range_func, TIME_COL: HMFunction.first.name} + df = df.groupby(agg_cols).agg(agg_params).reset_index() + elif hm_function is HMFunction.iqr: + agg_params = {end_col: iqr, TIME_COL: HMFunction.first.name} + df = df.groupby(agg_cols).agg(agg_params).reset_index() + elif hm_function is HMFunction.time_per_count: + agg_params = {end_col: HMFunction.count.name, TIME_COL: HMFunction.first.name} + df = df.groupby(agg_cols).agg(agg_params).reset_index() + step_time = (hm_step * 60) if hm_mode == 1 else (hm_step * 3600) + df[end_col] = step_time / df[end_col] + else: + agg_params = {end_col: hm_function.name, TIME_COL: HMFunction.first.name} + df = df.groupby(agg_cols).agg(agg_params).reset_index() + return df + + +@log_execution_time() +def gen_heat_map_cell_value(df: pd.DataFrame, graph_param: DicParam, agg_cols, end_col, hm_function: HMFunction): + """ Value z for each cell (x,y) """ + hm_mode = int(graph_param.common.hm_mode) + hm_step = int(graph_param.common.hm_step) + hm_trim = int(graph_param.common.hm_trim) + # trim data + if 'count' not in hm_function.name and hm_trim: + df = trim_data(df, agg_cols, end_col) + + # groupby + aggregate + df = groupby_and_aggregate(df, hm_function, hm_mode, hm_step, agg_cols, end_col) + + return df + + +@log_execution_time() +def generate_batches(start_tm, end_tm, batch_size=7): + """ Divide [start_time, end_time] to small batches. Default 7 days for each batch. """ + batch_start_str = reformat_dt_str(start_tm, DATE_FORMAT_QUERY) + batch_start = datetime.strptime(batch_start_str, DATE_FORMAT_QUERY) + + batch_end = batch_start + timedelta(days=batch_size) + batch_end_str = datetime.strftime(batch_end, DATE_FORMAT_QUERY) + batch_end_str = min(batch_end_str, end_tm) + + batches = [(batch_start_str, batch_end_str)] + + # previous_start = batch_start + previous_end = batch_end + while batch_end_str < end_tm: + batch_start = previous_end + batch_start_str = datetime.strftime(batch_start, DATE_FORMAT_QUERY) + + batch_end = batch_start + timedelta(days=batch_size) + batch_end_str = datetime.strftime(batch_end, DATE_FORMAT_QUERY) + batch_end_str = min(batch_end_str, end_tm) + + batches.append((batch_start_str, batch_end_str)) + # previous_start = batch_start + previous_end = batch_end + # break + + return batches diff --git a/histview2/api/multi_scatter_plot/controllers.py b/histview2/api/multi_scatter_plot/controllers.py new file mode 100644 index 0000000..4e800ba --- /dev/null +++ b/histview2/api/multi_scatter_plot/controllers.py @@ -0,0 +1,64 @@ +import timeit + +import simplejson +from flask import Blueprint, request + +from histview2.api.multi_scatter_plot.services import gen_scatter_plot, remove_unused_params, gen_scatter_n_contour_data +from histview2.common.constants import SCATTER_CONTOUR +from histview2.common.pysize import get_size +from histview2.common.services import http_content +from histview2.common.services.form_env import parse_multi_filter_into_one +from histview2.common.services.import_export_config_n_data import get_dic_form_from_debug_info, \ + set_export_dataset_id_to_dic_param +from histview2.common.trace_data_log import is_send_google_analytics, save_input_data_to_file, EventType + +api_multi_scatter_blueprint = Blueprint( + 'api_multi_scatter_module', + __name__, + url_prefix='/histview2/api/msp' +) + + +@api_multi_scatter_blueprint.route('/plot', methods=['POST']) +def trace_data(): + """ + Trace Data API + return dictionary + """ + start = timeit.default_timer() + dic_form = request.form.to_dict(flat=False) + save_input_data_to_file(dic_form, EventType.MSP) + + use_contour = int(request.form.get('use_contour')) if request.form.get('use_contour') else 0 + dic_param = parse_multi_filter_into_one(dic_form) + + # check if we run debug mode (import mode) + dic_param = get_dic_form_from_debug_info(dic_param) + + # if universal call gen_dframe else gen_results + orig_send_ga_flg = is_send_google_analytics + dic_param, dic_data = gen_scatter_plot(dic_param) + + # generate SCATTER_CONTOUR + dic_param[SCATTER_CONTOUR] = gen_scatter_n_contour_data(dic_param, dic_data, use_contour) + + # send Google Analytics changed flag + if orig_send_ga_flg and not is_send_google_analytics: + dic_param.update({'is_send_ga_off': True}) + + # remove unused params + remove_unused_params(dic_param) + + # calculate data size to send gtag + data_size = get_size(dic_param) + dic_param['data_size'] = data_size + + stop = timeit.default_timer() + dic_param['backend_time'] = stop - start + + # export mode ( output for export mode ) + set_export_dataset_id_to_dic_param(dic_param) + + out_dict = simplejson.dumps(dic_param, ensure_ascii=False, default=http_content.json_serial, ignore_nan=True) + + return out_dict, 200 diff --git a/histview2/api/multi_scatter_plot/services.py b/histview2/api/multi_scatter_plot/services.py new file mode 100644 index 0000000..b981818 --- /dev/null +++ b/histview2/api/multi_scatter_plot/services.py @@ -0,0 +1,489 @@ +import numpy as np +import pandas as pd +from loguru import logger +from scipy.linalg import pinv +from scipy.signal import convolve2d +from scipy.stats import gaussian_kde, binned_statistic_2d + +from histview2.api.trace_data.services.time_series_chart import (get_data_from_db, get_chart_infos, + gen_plotdata, make_irregular_data_none, + get_procs_in_dic_param, gen_dic_data_from_df, + main_check_filter_detail_match_graph_data, + calc_setting_scale_y) +from histview2.common.constants import ARRAY_FORMVAL, ARRAY_PLOTDATA, ACTUAL_RECORD_NUMBER, \ + IS_RES_LIMITED, ARRAY_Y, MATCHED_FILTER_IDS, UNMATCHED_FILTER_IDS, NOT_EXACT_MATCH_FILTER_IDS, END_PROC, \ + GET02_VALS_SELECT, END_COL_ID, CORRS, CORR, PCORR, ARRAY_X, SCALE_SETTING, KDE_DATA, NTOTALS, \ + NORMAL_MODE_MAX_RECORD +from histview2.common.memoize import memoize +from histview2.common.services.ana_inf_data import calculate_kde_trace_data +from histview2.common.services.form_env import bind_dic_param_to_class +from histview2.common.services.sse import notify_progress +from histview2.common.sigificant_digit import signify_digit_vector, signify_digit +from histview2.common.trace_data_log import * +from histview2.trace_data.models import Cycle +from sklearn.preprocessing import StandardScaler + + +@log_execution_time('[MULTI SCATTER PLOT]') +@notify_progress(60) +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.MSP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_scatter_plot(dic_param): + """tracing data to show graph + 1 start point x n end point + filter by condition points that between start point and end_point + """ + # bind dic_param + graph_param = bind_dic_param_to_class(dic_param) + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + # add start proc + graph_param.add_start_proc_to_array_formval() + + # add condition procs + graph_param.add_cond_procs_to_array_formval() + + dic_proc_name = {} + # get serials + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + serial_ids = [serial.id for serial in proc_cfg.get_serials(column_name_only=False)] + proc.add_cols(serial_ids) + dic_proc_name[proc.proc_id] = proc_cfg.name + + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = main_check_filter_detail_match_graph_data( + graph_param, df) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] = matched_filter_ids + dic_param[UNMATCHED_FILTER_IDS] = unmatched_filter_ids + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = not_exact_match_filter_ids + dic_param['proc_name'] = dic_proc_name + + # create output data + orig_graph_param = bind_dic_param_to_class(dic_param) + dic_data = gen_dic_data_from_df(df, orig_graph_param) + times = df[Cycle.time.key].tolist() or [] + + # TODO: ask Tinh san how about serial hover + # gen_dic_serial_data_scatter(df, dic_proc_cfgs, dic_param) + + # get chart infos + chart_infos, chart_infos_org = get_chart_infos(orig_graph_param, dic_data, times) + + dic_param[ARRAY_FORMVAL], dic_param[ARRAY_PLOTDATA] = \ + gen_plotdata(orig_graph_param, dic_data, chart_infos, chart_infos_org, reorder=False) + dic_param[ACTUAL_RECORD_NUMBER] = actual_record_number + + # calculate_summaries + # calc_summaries(dic_param) + + # scale settings + # min_max_list, all_graph_min, all_graph_max = calc_raw_common_scale_y(dic_param[ARRAY_PLOTDATA]) + # calc_scale_info(dic_param[ARRAY_PLOTDATA], min_max_list, all_graph_min, all_graph_max) + + # flag to show that trace result was limited + dic_param[IS_RES_LIMITED] = is_res_limited + + # convert irregular data + make_irregular_data_none(dic_param) + + # remove none data points + # remove_none_data(dic_param) + + # generate kde for each trace output array + # dic_param = gen_kde_data_trace_data(dic_param) + for plotdata in dic_param.get(ARRAY_PLOTDATA, []): + series_y = pd.Series(plotdata.get(ARRAY_Y, [])) + plotdata[SCALE_SETTING] = calc_setting_scale_y(plotdata, series_y) + plotdata[SCALE_SETTING][KDE_DATA], *_ = calculate_kde_trace_data(plotdata) + + # partial correlation + calc_partial_corr(dic_param) + + return dic_param, dic_data + + +@log_execution_time() +def remove_none_data(dic_param): + num_sensors = len(dic_param.get(ARRAY_PLOTDATA) or []) + if not num_sensors or not dic_param[ARRAY_PLOTDATA][0] or not len(dic_param[ARRAY_PLOTDATA][0].get(ARRAY_Y) or []): + return + + # find none vals + array_ys = zip(*[dic_param[ARRAY_PLOTDATA][ss].get(ARRAY_Y) or [] for ss in range(num_sensors)]) + list_nones = [idx for idx, vals in enumerate(array_ys) if any([v is None or np.isnan(v) for v in vals])] + + # remove none vals + num_points = len(dic_param[ARRAY_PLOTDATA][0].get(ARRAY_Y) or []) + for ss in range(num_sensors): + array_y = dic_param[ARRAY_PLOTDATA][ss][ARRAY_Y] + dic_param[ARRAY_PLOTDATA][ss][ARRAY_Y] = [array_y[i] for i in range(num_points) if i not in list_nones] + + +@log_execution_time() +def gen_scatter_n_contour_data_pair(array_y1, array_y2, use_contour): + # parameters + num_bins = 50 # 50 x 50 cells + outlier_ratio = 0.05 # ratio for number of points to plot + max_num_points = 1000 # maximum number of points for scatter plot + max_num_points_kde = 10000 # maximum number of points for kde + + df = pd.DataFrame({'array_y1': array_y1, 'array_y2': array_y2}) + df = df.replace(dict.fromkeys([np.inf, -np.inf, np.nan], np.nan)).dropna() + array_y1 = df['array_y1'].to_numpy() + array_y2 = df['array_y2'].to_numpy() + + contour_data = { + 'x': [], + 'y': [], + 'z': [], + 'contours_coloring': 'heatmap', + 'line_width': 0 + } + scatter_data = { + 'x': [], + 'y': [], + 'mode': 'markers' + } + + if len(array_y1) < 2 or len(array_y2) < 2: + return [contour_data, scatter_data] + + try: + # fit kde and generate/evaluate gridpoints + kernel = fit_2d_kde(array_y1, array_y2, max_num_points_kde) + hist = calc_2d_hist(array_y1, array_y2, num_bins) + kde_gridpoints, x_grid, y_grid = calc_kde_gridpoints(kernel, hist.x_edge, hist.y_edge) + + # for contour: store x-y value and density of each gridpoints + # dic_contour = {'x': x_grid, 'y': y_grid, 'z': kde_gridpoints.ravel()} # TODO comment out for now + + if len(array_y1) < NORMAL_MODE_MAX_RECORD and not use_contour: + # return full point + scatter_data = { + 'x': signify_digit_vector(array_y1), + 'y': signify_digit_vector(array_y2), + 'mode': 'markers' + } + + if len(array_y1) >= NORMAL_MODE_MAX_RECORD or use_contour: + # return contour; + num_outliers = np.min([int(len(array_y1) * outlier_ratio), max_num_points]) + flg_outlier = get_outlier_flg(hist, kde_gridpoints, num_outliers, num_bins) + # normalize and change to log scale (fit to Logarithm) + z_value = np.log(kde_gridpoints.ravel() / np.max(kde_gridpoints.ravel()) * 1000 + 1) + contour_data = { + 'x': signify_digit_vector(x_grid), + 'y': signify_digit_vector(y_grid), + 'z': signify_digit_vector(z_value), + 'contours_coloring': 'heatmap', + 'line_width': 1 + } + + scatter_data = { + 'x': signify_digit_vector(array_y1[flg_outlier]), + 'y': signify_digit_vector(array_y2[flg_outlier]), + 'mode': 'markers' + } + + except Exception as ex: + logger.exception(ex) + + return [contour_data, scatter_data] + + +@log_execution_time() +def fit_2d_kde(x, y, max_num_points_kde=10000): + """ + Fit density estimator + scipy.stats.gaussian_kde is used. + + Parameters + ---------- + x, y: array + Raw data + max_num_points_kde: int + x and y is under-sampled to this length + + Returns + ------- + kernel: + Fitted gaussian_kde + """ + + idx_sample = np.arange(len(x)) + if len(x) > max_num_points_kde: + idx_sample = np.random.choice(np.arange(len(x)), size=max_num_points_kde) + x = x[idx_sample].copy() + y = y[idx_sample].copy() + + # add jitter when x and y have almost complete linear correlation, or constant value + x, y = add_jitter_for_kde(x, y) + + kernel = gaussian_kde(np.vstack((x, y))) + # kde with Silverman bandwidth estimation method + # kernel = gen_gaussian_kde_1d_same_as_r(np.vstack((x, y))) + return kernel + + +@log_execution_time() +def add_jitter_for_kde(x, y): + """ + Add jitter (random noise) to x, y + If x, y have almost complete linear correlation, or constant value. + This is due to usage of inverse covariance matrix in gaussian_kde() + + Parameters + ---------- + x, y: array + Raw data + + Returns + ------- + x_, y_: array + Added jitter if necessary + """ + + xrng = (np.nanmax(x) - np.nanmin(x)) + yrng = (np.nanmax(y) - np.nanmin(y)) + + add_jitter = False + if (xrng == 0.0) or (yrng == 0.0): + add_jitter = True + elif np.abs(np.corrcoef(x, y)[0, 1]) > 0.999: + # corrcoef returns nan if y or x is constant + add_jitter = True + + if add_jitter: + x_offset = xrng / 50 + y_offset = yrng / 50 + x_offset = x_offset if xrng > 0.0 else 0.01 + y_offset = y_offset if yrng > 0.0 else 0.01 + x_ = x + np.random.uniform(-x_offset, x_offset, len(x)) + y_ = y + np.random.uniform(-y_offset, y_offset, len(y)) + return x_, y_ + + return x, y + + +@log_execution_time() +def calc_2d_hist(x, y, num_bins): + """ + Calculate 2D histogram + scipy.stats.binned_statistic_2d is used. + + Parameters + ---------- + x, y: array + Raw data + num_bins: int + Total number of cells = num_bins^2 + + Returns + ------- + hist: + Fitted 2D histogram. + Counts for each cell, and x-y value for binning is contained. + """ + + # range of x and y + xmin = np.nanmin(x) + xmax = np.nanmax(x) + ymin = np.nanmin(y) + ymax = np.nanmax(y) + + # 2D histogram. TypeError when values=None + # https://stackoverflow.com/questions/60623899/why-is-binned-statistic-2d-now-throwing-typeerror + hist = binned_statistic_2d(x=x, y=y, values=x, statistic='count', + bins=num_bins, range=[[xmin, xmax], [ymin, ymax]]) + return hist + + +@log_execution_time() +def calc_kde_gridpoints(kernel, x_edge, y_edge): + """ + Calculate density values on each gridpoint + + Parameters + ---------- + kernel: + Fitted density estimator + x_edge, y_edge: array + Vales fo binning + + Returns + ------- + kde_gridpoints: array + Density values on each gridpoint + shape = len(x_edge), len(y_edge) + x_grid, y_grid: array + x-y values of each gridpoint + len = len(x_edge) * len(y_edge) + """ + + # calculate density of each gridpoints + x_grid, y_grid = np.meshgrid(x_edge, y_edge) + x_grid = x_grid.ravel() + y_grid = y_grid.ravel() + kde_gridpoints = kernel(np.vstack((x_grid, y_grid))).reshape((len(x_edge), len(y_edge))) + return kde_gridpoints, x_grid, y_grid + + +@log_execution_time() +def get_outlier_flg(hist, kde_gridpoints, num_outliers, num_bins): + """ + Get outlier flag of each data point + Data with low density is estimated as outliers. + + Parameters + ---------- + hist: + Fitted 2D histogram + kde_gridpoints: array + Density values on each gridpoint + num_outliers: int + Number of outliers to show in scatter plot + + Returns + ------- + flg_outlier: array + True/False values + """ + + num_cells = hist.statistic.shape[0] + + # we have to be careful that + # `hist.binnumber` returns bin index of (num_bins+2, num_bins+2) array, + # where +2 is for boundaries of each dimension + # https://stackoverflow.com/questions/31708773/translate-scipy-stats-binned-statistic-2ds-binnumber-to-a-x-y-bin + idx_cells_scipy = (np.arange(0, (num_cells + 2) ** 2)).reshape(num_cells + 2, num_cells + 2) + idx_cells_scipy = idx_cells_scipy[1:(num_cells + 1), 1:(num_cells + 1)].ravel() + + # density of each cells (average of surrounding girdpoints) + ave_filter = np.ones((2, 2)) / 4.0 + kde_cells = convolve2d(ave_filter, kde_gridpoints, mode='valid') + + cnts = hist.statistic.ravel() + idx_cells = np.argsort(kde_cells.T.ravel()) + + csum = 0 + for k, cell in enumerate(idx_cells): + csum += cnts[cell] + if csum > num_outliers: + break + + idx_outlier_cells = idx_cells_scipy[idx_cells[:(k + 1)]] + flg_outlier = np.isin(hist.binnumber, idx_outlier_cells) + return flg_outlier + + +@log_execution_time() +def gen_scatter_n_contour_data(dic_param: dict, dic_data, use_contour): + scatter_contours = {} + array_formval = dic_param[ARRAY_FORMVAL] + num_sensor = len(array_formval) + for i in range(num_sensor - 1): + c_idx = i + 1 + array_formval_i = array_formval[i] + proc_id_i = array_formval_i.get(END_PROC) + col_id_i = array_formval_i.get(GET02_VALS_SELECT) + array_y_i = dic_data[proc_id_i][col_id_i] + + for k in range(i + 1, num_sensor): + r_idx = k + 1 + array_formval_k = array_formval[k] + proc_id_k = array_formval_k.get(END_PROC) + col_id_k = array_formval_k.get(GET02_VALS_SELECT) + array_y_k = dic_data[proc_id_k][col_id_k] + + contour_data, scatter_data = gen_scatter_n_contour_data_pair(array_y_i, array_y_k, use_contour) + scatter_contours['{}-{}'.format(r_idx, c_idx)] = { + 'contour_data': contour_data, + 'scatter_data': scatter_data, + 'proc_id_x': proc_id_i, + 'col_id_x': col_id_i, + 'proc_id_y': proc_id_k, + 'col_id_y': col_id_k, + } + + return scatter_contours + + +@log_execution_time() +def partial_corr(data): + # transpose dataframe before compute correlation + # correlation_mat = np.corrcoef(data.T) + correlation_mat = np.cov(data.T, ddof=0) + # It is safer to calculate inverse by (Moore-Penrose) pseudo inverse, in case of singular matrix + precision_mat = pinv(correlation_mat) + + parcor_mat = np.zeros_like(correlation_mat) + np.fill_diagonal(parcor_mat, np.diag(correlation_mat)) + + rowidx, colidx = np.triu_indices(parcor_mat.shape[0]) + for i, j in zip(rowidx, colidx): + if i == j: + continue + parcor = - precision_mat[i, j] / np.sqrt(precision_mat[i, i] * precision_mat[j, j]) + parcor_mat[i, j] = parcor + parcor_mat[j, i] = parcor + + return parcor_mat, correlation_mat + + +@log_execution_time() +def calc_partial_corr(dic_param): + plot_list = {} + for plotdata in dic_param[ARRAY_PLOTDATA]: + plot_list[plotdata[END_COL_ID]] = plotdata[ARRAY_Y] + + df = pd.DataFrame(plot_list) + df.dropna(inplace=True) + columns = df.columns.to_list() + + corrs = { + CORR: {}, # correlation coefficient + PCORR: {}, # partial correlation + NTOTALS: {} + } + + if df.shape[0]: + scaler = StandardScaler() + data = scaler.fit_transform(df) + p_corr_mat, corr_mat = partial_corr(data) + + # df: + # 0 col1 col2 col3 col4 + # col1 1 x x x + # col2 x 1 x x + # col3 x x 1 x + # col4 x x x 1 + + # push item into dict_param + for k, col in enumerate(columns): + corrs[CORR][col] = {} + corrs[PCORR][col] = {} + corrs[NTOTALS][col] = df.shape[0] + for i, row in enumerate(columns): + corrs[CORR][col][row] = signify_digit(corr_mat[k][i]) + corrs[PCORR][col][row] = signify_digit(p_corr_mat[k][i]) + + dic_param[CORRS] = corrs + return dic_param + + +def remove_unused_params(dic_param): + for plot_data in dic_param[ARRAY_PLOTDATA]: + del plot_data[ARRAY_X] + del plot_data[ARRAY_Y] + + # del dic_param[SERIAL_DATA] + # del dic_param[TIMES] + + return dic_param diff --git a/histview2/api/parallel_plot/controllers.py b/histview2/api/parallel_plot/controllers.py new file mode 100644 index 0000000..1d63db1 --- /dev/null +++ b/histview2/api/parallel_plot/controllers.py @@ -0,0 +1,59 @@ +import timeit + +import simplejson +from flask import Blueprint, request + +from histview2 import dic_yaml_config_file +from histview2.api.parallel_plot.services import gen_graph_paracords +from histview2.common.constants import * +from histview2.common.services import http_content +from histview2.common.services.form_env import parse_multi_filter_into_one +from histview2.common.services.import_export_config_n_data import get_dic_form_from_debug_info, \ + set_export_dataset_id_to_dic_param +from histview2.common.trace_data_log import save_input_data_to_file, EventType + +api_paracords_blueprint = Blueprint( + 'api_paracords', + __name__, + url_prefix='/histview2/api/pcp' +) + +# ローカルパラメータの設定 +local_params = { + "config_yaml_fname_proc": dic_yaml_config_file[YAML_PROC], + "config_yaml_fname_histview2": dic_yaml_config_file[YAML_CONFIG_HISTVIEW2], + "config_yaml_fname_db": dic_yaml_config_file[YAML_CONFIG_DB], +} + + +@api_paracords_blueprint.route('/index', methods=['POST']) +def trace_data(): + """ + Trace Data API + return dictionary + """ + start = timeit.default_timer() + dic_form = request.form.to_dict(flat=False) + save_input_data_to_file(dic_form, EventType.PCP) + dic_param = parse_multi_filter_into_one(dic_form) + + # check if we run debug mode (import mode) + dic_param = get_dic_form_from_debug_info(dic_param) + + dic_param = gen_graph_paracords(dic_param) + + stop = timeit.default_timer() + dic_param['backend_time'] = stop - start + + # export mode ( output for export mode ) + set_export_dataset_id_to_dic_param(dic_param) + + # trace_data.htmlをもとにHTML生成 + out_dict = simplejson.dumps(dic_param, ensure_ascii=False, default=http_content.json_serial, ignore_nan=True) + return out_dict, 200 + + +@api_paracords_blueprint.route('/testme', methods=['GET']) +def testme(): + # TODO: remove API test function + return 'OK', 200 diff --git a/histview2/api/parallel_plot/services.py b/histview2/api/parallel_plot/services.py new file mode 100644 index 0000000..6a3e6c0 --- /dev/null +++ b/histview2/api/parallel_plot/services.py @@ -0,0 +1,1142 @@ +import json +import traceback +from collections import defaultdict +from typing import Dict, List + +import numpy as np +import pandas as pd +from loguru import logger +from numpy import quantile +from pandas import DataFrame +from sqlalchemy import and_, or_ + +from histview2 import db +from histview2.api.analyze.services.pca import remove_outlier +from histview2.api.trace_data.services.regex_infinity import validate_numeric_minus, validate_numeric_plus, \ + validate_string +from histview2.api.trace_data.services.time_series_chart import main_check_filter_detail_match_graph_data, \ + get_data_from_db +from histview2.common.common_utils import convert_time, add_days, gen_sql_label, \ + gen_sql_like_value, chunks +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.memoize import memoize +from histview2.common.scheduler import dic_running_job, JobType +from histview2.common.services.form_env import bind_dic_param_to_class +from histview2.common.services.sse import notify_progress +from histview2.common.trace_data_log import trace_log, TraceErrKey, EventAction, Target, EventType +from histview2.setting_module.models import CfgConstant, CfgProcess, CfgProcessColumn, CfgFilterDetail +from histview2.trace_data.models import find_cycle_class, GlobalRelation, Sensor, Cycle, find_sensor_class +from histview2.trace_data.schemas import DicParam, EndProc, ConditionProc, CategoryProc + + +@log_execution_time('[TRACE DATA]') +@notify_progress(60) +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.PCP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_graph_paracords(dic_param): + """tracing data to show graph + 1 start point x n end point + filter by condition point + https://files.slack.com/files-pri/TJHPR9BN3-F01GG67J84C/image.pngnts that between start point and end_point + """ + # bind dic_param + graph_param = bind_dic_param_to_class(dic_param) + + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + # add start proc + graph_param.add_start_proc_to_array_formval() + + # add condition procs + graph_param.add_cond_procs_to_array_formval() + + # add category + graph_param.add_cate_procs_to_array_formval() + + # get serials + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + serial_ids = [serial.id for serial in proc_cfg.get_serials(column_name_only=False)] + proc.add_cols(serial_ids) + + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = main_check_filter_detail_match_graph_data( + graph_param, df) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] = matched_filter_ids + dic_param[UNMATCHED_FILTER_IDS] = unmatched_filter_ids + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = not_exact_match_filter_ids + + # order data by serials + # df = check_and_order_data(df, graph_param, dic_proc_cfgs) + if int(dic_param[COMMON][IS_REMOVE_OUTLIER]) == 1: + numeric_cols = [] + objective_var = int(dic_param[COMMON]['objectiveVar'][0]) if dic_param[COMMON]['objectiveVar'] else None + for proc in graph_param.array_formval: + end_cols = [] + proc_cfg = dic_proc_cfgs[proc.proc_id] + end_col_ids = graph_param.get_sensor_cols(proc.proc_id) + if objective_var and objective_var in end_col_ids: + end_cols = proc_cfg.get_cols([objective_var]) + + if len(end_cols): + numeric_cols = [gen_sql_label(col.id, col.column_name) for col in end_cols + if DataType[col.data_type] in [DataType.REAL, DataType.INTEGER]] + + df_numeric: DataFrame = df[numeric_cols] + df_numeric = remove_outlier(df_numeric) + df[numeric_cols] = df_numeric[numeric_cols].to_numpy() + df = df.dropna(subset=numeric_cols) + + # flag to show that trace result was limited + dic_param[DATA_SIZE] = df.memory_usage(deep=True).sum() + dic_param[IS_RES_LIMITED] = is_res_limited + + # create output data + orig_graph_param = bind_dic_param_to_class(dic_param) + orig_graph_param.add_cate_procs_to_array_formval() + dic_data = gen_dic_data_from_df(df, orig_graph_param) + gen_dic_serial_data_from_df(df, dic_proc_cfgs, dic_param) + + dic_param[ARRAY_FORMVAL], dic_param[ARRAY_PLOTDATA] \ + = gen_plotdata(orig_graph_param, dic_data, dic_proc_cfgs) + dic_param[ACTUAL_RECORD_NUMBER] = actual_record_number + + return dic_param + + +def gen_blank_df_end_col(proc: EndProc): + params = {gen_sql_label(col_id, proc.col_names[idx]): [] for idx, col_id in enumerate(proc.col_ids)} + params.update({Cycle.id.key: [], Cycle.global_id.key: [], Cycle.time.key: []}) + return pd.DataFrame(params) + + +def gen_df_end(proc: EndProc, proc_cfg: CfgProcess, start_relate_ids=None, start_tm=None, end_tm=None): + proc_id = proc.proc_id + + # get serials + serials = proc_cfg.get_serials(column_name_only=False) + serials = [gen_sql_label(serial.id, serial.column_name) for serial in serials] + + # get sensor values + df_end = get_sensor_values(proc, start_relate_ids=start_relate_ids, start_tm=start_tm, end_tm=end_tm) + if df_end.empty: + df_end = gen_blank_df_end_col(proc) + + # filter duplicate + if df_end.columns.size: + df_end = df_end[df_end.eval('global_id.notnull()')] + + # drop duplicate + if df_end.columns.size: + df_end = df_end.drop_duplicates(subset=serials, keep='last') + + # set index + if df_end.columns.size: + df_end.set_index(Cycle.global_id.key, inplace=True) + + return df_end + + +def gen_df_end_same_with_start(proc: EndProc, proc_cfg: CfgProcess, start_tm, end_tm, drop_duplicate=True): + # proc_id = proc.proc_id + + # get serials + serials = proc_cfg.get_serials(column_name_only=False) + serials = [gen_sql_label(serial.id, serial.column_name) for serial in serials] + + # get sensor values + df_end = get_sensor_values(proc, start_tm=start_tm, end_tm=end_tm, use_global_id=False) + if df_end.empty: + return pd.DataFrame() + + df_end.set_index(Cycle.id.key, inplace=True) + + # if only 1 proc, show all data without filter duplicate + if drop_duplicate: # TODO ask PO + df_end.drop_duplicates(subset=serials, keep='last', inplace=True) + + return df_end + + +def filter_proc_same_with_start(proc: ConditionProc, start_tm, end_tm): + if not proc.dic_col_id_filters: + return None + + cond_records = get_cond_data(proc, start_tm=start_tm, end_tm=end_tm, use_global_id=False) + # important : None is no filter, [] is no data + if cond_records is None: + return None + + return [cycle.id for cycle in cond_records] + + +def filter_proc(proc: ConditionProc, start_relate_ids=None, start_tm=None, end_tm=None): + if not proc.dic_col_id_filters: + return None + + cond_records = get_cond_data(proc, start_relate_ids=start_relate_ids, start_tm=start_tm, end_tm=end_tm) + # important : None is no filter, [] is no data + if cond_records is None: + return None + + return [cycle.global_id for cycle in cond_records] + + +def create_rsuffix(proc_id): + return '_{}'.format(proc_id) + + +def graph_one_proc(proc_cfg: CfgProcess, graph_param: DicParam, start_tm, end_tm, sql_limit): + """ get data from database + + Arguments: + trace {[type]} -- [description] + dic_param {[type]} -- [description] + + Returns: + [type] -- [description] + """ + + # start proc + # proc_id = graph_param.common.start_proc + proc_id = proc_cfg.id # or graph_param.common.start_proc + data = get_start_proc_data(proc_id, start_tm, end_tm, with_limit=sql_limit, with_time_order=True) + # no data + if not data: + return gen_blank_df(graph_param) + + df_start = pd.DataFrame(data) + df_start.set_index(Cycle.id.key, inplace=True) + + # condition + for proc in graph_param.common.cond_procs: + ids = filter_proc_same_with_start(proc, start_tm, end_tm) + if ids is None: + continue + + df_start = df_start[df_start.index.isin(ids)] + + # end proc + for proc in graph_param.array_formval: + df_end = gen_df_end_same_with_start(proc, proc_cfg, start_tm, end_tm, drop_duplicate=False) + df_start = df_start.join(df_end, rsuffix=create_rsuffix(proc.proc_id)).reset_index() + + return df_start + + +def graph_many_proc(dic_proc_cfgs: Dict[int, CfgProcess], graph_param: DicParam, start_tm, end_tm, sql_limit): + """ get data from database + + Arguments: + trace {[type]} -- [description] + dic_param {[type]} -- [description] + + Returns: + [type] -- [description] + """ + # start proc + start_proc_id = graph_param.common.start_proc + + # without relate + data = get_start_proc_data(start_proc_id, start_tm, end_tm, with_limit=sql_limit) + # no data + if not data: + return gen_blank_df(graph_param), False + + df_start = pd.DataFrame(data) + + # with relate + data_with_relate_id = get_start_proc_data_with_relate_id(start_proc_id, start_tm, end_tm, with_limit=sql_limit) + if data_with_relate_id: + df_start_with_relate_id = pd.DataFrame(data_with_relate_id) + df_start = df_start.append(df_start_with_relate_id, ignore_index=True) + + # downcast data type + # data_types = {Cycle.global_id.key: np.int64, Cycle.is_outlier.key: 'category'} + # for col in data_types: + # df_start[col].replace({np.nan: None}, inplace=True) + # df_start = df_start.astype(data_types) + + start_relate_ids = list(df_start[df_start.eval('global_id.notnull()')][Cycle.global_id.key]) + + is_res_limited = True + if len(start_relate_ids) < 5000: + start_relate_ids = [start_relate_ids[x:x + 900] for x in range(0, len(start_relate_ids), 900)] + is_res_limited = False + else: + start_relate_ids = None + + # set index + df_start.set_index(Cycle.id.key, drop=False, inplace=True) + + # condition that same with start + cycle_ids = None + is_filter = False + for proc in graph_param.common.cond_procs: + if not proc.proc_id == start_proc_id: + continue + + ids = filter_proc_same_with_start(proc, start_tm, end_tm) + if ids is None: + continue + + if cycle_ids is None: + cycle_ids = set(ids) + else: + cycle_ids.intersection_update(ids) + + is_filter = True + + if is_filter: + df_start = df_start[df_start.index.isin(cycle_ids)] + if not df_start.columns.size: + return gen_blank_df(graph_param), False + + # end proc that same with start + for proc in graph_param.array_formval: + if not proc.proc_id == start_proc_id: + continue + + # get sensor value data + df_end = gen_df_end_same_with_start(proc, dic_proc_cfgs[proc.proc_id], start_tm, end_tm) + df_start = df_start.join(df_end, how='inner', rsuffix=create_rsuffix(proc.proc_id)) + + if not df_start.columns.size: + return gen_blank_df(graph_param), False + + # get min max time {proc_id:[min,max]} + e_start_tm = convert_time(start_tm, return_string=False) + e_start_tm = add_days(e_start_tm, -14) + e_start_tm = convert_time(e_start_tm) + e_end_tm = convert_time(end_tm, return_string=False) + e_end_tm = add_days(e_end_tm, 14) + e_end_tm = convert_time(e_end_tm) + + global_ids = None + is_filter = False + for proc in graph_param.common.cond_procs: + if proc.proc_id == start_proc_id: + continue + + ids = filter_proc(proc, start_relate_ids, e_start_tm, e_end_tm) + if ids is None: + continue + + if global_ids is None: + global_ids = set(ids) + else: + global_ids.intersection_update(ids) + + is_filter = True + + if is_filter: + if data_with_relate_id: + idxs = df_start[df_start[Cycle.global_id.key].isin(global_ids)].index + idxs = set(idxs) + df_start = df_start.loc[idxs] + # df_start_grp = df_start.groupby(df_start.index) + # df_start = df_start[ + # df_start_grp[Cycle.global_id.key].transform(lambda sub_df: sub_df.isin(global_ids).any())] + else: + df_start = df_start[df_start[Cycle.global_id.key].isin(global_ids)] + + # set new Index + df_start.set_index(Cycle.global_id.key, inplace=True) + + # end proc + for proc in graph_param.array_formval: + if proc.proc_id == start_proc_id: + continue + + df_end = gen_df_end(proc, dic_proc_cfgs[proc.proc_id], start_relate_ids, e_start_tm, e_end_tm) + df_start = df_start.join(df_end, rsuffix=create_rsuffix(proc.proc_id)) + + # group by cycle id to drop duplicate ( 1:n with global relation) + df_start.set_index(Cycle.id.key, inplace=True) + if data_with_relate_id: + df_start = df_start.groupby(df_start.index).first().reset_index() + # df_start = df_start.groupby(df_start.index).agg(lambda vals: vals.loc[~vals.isnull()].iloc[0]) + # df_start = df_start.groupby(df_start.index).agg( + # lambda vals: next((val for val in vals if val is not None), None)) + + # sort by time + df_start.sort_values(Cycle.time.key, inplace=True) + + return df_start, is_res_limited + + +@log_execution_time() +def validate_data(df): + # regex filter exclude columns + exclude_cols = [Cycle.id.key, Cycle.global_id.key, Cycle.time.key, Cycle.is_outlier.key] + + # convert data types + df = df.convert_dtypes() + + # integer cols + int_cols = df.select_dtypes(include='integer').columns.tolist() + return_vals = [pd.NA, pd.NA] + for col in int_cols: + if col in exclude_cols: + continue + + df = validate_numeric_minus(df, col, return_vals) + df = validate_numeric_plus(df, col, return_vals + [pd.NA]) + + # float + float_cols = df.select_dtypes(include='float').columns.tolist() + return_neg_vals = [float('-inf'), float('-inf')] + return_pos_vals = [float('inf'), float('inf'), np.NAN] + for col in float_cols: + if col in exclude_cols: + continue + + df = validate_numeric_minus(df, col, return_neg_vals) + df = validate_numeric_plus(df, col, return_pos_vals) + + # non-numeric cols + for col in df.columns: + if col in exclude_cols: + continue + + if col in int_cols or col in float_cols: + continue + df = validate_string(df, col) + + return df + + +@log_execution_time() +def gen_dic_data_from_df(df: DataFrame, graph_param: DicParam): + dic_data = defaultdict(dict) + for proc in graph_param.array_formval: + for col_id, col_name in zip(proc.col_ids, proc.col_names): + sql_label = gen_sql_label(col_id, col_name) + if sql_label in df.columns: + dic_data[proc.proc_id][col_id] = df[sql_label].replace({np.nan: None}).tolist() + else: + dic_data[proc.proc_id][col_id] = [None] * df.index.size + + dic_data[proc.proc_id][Cycle.time.key] = [] + time_col_alias = '{}_{}'.format(Cycle.time.key, proc.proc_id) + if time_col_alias in df: + dic_data[proc.proc_id][Cycle.time.key] = df[time_col_alias].replace({np.nan: None}).tolist() + + return dic_data + + +@log_execution_time() +def gen_dic_serial_data_from_df(df: DataFrame, dic_proc_cfgs, dic_param): + dic_param[SERIAL_DATA] = dict() + for proc_id, proc_cfg in dic_proc_cfgs.items(): + serial_cols = proc_cfg.get_serials(column_name_only=False) + sql_labels = [gen_sql_label(serial_col.id, serial_col.column_name) for serial_col in serial_cols] + if sql_labels and all(item in df.columns for item in sql_labels): + dic_param[SERIAL_DATA][proc_id] = df[sql_labels] \ + .replace({np.nan: ''}) \ + .to_records(index=False) \ + .tolist() + else: + dic_param[SERIAL_DATA][proc_id] = [] + + +@log_execution_time() +def group_by_start_cycle(data): + dic_cycle_idx = {} + relate_ids = [] + idxs = [] + cycle_ids = [] + + cnt = 0 + for idx, row in enumerate(data): + current_idx = dic_cycle_idx.get(row.id) + if current_idx is None: + dic_cycle_idx[row.id] = cnt + + # relate + relate_ids.append(gen_relate_ids(row)) + + cycle_ids.append([row.id]) + idxs.append(idx) + cnt += 1 + else: + if row.relate_id: + relate_ids[current_idx].append(row.relate_id) + + return cycle_ids, relate_ids, idxs + + +@log_execution_time() +def filter_data(start_proc_name, cycle_ids, relate_ids, cycle_conds): + if not cycle_conds: + return [i for i in range(len(relate_ids))] + + filter_idxs = [] + for idx, [cycle_id, relate_id] in enumerate(zip(cycle_ids, relate_ids)): + checks = [None] * len(cycle_conds) + for chk_idx, [proc_name, chk_keys] in enumerate(cycle_conds): + if proc_name == start_proc_name: + ids = cycle_id + else: + ids = relate_id + + for id in ids: + if checks[chk_idx] is None and id in chk_keys: + checks[chk_idx] = True + + if all(checks): + filter_idxs.append(idx) + + return filter_idxs + + +@log_execution_time() +def gen_filtered_data(relate_ids, filtered_relate_idxs, data, data_idxs): + times = [] + cycles = [] + outliers = [] + new_relate_ids = [] + + for idx in filtered_relate_idxs: + data_idx = data_idxs[idx] + row = data[data_idx] + new_relate_ids.append(relate_ids[idx]) + times.append(row.time) + cycles.append([row.id]) + outliers.append(row.is_outlier) + + return new_relate_ids, times, cycles, outliers + + +@log_execution_time() +def gen_null_array_for_sensor(graph_param: DicParam, arr_len, show_none=True): + ele = None if show_none else '' + dic_proc_sensors = defaultdict(dict) + + for proc in graph_param.array_formval: + for sensor in proc.col_ids: + dic_proc_sensors[proc.proc_id][sensor] = [ele] * arr_len + + return dic_proc_sensors + + +@log_execution_time() +def gen_final_data(graphic_param: DicParam, cycle_ids, relate_ids, dic_sensor_vals, show_none=True): + # create null arrays + dic_data = gen_null_array_for_sensor(graphic_param, len(relate_ids), show_none=show_none) + + for idx, [cycle_id, relate_id] in enumerate(zip(cycle_ids, relate_ids)): + for proc_id, dic_col_data in dic_data.items(): + if proc_id == graphic_param.common.start_proc: + ids = cycle_id + else: + ids = relate_id + + for id in ids: + if id is None: + continue + + row = dic_sensor_vals[proc_id].get(id) + if not row: + continue + + for col_id in dic_col_data: + # TODO : Duy name is id or column name + # val = getattr(row, SQL_COL_PREFIX + sensor_name, None) + val = getattr(row, str(col_id), None) + if val is None: + continue + + dic_data[proc_id][col_id][idx] = val + + return dic_data + + +@log_execution_time() +def get_proc_ids(procs): + pass + + +@log_execution_time() +def get_start_proc_data_with_relate_id(proc_id, start_tm, end_tm, with_limit=None): + """ + inner join with relate table + :param proc_id: + :param start_tm: + :param end_tm: + :param with_limit: + :return: + """ + # start proc subquery + cycle_cls = find_cycle_class(proc_id) + data = db.session.query(cycle_cls.id, GlobalRelation.relate_id.label(Cycle.global_id.key), cycle_cls.time, + cycle_cls.is_outlier) + data = data.filter(cycle_cls.process_id == proc_id) + data = data.filter(cycle_cls.time >= start_tm) + data = data.filter(cycle_cls.time < end_tm) + + # join global relation + data = data.join(GlobalRelation, GlobalRelation.global_id == cycle_cls.global_id) + + if with_limit: + data = data.limit(with_limit) + + data = data.all() + + return data + + +@log_execution_time() +def get_start_proc_data(proc_id, start_tm, end_tm, with_limit=None, with_time_order=None): + """ + get start proc only (with out relation) + :param proc_id: + :param start_tm: + :param end_tm: + :param with_limit: + :param with_time_order: + :return: + """ + cycle_cls = find_cycle_class(proc_id) + cycle = db.session.query(cycle_cls.id, cycle_cls.global_id, cycle_cls.time, cycle_cls.is_outlier) + cycle = cycle.filter(cycle_cls.process_id == proc_id) + cycle = cycle.filter(cycle_cls.time >= start_tm) + cycle = cycle.filter(cycle_cls.time < end_tm) + + if with_time_order: + cycle = cycle.order_by(cycle_cls.time) + + if with_limit: + cycle = cycle.limit(with_limit) + + cycle = cycle.all() + + return cycle + + +def get_sensor_values_chunk(data_query, chunk_sensor, dic_sensors, cycle_cls, start_relate_ids, start_tm, end_tm): + for col_id, col_name in chunk_sensor: + sensor = dic_sensors[col_name] + sensor_val_cls = find_sensor_class(sensor.id, DataType(sensor.type), auto_alias=True) + sensor_val = sensor_val_cls.coef(col_id) + + data_query = data_query.outerjoin( + sensor_val_cls, + and_(sensor_val_cls.cycle_id == cycle_cls.id, sensor_val_cls.sensor_id == sensor.id) + ) + + data_query = data_query.add_columns(sensor_val) + + # chunk + if start_relate_ids: + records = [] + for ids in start_relate_ids: + temp = data_query.filter(cycle_cls.global_id.in_(ids)) + records += temp.all() + id_key = Cycle.global_id.key + else: + data_query = data_query.filter(cycle_cls.time >= start_tm) + data_query = data_query.filter(cycle_cls.time < end_tm) + records = data_query.all() + id_key = Cycle.id.key + + if records: + return pd.DataFrame(records) + else: + params = {gen_sql_label(col_id, col_name) for col_id, col_name in chunk_sensor} + params.update({ + id_key: [], + Cycle.time.key: [], + }) + df_chunk = pd.DataFrame({gen_sql_label(col_id, col_name): [] for col_id, col_name in chunk_sensor}) + return df_chunk + + +@log_execution_time() +def get_sensor_values(proc: EndProc, start_relate_ids=None, start_tm=None, end_tm=None, use_global_id=True): + """gen inner join sql for all column in 1 proc + + Arguments: + proc_id {[string]} -- [process id] + cols {[list]} -- [column name list] + """ + dic_sensors = gen_dic_sensors(proc.proc_id, proc.col_names) + + cycle_cls = find_cycle_class(proc.proc_id) + if use_global_id: + data = db.session.query(cycle_cls.global_id, cycle_cls.time) + else: + data = db.session.query(cycle_cls.id, cycle_cls.time) + + data = data.filter(cycle_cls.process_id == proc.proc_id) + dataframes = [] + all_sensors = list(zip(proc.col_ids, proc.col_names)) + for idx, chunk_sensor in enumerate(chunks(all_sensors, 50)): + df_chunk = get_sensor_values_chunk(data, chunk_sensor, dic_sensors, cycle_cls, start_relate_ids, + start_tm, end_tm) + if idx != 0 and Cycle.time.key in df_chunk.columns: + df_chunk = df_chunk.drop(Cycle.time.key, axis=1) + dataframes.append(df_chunk) + + df = pd.concat([dfc.set_index(dfc.columns[0]) for dfc in dataframes], ignore_index=False, axis=1).reset_index() + + return df + + +@log_execution_time() +def get_cond_data(proc: ConditionProc, start_relate_ids=None, start_tm=None, end_tm=None, use_global_id=True): + """generate subquery for every condition procs + """ + # get sensor info ex: sensor id , data type (int,real,text) + filter_query = Sensor.query.filter(Sensor.process_id == proc.proc_id) + + # filter + cycle_cls = find_cycle_class(proc.proc_id) + if use_global_id: + data = db.session.query(cycle_cls.global_id) + else: + data = db.session.query(cycle_cls.id) + + data = data.filter(cycle_cls.process_id == proc.proc_id) + + # for filter_sensor in filter_sensors: + for col_name, filter_details in proc.dic_col_name_filters.items(): + sensor = filter_query.filter(Sensor.column_name == col_name).first() + sensor_val = find_sensor_class(sensor.id, DataType(sensor.type), auto_alias=True) + + ands = [] + for filter_detail in filter_details: + comp_ins = [] + comp_likes = [] + comp_regexps = [] + cfg_filter_detail: CfgFilterDetail + for cfg_filter_detail in filter_detail.cfg_filter_details: + val = cfg_filter_detail.filter_condition + if cfg_filter_detail.filter_function == FilterFunc.REGEX.name: + comp_regexps.append(val) + elif not cfg_filter_detail.filter_function \ + or cfg_filter_detail.filter_function == FilterFunc.MATCHES.name: + comp_ins.append(val) + else: + comp_likes.extend(gen_sql_like_value(val, FilterFunc[cfg_filter_detail.filter_function], + position=cfg_filter_detail.filter_from_pos)) + + ands.append( + or_( + sensor_val.value.in_(comp_ins), + *[sensor_val.value.op(SQL_REGEXP_FUNC)(val) for val in comp_regexps if val is not None], + *[sensor_val.value.like(val) for val in comp_likes if val is not None], + ) + ) + + data = data.join( + sensor_val, and_( + sensor_val.cycle_id == cycle_cls.id, + sensor_val.sensor_id == sensor.id, + *ands, + ) + ) + + # chunk + if start_relate_ids: + records = [] + for ids in start_relate_ids: + temp = data.filter(cycle_cls.global_id.in_(ids)) + records += temp.all() + else: + data = data.filter(cycle_cls.time >= start_tm) + data = data.filter(cycle_cls.time < end_tm) + records = data.all() + + return records + + +@log_execution_time() +def gen_dic_sensors(proc_id, cols=None): + """gen dictionary of sensors + {column_name: T_sensor instance} + + Arguments: + proc_id {string} -- process id + """ + + sensors = Sensor.query.filter(Sensor.process_id == proc_id) + if cols: + sensors = sensors.filter(Sensor.column_name.in_(cols)) + + return {sensor.column_name: sensor for sensor in sensors} + + +def order_end_proc_sensor(orig_graph_param: DicParam): + dic_orders = {} + for proc in orig_graph_param.array_formval: + proc_id = proc.proc_id + orders = CfgConstant.get_value_by_type_name(type=CfgConstantType.TS_CARD_ORDER.name, name=proc_id) or '{}' + orders = json.loads(orders) + if orders: + dic_orders[proc_id] = orders + + lst_proc_end_col = [] + for proc in orig_graph_param.array_formval: + proc_id = proc.proc_id + for col_id in proc.col_ids: + proc_order = dic_orders.get(proc_id) or {} + order = proc_order.get(str(col_id)) or 999 + lst_proc_end_col.append((proc_id, col_id, order)) + + return sorted(lst_proc_end_col, key=lambda x: x[-1]) + + +@log_execution_time() +@notify_progress(50) +def gen_plotdata(orig_graph_param: DicParam, dic_data, dic_proc_cfg): + # re-order proc-sensors to show to UI + lst_proc_end_col = order_end_proc_sensor(orig_graph_param) + + plotdatas = [] + array_formval = [] + for proc_id, col_id, _ in lst_proc_end_col: + col_detail = {} + rank_value = {} + get_cols = dic_proc_cfg[proc_id].get_cols([col_id]) + array_y = dic_data[proc_id][col_id] + + if get_cols: + # remove none from data + array_y_without_na = pd.DataFrame(array_y).dropna() + array_y_without_na = array_y_without_na[0].to_list() if not array_y_without_na.empty else [] + # if get_cols[0].data_type == DataType.TEXT.name and len(array_y_without_na): + if get_cols[0].data_type in [DataType.TEXT.name, DataType.INTEGER.name]: + cat_array_y = pd.Series(array_y).astype('category').cat + array_y = cat_array_y.codes.tolist() + + rank_value = {-1: NA_STR} + if (len(cat_array_y.categories.to_list())): + rank_value = dict(enumerate(cat_array_y.categories)) + col_detail = { + 'id': col_id, + 'name': get_cols[0].name, + 'type': get_cols[0].data_type, + 'proc_id': proc_id, + 'proc_name': get_cols[0].cfg_process.name + } + + plotdata = dict(array_y=array_y, col_detail=col_detail, rank_value=rank_value) + plotdatas.append(plotdata) + + array_formval.append({ + END_PROC: proc_id, + GET02_VALS_SELECT: col_id + }) + + return array_formval, plotdatas + + +@log_execution_time() +def gen_category_data(dic_proc_cfgs: Dict[int, CfgProcess], cate_procs: List[CategoryProc], dic_data): + plotdatas = [] + for proc in cate_procs: + proc_id = proc.proc_id + dic_proc = dic_data.get(proc_id) + if dic_proc is None: + continue + + proc_cfg = dic_proc_cfgs[proc_id] + dic_column_cfgs: Dict[int, CfgProcessColumn] = {col.id: col for col in proc_cfg.columns} + + for col_id, col_show_name in zip(proc.col_ids, proc.col_show_names): + array_y = dic_proc.get(col_id) + if array_y is None: + continue + + plotdata = dict(proc_name=proc_id, proc_master_name=proc_cfg.name, + column_name=col_id, column_master_name=col_show_name, + data=array_y) + plotdatas.append(plotdata) + + return plotdatas + + +@log_execution_time() +def clear_all_keyword(dic_param): + """ clear [All] keyword in selectbox + + Arguments: + dic_param {json} -- [params from client] + """ + dic_common = dic_param[COMMON] + cate_procs = dic_common.get(CATE_PROCS, []) + dic_formval = dic_param[ARRAY_FORMVAL] + for idx in range(len(dic_formval)): + select_vals = dic_formval[idx][GET02_VALS_SELECT] + if isinstance(select_vals, (list, tuple)): + dic_formval[idx][GET02_VALS_SELECT] = [val for val in select_vals if val not in [SELECT_ALL, NO_FILTER]] + else: + dic_formval[idx][GET02_VALS_SELECT] = [select_vals] + + for idx in range(len(cate_procs)): + select_vals = cate_procs[idx][GET02_CATE_SELECT] + if isinstance(select_vals, (list, tuple)): + cate_procs[idx][GET02_CATE_SELECT] = [val for val in select_vals if val not in [SELECT_ALL, NO_FILTER]] + else: + cate_procs[idx][GET02_CATE_SELECT] = [select_vals] + + # Need NO_FILTER keyword to decide filter or not , so we can not remove NO_FILTER keyword here. + for cond in dic_common[COND_PROCS]: + for key, value in cond.items(): + if isinstance(value, (list, tuple)): + vals = value + else: + vals = [value] + + if NO_FILTER in vals: + continue + + cond[key] = [val for val in vals if not val == SELECT_ALL] + + +@log_execution_time() +def update_outlier_flg(proc_id, cycle_ids, is_outlier): + """update outlier to t_cycle table + + Arguments: + cycle_ids {[type]} -- [description] + is_outlier {[type]} -- [description] + + Returns: + [type] -- [description] + """ + + # get global_ids linked to target cycles + cycle_cls = find_cycle_class(proc_id) + cycle_recs = cycle_cls.get_cycles_by_ids(cycle_ids) + if not cycle_recs: + return True + + global_ids = [] + for rec in cycle_recs: + if rec.global_id: + global_ids.append(rec.global_id) + else: + rec.is_outlier = is_outlier + + target_global_ids = GlobalRelation.get_all_relations_by_globals(global_ids, set_done_globals=set()) + + # update outlier for linked global ids + # TODO: fix front end + cycle_cls.update_outlier_by_global_ids(list(target_global_ids), is_outlier) + + db.session.commit() + return True + + +@log_execution_time() +def get_serials(trace, proc_name): + return [s.split()[0] for s in trace.hist2_yaml.get_serial_col(proc_name) if s] + + +@log_execution_time() +def get_date_col(trace, proc_name): + date_col = trace.hist2_yaml.get_date_col(proc_name) + date_col = date_col.split()[0] + return date_col + + +def gen_new_dic_param(dic_param, dic_non_sensor, start_proc_first=False): + pass + + +def get_non_sensor_cols(dic_proc_cfgs: Dict[int, CfgProcess], graph_param: DicParam): + """get non sensor headers + + Arguments: + trace {[type]} -- [description] + dic_param {[type]} -- [description] + + Returns: + [type] -- [description] + """ + dic_header = {} + + for proc in graph_param.array_formval: + proc_id = proc.proc_id + proc_cfg = dic_proc_cfgs[proc_id] + serials = proc_cfg.get_serials() + date_col = proc_cfg.get_date_col() + cols = serials + [date_col] + dic_header[proc_id] = cols + + # start proc + proc_id = graph_param.common.start_proc + if not dic_header.get(proc_id): + proc_cfg = dic_proc_cfgs[proc_id] + serials = proc_cfg.get_serials() + date_col = proc_cfg.get_date_col() + cols = serials + [date_col] + dic_header[proc_id] = cols + + return dic_header + + +def get_cate_var(graph_param: DicParam): + cate_procs = graph_param.common.cate_procs + if cate_procs: + return {ele[CATE_PROC]: ele[GET02_CATE_SELECT] for ele in cate_procs if + ele.get(CATE_PROC) and ele.get(GET02_CATE_SELECT)} + + return None + + +def gen_relate_ids(row): + """ + gen start proc relate ids + """ + + relate_ids = [] + if row.global_id: + relate_ids.append(row.global_id) + if row.relate_id: + relate_ids.append(row.relate_id) + + return relate_ids + + +def is_import_job_running(): + return any([job.startswith(str(JobType.FACTORY_IMPORT)) or job.startswith(str(JobType.CSV_IMPORT)) + for job in set(dic_running_job.keys())]) + + +@log_execution_time() +def make_irregular_data_none(dic_param): + array_plotdata = dic_param.get(ARRAY_PLOTDATA) + for num, plotdata in enumerate(array_plotdata): + array_y = plotdata.get(ARRAY_Y) or [] + array_y_type = plotdata.get(ARRAY_Y_TYPE) or [] + if array_y_type: # use y_type to check for irregular data + array_plotdata[num][ARRAY_Y] = \ + [None if array_y_type[idx] not in ( + YType.NORMAL.value, YType.OUTLIER.value, YType.NEG_OUTLIER.value) else e + for idx, e in enumerate(array_y)] + else: # or use value to check for irregular data directly + array_plotdata[num][ARRAY_Y] = \ + [None if e == float('inf') or e == float('-inf') else e for e in array_y] + return dic_param + + +def get_maxmax_minmin_chartinfo(chart_infos): + y_min = float('inf') + y_max = float('-inf') + for chart_info in chart_infos: + c_y_min = chart_info.get(Y_MIN) if chart_info.get(Y_MIN) is not None else float('inf') + if y_min > c_y_min: + y_min = c_y_min + c_y_max = chart_info.get(Y_MAX) if chart_info.get(Y_MAX) is not None else float('-inf') + if y_max < c_y_max: + y_max = c_y_max + # Default (y_min, y_max) = (0, 1) if can not found min/max + # y_min = 0 if y_min == float('inf') else y_min + # y_max = (y_min + 1) if y_max == float('-inf') else y_max + y_min = None if y_min == float('inf') else y_min + y_max = None if y_max == float('-inf') else y_max + return [y_min, y_max] + + +def produce_irregular_plotdata(dic_param): + array_plotdata = dic_param.get(ARRAY_PLOTDATA) + for num, plotdata in enumerate(array_plotdata): + array_y = plotdata.get(ARRAY_Y) or [] + + # calculate upper/lower limit + chart_infos = plotdata[CHART_INFOS] or [] + y_min, y_max = get_maxmax_minmin_chartinfo(chart_infos) + + if y_max is None or y_min is None: + whisker_lower, whisker_upper = calc_upper_lower_whisker(array_y) + y_min = whisker_lower if y_min is None else y_min + y_max = whisker_upper if y_max is None else y_max + + # create new irregular_plotdata of array_y + array_y_type = [] + for idx, e in enumerate(array_y): + # convert inf/none to min/max; nan/na is not supported + if e is None: + array_y_type.append(YType.NONE.value) + elif e == float('inf'): + array_y_type.append(YType.INF.value) + elif e == float('-inf'): + array_y_type.append(YType.NEG_INF.value) + else: # normal values + # convert outlier to min/max + # if e > y-max or e < y-min: + if y_max is not None and e > y_max: + # Sprint 79 #12: Keep actual value, FE display actual value + # array_plotdata[num][ARRAY_Y][idx] = y_max + array_y_type.append(YType.OUTLIER.value) + elif y_min is not None and e < y_min: + # array_plotdata[num][ARRAY_Y][idx] = y_min + array_y_type.append(YType.NEG_OUTLIER.value) + else: + array_y_type.append(YType.NORMAL.value) + + array_plotdata[num][ARRAY_Y_TYPE] = array_y_type + array_plotdata[num][Y_MAX] = y_max + array_plotdata[num][Y_MIN] = y_min + + +def calc_upper_lower_whisker(arr): + arr = [e for e in arr if e not in {None, float('inf'), float('-inf')}] + if arr: + q1 = quantile(arr, 0.25, interpolation='midpoint') + q3 = quantile(arr, 0.75, interpolation='midpoint') + iqr = q3 - q1 + if iqr: + whisker_lower = q1 - 2.5 * iqr + whisker_upper = q3 + 2.5 * iqr + else: + whisker_lower = 0.9 * min(arr) + whisker_upper = 1.1 * max(arr) + return whisker_lower, whisker_upper + return None, None + + +def save_proc_sensor_order_to_db(orders): + try: + for proc_code, new_orders in orders.items(): + CfgConstant.create_or_merge_by_type(const_type=CfgConstantType.TS_CARD_ORDER.name, + const_name=proc_code, + const_value=new_orders) + except Exception as ex: + traceback.print_exc() + logger.error(ex) + + +def get_procs_in_dic_param(graph_param: DicParam): + """ + get process + :param graph_param: + :return: + """ + procs = set() + procs.add(graph_param.common.start_proc) + for proc in graph_param.common.cond_procs: + procs.add(proc.proc_id) + + for proc in graph_param.common.cate_procs: + procs.add(proc.proc_id) + + for proc in graph_param.array_formval: + procs.add(proc.proc_id) + + return {proc.id: proc for proc in CfgProcess.get_procs(procs)} + + +def gen_blank_df(graph_param: DicParam): + data = {Cycle.time.key: [], Cycle.is_outlier.key: []} + return pd.DataFrame(data) diff --git a/histview2/api/ridgeline_plot/controllers.py b/histview2/api/ridgeline_plot/controllers.py new file mode 100644 index 0000000..e3b59d4 --- /dev/null +++ b/histview2/api/ridgeline_plot/controllers.py @@ -0,0 +1,142 @@ +import json +import timeit + +from flask import Blueprint, request, Response + +from histview2.api.ridgeline_plot.services import (gen_trace_data_by_categorical_var, + customize_dict_param, gen_rlp_data_by_term, gen_csv_data, + csv_export_dispatch, save_input_data_to_file, + merge_multiple_dic_params, + gen_trace_data_by_cyclic) +from histview2.common.services import http_content, csv_content +from histview2.common.services.form_env import (parse_multi_filter_into_one, + parse_request_params, + bind_multiple_end_proc_rlp) +from histview2.common.services.import_export_config_n_data import get_dic_form_from_debug_info, \ + set_export_dataset_id_to_dic_param +from histview2.common.trace_data_log import EventType +from histview2.common.yaml_utils import * + +api_ridgeline_plot_blueprint = Blueprint( + 'api_ridgeline_plot', + __name__, + url_prefix='/histview2/api/rlp' +) + +RLP_MAX_GRAPH = 20 + + +@api_ridgeline_plot_blueprint.route('/index', methods=['POST']) +def trace_data(): + """ + Trace Data API + return dictionary + """ + start = timeit.default_timer() + dic_form = request.form.to_dict(flat=False) + save_input_data_to_file(dic_form, EventType.RLP) + dic_param = parse_multi_filter_into_one(dic_form) + + # check if we run debug mode (import mode) + dic_param = get_dic_form_from_debug_info(dic_param) + + customize_dict_param(dic_param) + + compare_type = dic_param.get(COMMON).get(COMPARE_TYPE) + + if compare_type == RL_CATEGORY: + dic_param = gen_trace_data_by_categorical_var(dic_param) + if compare_type == RL_CYCLIC_TERM: + dic_param = gen_trace_data_by_cyclic(dic_param, RLP_MAX_GRAPH) + elif compare_type == RL_DIRECT_TERM: + dic_param = gen_rlp_data_by_term(dic_param, RLP_MAX_GRAPH) + + stop = timeit.default_timer() + dic_param['backend_time'] = stop - start + + # export mode ( output for export mode ) + set_export_dataset_id_to_dic_param(dic_param) + + # remove raw data + for plot in dic_param[ARRAY_PLOTDATA]: + del plot[RL_DATA] + + out_dict = json.dumps(dic_param, ensure_ascii=False, default=http_content.json_serial) + return out_dict, 200 + + +@api_ridgeline_plot_blueprint.route('/csv_export', methods=['GET']) +def csv_export(): + """csv export + + Returns: + [type] -- [description] + """ + + dic_form = parse_request_params(request) + multiple_dform = bind_multiple_end_proc_rlp(dic_form) + + dic_datas = [] + dic_params = [] + for dform in multiple_dform: + dic_param = parse_multi_filter_into_one(dform) + customize_dict_param(dic_param) + + dic_data = csv_export_dispatch(dic_param) + dic_datas.append(dic_data) + dic_params.append(dic_param) + mdic_data = merge_multiple_dic_params(dic_datas) + mdic_param = merge_multiple_dic_params(dic_params) + if not mdic_data: + return {}, 200 + + csv_str = gen_csv_data(mdic_param, mdic_data, mdic_param[COMMON][GET02_VALS_SELECT], + mdic_param[COMMON][CLIENT_TIMEZONE]) + + csv_filename = csv_content.gen_csv_fname() + + response = Response(csv_str.encode("utf-8-sig"), mimetype="text/csv", + headers={ + "Content-Disposition": "attachment;filename={}".format(csv_filename), + }) + response.charset = "utf-8-sig" + + return response + + +@api_ridgeline_plot_blueprint.route('/tsv_export', methods=['GET']) +def tsv_export(): + """tsv export + + Returns: + [type] -- [description] + """ + dic_form = parse_request_params(request) + multiple_dform = bind_multiple_end_proc_rlp(dic_form) + + dic_datas = [] + dic_params = [] + for dform in multiple_dform: + dic_param = parse_multi_filter_into_one(dform) + customize_dict_param(dic_param) + dic_data = csv_export_dispatch(dic_param) + dic_datas.append(dic_data) + dic_params.append(dic_param) + + mdic_data = merge_multiple_dic_params(dic_datas) + mdic_param = merge_multiple_dic_params(dic_params) + if not mdic_data: + return {}, 200 + + csv_str = gen_csv_data(mdic_param, mdic_data, mdic_param[COMMON][GET02_VALS_SELECT], + mdic_param[COMMON][CLIENT_TIMEZONE], delimiter='\t') + + csv_filename = csv_content.gen_csv_fname("tsv") + + response = Response(csv_str.encode("utf-8-sig"), mimetype="text/tsv", + headers={ + "Content-Disposition": "attachment;filename={}".format(csv_filename), + }) + response.charset = "utf-8-sig" + + return response diff --git a/histview2/api/ridgeline_plot/services.py b/histview2/api/ridgeline_plot/services.py new file mode 100644 index 0000000..4c2c2ca --- /dev/null +++ b/histview2/api/ridgeline_plot/services.py @@ -0,0 +1,845 @@ +from collections import defaultdict +from copy import deepcopy + +import numpy as np +import pandas as pd +import pytz +from dateutil import tz + +from histview2.api.categorical_plot.services import (gen_graph_param, + category_bind_dic_param_to_class, + gen_trace_data_by_cyclic_common, split_data_by_condition, + customize_dict_param_common) +from histview2.api.trace_data.services.time_series_chart import (get_data_from_db, gen_new_dic_param, + get_non_sensor_cols, gen_graph, + gen_dic_data_from_df, get_procs_in_dic_param, + main_check_filter_detail_match_graph_data, + get_cfg_proc_col_info) +from histview2.common.common_utils import (start_of_minute, end_of_minute) +from histview2.common.constants import * +from histview2.common.memoize import memoize +from histview2.common.services.ana_inf_data import get_bound, get_grid_points, calculate_kde_for_ridgeline +from histview2.common.services.sse import notify_progress +from histview2.common.timezone_utils import convert_dt_str_to_timezone +from histview2.common.trace_data_log import * +from histview2.setting_module.models import CfgProcess, CfgProcessColumn +from histview2.trace_data.models import Cycle +from histview2.trace_data.schemas import DicParam + + +@log_execution_time() +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.RLP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_trace_data_by_cyclic(dic_param, max_graph=None): + dic_param = gen_trace_data_by_cyclic_common(dic_param) + dic_plotdata = defaultdict(list) + for plotdata in dic_param[ARRAY_PLOTDATA]: + dic_plotdata[plotdata['end_col']].append(plotdata) + + dic_param[ARRAY_PLOTDATA], dic_param[IS_GRAPH_LIMITED] = gen_cyclic_term_plotdata(dic_plotdata, dic_param, + max_graph) + + # calculate emd data + cal_emd_data(dic_param) + gen_rlp_kde(dic_param) + + return dic_param + + +@log_execution_time() +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.RLP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_trace_data_by_categorical_var(dic_param, max_graph=None): + """tracing data to show graph + 1 start point x n end point + filter by condition points that between start point and end_point + """ + # gen graph_param + graph_param, dic_proc_cfgs = gen_graph_param(dic_param) + + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = main_check_filter_detail_match_graph_data( + graph_param, df) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] = matched_filter_ids + dic_param[UNMATCHED_FILTER_IDS] = unmatched_filter_ids + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = not_exact_match_filter_ids + + # gen dic_data + dic_data = gen_dic_data_from_df(df, graph_param) + + # flag to show that trace result was limited + dic_param[IS_RES_LIMITED] = is_res_limited + + # convert proc to cols dic + # transform raw data to graph data + # create output data + orig_graph_param: DicParam = category_bind_dic_param_to_class(dic_param) + dic_data, is_graph_limited = split_data_by_condition(dic_data, orig_graph_param, max_graph) + dic_param[IS_GRAPH_LIMITED] = is_graph_limited + + end_cols = [] + for param in orig_graph_param.array_formval: + end_cols += param.col_ids + + dic_param[ARRAY_PLOTDATA] = gen_custom_plotdata(dic_data, end_cols) + dic_param[ACTUAL_RECORD_NUMBER] = actual_record_number + dic_param[TIMES] = df[Cycle.time.key].tolist() + + # calculate emd data + cal_emd_data(dic_param) + gen_rlp_kde(dic_param) + + return dic_param + + +@log_execution_time() +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.RLP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_rlp_data_by_term(dic_param, max_graph=None): + """rlp data to show graph + filter by condition points that between start point and end_point + """ + + dic_param[ARRAY_PLOTDATA] = [] + terms = dic_param.get(TIME_CONDS) or [] + + dic_param[MATCHED_FILTER_IDS] = [] + dic_param[UNMATCHED_FILTER_IDS] = [] + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = [] + + dic_rlp = defaultdict(dict) + term_results = [] + for term in terms: + # create dic_param for each term from original dic_param + term_dic_param = deepcopy(dic_param) + term_dic_param[TIME_CONDS] = [term] + term_dic_param[COMMON][START_DATE] = term[START_DATE] + term_dic_param[COMMON][START_TM] = term[START_TM] + term_dic_param[COMMON][END_DATE] = term[END_DATE] + term_dic_param[COMMON][END_TM] = term[END_TM] + + # get data from database + visual setting from yaml + term_result = gen_graph(term_dic_param) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] += term_result.get(MATCHED_FILTER_IDS, []) + dic_param[UNMATCHED_FILTER_IDS] += term_result.get(UNMATCHED_FILTER_IDS, []) + dic_param[NOT_EXACT_MATCH_FILTER_IDS] += term_result.get(NOT_EXACT_MATCH_FILTER_IDS, []) + dic_param[ACTUAL_RECORD_NUMBER] = term_result.get(ACTUAL_RECORD_NUMBER, 0) + + term_results.append(term_result) + + # rpl_array_data + dic_rlp, is_graph_limited = transform_data_to_rlp(term_results, max_graph) + dic_param[IS_GRAPH_LIMITED] = is_graph_limited + dic_param[ARRAY_PLOTDATA] = [plotdata for dic_cat_exp in dic_rlp.values() for plotdata in dic_cat_exp.values()] + + # calculate emd data + cal_emd_data(dic_param) + gen_rlp_kde(dic_param) + + return dic_param + + +def transform_data_to_rlp(term_results, max_graph=None): + is_graph_limited = False + dic_plots = defaultdict(dict) + count = 0 + for term_result in term_results: + time_range = term_result[TIME_CONDS][0][START_DT] + 'Z' + ' | ' + term_result[TIME_CONDS][0][END_DT] + 'Z' + for dic_plot in term_result[ARRAY_PLOTDATA]: + selected_sensor = int(dic_plot[END_COL_ID]) + array_y = dic_plot[ARRAY_Y] + sensor_name = dic_plot[END_COL_NAME] + proc_name = dic_plot[END_PROC_NAME] + group_name = dic_plot.get(CAT_EXP_BOX) or '' + + if selected_sensor in dic_plots: + if group_name in dic_plots[selected_sensor]: + plotdata = dic_plots[selected_sensor][group_name] + else: + if max_graph and count >= max_graph: + is_graph_limited = True + continue + + plotdata = gen_blank_rlp_plot(proc_name=proc_name, sensor_name=sensor_name, group_name=group_name) + dic_plots[selected_sensor][group_name] = plotdata + count += 1 + else: + if max_graph and count >= max_graph: + is_graph_limited = True + continue + + plotdata = gen_blank_rlp_plot(proc_name=proc_name, sensor_name=sensor_name, group_name=group_name) + dic_plots[selected_sensor][group_name] = plotdata + count += 1 + + plotdata[RL_DATA].extend(array_y) + plotdata[RL_GROUPS].extend([time_range] * len(array_y)) + rlpdata = dict(array_x=array_y, cate_name=time_range) + plotdata[RL_RIDGELINES].append(rlpdata) + + return dic_plots, is_graph_limited + + +def merge_dict(dict1, dict2): + """ Merge dictionaries and keep values of common keys in list""" + if not dict1: + dict1 = {} + if not dict2: + dict2 = {} + + dict3 = {**dict1, **dict2} + for key, value in dict3.items(): + if key in dict1 and key in dict2: + if isinstance(value, str) or isinstance(value, int): + dict3[key] = value + elif isinstance(value, list): + dict3[key] = value + dict1[key] + else: + dict3[key] = merge_dict(value, dict1[key]) + return dict3 + + +@log_execution_time() +def get_data(trace, dic_param): + """get data from database + + Arguments: + dic_param {[type]} -- [description] + + Returns: + [type] -- [description] + """ + db_code = trace.proc_yaml.get_db_id(dic_param[COMMON][START_PROC]) + is_efa = trace.db_yaml.get_etl_func(db_code) + compare_type = dic_param[COMMON][COMPARE_TYPE] + + dic_cate_var = None + if is_efa: + # get all checked cols + dic_non_sensor = get_checked_cols(trace, dic_param) + else: + # get serials + date + dic_non_sensor = get_non_sensor_cols(trace, dic_param) + # get category var and val + + if compare_type == RL_CATEGORY: + dic_cate_var = get_cate_var(dic_param) + + # edit dic_param + edited_dic_param = gen_new_dic_param(dic_param, dic_non_sensor) + + # cate var and val + if dic_cate_var: + edited_dic_param = gen_new_dic_param(edited_dic_param, dic_cate_var) + + if compare_type == RL_CATEGORY: + edited_dic_param = add_cond_to_dic_param(edited_dic_param) + + # get data from database + dic_data, times, _, _, actual_record_number, is_res_limited = get_data_from_db(trace, edited_dic_param) + + return dic_data, times, actual_record_number, is_res_limited + + +@log_execution_time() +def customize_dict_param(dic_param): + """ Combine start_time, end_time, start_date, end_date into one object + + Arguments: + dic_form {[type]} -- [description] + """ + # end_proc + dic_end_procs = customize_dict_param_common(dic_param) + dic_param[COMMON][END_PROC] = dic_end_procs + dic_param[COMMON][GET02_VALS_SELECT] = list(dic_end_procs) + + # time + start_dates = dic_param.get(COMMON).get(START_DATE) + start_times = dic_param.get(COMMON).get(START_TM) + end_dates = dic_param.get(COMMON).get(END_DATE) + end_times = dic_param.get(COMMON).get(END_TM) + + if type(start_dates) is not list and type(start_dates) is not tuple: + start_dates = [start_dates] + start_times = [start_times] + end_dates = [end_dates] + end_times = [end_times] + + if start_dates and start_times and end_dates and end_times \ + and len(start_dates) == len(start_times) == len(end_dates) == len(end_times): + names = [START_DATE, START_TM, END_DATE, END_TM] + lst_datetimes = [dict(zip(names, row)) for row in zip(start_dates, start_times, end_dates, end_times)] + for idx, time_cond in enumerate(lst_datetimes): + start_dt = start_of_minute(time_cond.get(START_DATE), time_cond.get(START_TM)) + end_dt = end_of_minute(time_cond.get(END_DATE), time_cond.get(END_TM)) + lst_datetimes[idx][START_DT] = start_dt + lst_datetimes[idx][END_DT] = end_dt + dic_param[TIME_CONDS] = lst_datetimes + else: + dic_param[TIME_CONDS] = [] + + +def convert_end_cols_to_array(dic_param): + end_col_alias = dic_param[COMMON][GET02_VALS_SELECT] + if type(end_col_alias) == str: + dic_param[COMMON][GET02_VALS_SELECT] = [end_col_alias] + + from_end_col_alias = dic_param[ARRAY_FORMVAL][0][GET02_VALS_SELECT] + if type(from_end_col_alias) == str: + dic_param[ARRAY_FORMVAL][0][GET02_VALS_SELECT] = [from_end_col_alias] + + +@log_execution_time() +def split_data_by_cyclic_terms(dic_data, times, dic_param): + """split data by condition + + Arguments: + data {[type]} -- [description] + + Returns: + [type] -- [description] + """ + # proc_name = dic_param[COMMON][START_PROC] + # proc_yaml = ProcConfigYaml() + # checked_cols = proc_yaml.get_checked_columns(proc_name) + + proc_id = dic_param.common.start_proc + # cate_col = dic_param.common.cate_procs[0].col_ids[0] + # cates = sorted(set(dic_data[proc_id][cate_col])) + # end_cols = dic_param.array_formval[0].col_ids + + cyclic_terms = dic_param.cyclic_terms + dic_output = {} + + # end_col_alias = dic_param[ARRAY_FORMVAL][0][GET02_VALS_SELECT] + # end_col_alias = dic_param.array_formval[0].col_names + end_col_ids = dic_param.array_formval[0].col_ids + for end_col in end_col_ids: + # end_col = checked_cols.get(end_col)[YAML_COL_NAMES] + # end_col_name = end_col_alias[k] + dic_output[end_col] = {term: [] for term in cyclic_terms} + + for idx, t_cycle_time in enumerate(times): + for end_col in end_col_ids: + # end_col = checked_cols.get(end_col)[YAML_COL_NAMES] + # end_col_name = end_col_alias[k] + end_col_data = dic_data[proc_id][end_col] + for cyclic_term in cyclic_terms: + if cyclic_term[0] <= t_cycle_time <= cyclic_term[1]: + dic_output[end_col][cyclic_term].append(end_col_data[idx]) + + return dic_output + + +# @log_execution_time() +# def split_data_by_condition(dic_data, graph_param: DicParam): +# """split data by condition +# +# Arguments: +# data {[type]} -- [description] +# +# Returns: +# [type] -- [description] +# """ +# proc_id = graph_param.common.start_proc +# cate_col = graph_param.common.cate_procs[0].col_ids[0] +# no_null_data_set = set([x for x in set(dic_data[proc_id][cate_col]) if x is not None]) +# cates = sorted(no_null_data_set) +# dic_output = {} +# +# end_cols = graph_param.array_formval[0].col_ids +# for end_col in end_cols: +# dic_output[end_col] = {cate: [] for cate in cates} +# +# for cate_val, end_val in zip(dic_data[proc_id][cate_col], dic_data[proc_id][end_col]): +# if cate_val is not None: +# dic_output[end_col][cate_val].append(end_val) +# +# return dic_output + + +def get_cate_var(dic_param): + cate_vars = dic_param[COMMON].get(f'{CATE_VARIABLE}1', []) + if not isinstance(cate_vars, (list, tuple)): + cate_vars = [cate_vars] + + return {dic_param[COMMON][START_PROC]: cate_vars} + + +def add_cond_to_dic_param(dic_param): + cate_var = dic_param[COMMON].get(f'{CATE_VARIABLE}1') + cate_vals = dic_param[COMMON].get(f'{CATE_VALUE_MULTI}1', []) + if not isinstance(cate_vals, (list, tuple)): + cate_vals = [cate_vals] + + edited_dic_param = deepcopy(dic_param) + + # start proc + proc_name = dic_param[COMMON][START_PROC] + + cond_proc = None + for ele in edited_dic_param[COMMON][COND_PROCS]: + if ele[COND_PROC] == proc_name: + cond_proc = ele + break + + if cond_proc: + cond_proc.update({cate_var: cate_vals}) + else: + cond_proc = {COND_PROC: proc_name, cate_var: cate_vals} + edited_dic_param[COMMON][COND_PROCS].append(cond_proc) + + return edited_dic_param + + +@log_execution_time() +def cal_emd_data(dic_param): + array_plotdatas = dic_param.get(ARRAY_PLOTDATA) or {} + num_bins = 100 + emds = [] + for sensor_dat in array_plotdatas: + data = sensor_dat[RL_DATA] + if not len(data): + continue + + group_ids = sensor_dat[RL_GROUPS] + + # convert to dataframe + dic_emds = {RL_GROUPS: group_ids, 'data': data} + df = pd.DataFrame(dic_emds) + + # dropna before calc emd + df = df.replace(dict.fromkeys([np.inf, -np.inf, np.nan], np.nan)).dropna() + group_ids = df[RL_GROUPS] + + # revert original dataframe without groups + df.drop(RL_GROUPS, inplace=True, axis=1) + emd_stacked_without_nan = df.to_numpy() + emd_array = calc_emd_for_ridgeline(emd_stacked_without_nan, np.array(group_ids), num_bins) + + emds.append(np.stack(emd_array, axis=-1).tolist()[0]) + + dic_param[RL_EMD] = emds + + +@log_execution_time() +def calc_emd_for_ridgeline(data, group_id, num_bins, signed=True, diff=False): + """ + Calculate Earth Mover's Distance (EMD) for each sensor data + + Inputs: + data [2d numpy array] + group_id [1d numpy array] + num_bins [integer] + signed [boolean] if True, return EMD without taking np.abs() + diff [boolean] if True, calculates EMD based on the diff of 1-step + Returns: + emd_mat [2d numpy array] (group_id x num of sensors) + """ + + # in case when data was 1d array (only 1 sensor selected) + if len(data.shape) == 1: + data = data.reshape(-1, 1) + + num_groups = len(np.unique(group_id)) + num_sensors = data.shape[1] + emd_mat = np.zeros((num_groups, num_sensors)) + + # calculate emd sequence in each sensor + for sensor in np.arange(num_sensors): + dens_mat = np.zeros((num_groups, num_bins)) + x = data[:, sensor] + + # generate bins for histograms + x_wo_none = x[x != None] + group_id_wo_none = np.delete(group_id, np.where(x == None)) + + x_min = np.nanmin(x_wo_none) + x_max = np.nanmax(x_wo_none) + # in case of 0 standard deviation + if x_min == x_max: + x_min -= 4 + x_max += 4 + bins = np.linspace(x_min, x_max, num=num_bins + 1) + + # histogram for all group_ids + for g, grp in enumerate(np.unique(group_id_wo_none)): + bin_count, _ = np.histogram(x_wo_none[group_id_wo_none == grp], bins=bins) + dens_mat[g, :] = bin_count / np.sum(bin_count) + + # reference density (first density or previous density) + if diff: + ref_density = np.vstack([dens_mat[0, :], dens_mat[:(num_groups - 1), :]]) + else: + ref_density = np.tile(dens_mat[0, :], (num_groups, 1)) + + # calculate emd (matrix multiplication form) + if signed: + emd = (dens_mat - ref_density) @ np.arange(1, num_bins + 1).reshape(-1, 1) + else: + emd = np.zeros(num_groups) + for g, _ in enumerate(np.unique(group_id_wo_none)): + # exact 1D EMD + # https://en.wikipedia.org/wiki/Earth_mover%27s_distance#Computing_the_EMD + emd_1d = np.zeros(num_bins + 1) + for bin_idx in range(1, num_bins + 1): + emd_1d[bin_idx] = ref_density[g, bin_idx - 1] - dens_mat[g, bin_idx - 1] + emd_1d[bin_idx - 1] + emd[g] = np.sum(np.abs(emd_1d)) + + # scale emd to have original unit + emd = emd / (num_bins - 1) * (x_max - x_min) + emd_mat[:, sensor] = emd.reshape(-1) + + return emd_mat + + +@log_execution_time() +def gen_cyclic_term_plotdata(dic_data, dic_param, max_graph=None): + is_graph_limited = False + plotdatas = [] + sensors = dic_param[COMMON][GET02_VALS_SELECT] + + for k, sensor in enumerate(sensors): + if max_graph and len(plotdatas) >= max_graph: + plotdatas = plotdatas[:max_graph] + is_graph_limited = True + break + + dic_group_by_cat = {} + for dic_plot in dic_data[int(sensor)]: + array_y = dic_plot[ARRAY_Y] + if array_y: + term_obj = dic_param[TIME_CONDS][dic_plot['term_id']] + cate_name_str = f'{term_obj[START_DT]} | {term_obj[END_DT]}' + group_name = dic_plot.get(CAT_EXP_BOX) or '' + + if group_name in dic_group_by_cat: + plotdata = dic_group_by_cat[group_name] + else: + plotdata = gen_blank_rlp_plot(proc_name=dic_plot[END_PROC_NAME], sensor_name=dic_plot[END_COL_NAME], + group_name=group_name) + dic_group_by_cat[group_name] = plotdata + + plotdata[RL_DATA].extend(array_y) + plotdata[RL_GROUPS].extend([cate_name_str] * len(array_y)) + rlpdata = dict(array_x=array_y, cate_name=cate_name_str) + plotdata[RL_RIDGELINES].append(rlpdata) + + if dic_group_by_cat: + plotdatas += list(dic_group_by_cat.values()) + return plotdatas, is_graph_limited + + +@log_execution_time() +def gen_custom_plotdata(dic_data, sensors): + plotdatas = [] + dic_procs, dic_cols = get_cfg_proc_col_info(sensors) + for sensor_id in sensors: + cfg_col: CfgProcessColumn = dic_cols[sensor_id] + cfg_proc: CfgProcess = dic_procs[cfg_col.process_id] + + plotdata = gen_blank_rlp_plot(proc_name=cfg_proc.name, sensor_name=cfg_col.name) + for cate_name, dic_plot in dic_data[sensor_id].items(): + array_y = dic_plot[ARRAY_Y] + plotdata[RL_DATA].extend(array_y) + plotdata[RL_GROUPS].extend([cate_name] * len(array_y)) + rlpdata = dict(array_x=array_y, cate_name=cate_name) + plotdata[RL_RIDGELINES].append(rlpdata) + plotdatas.append(plotdata) + return plotdatas + + +def get_checked_cols(trace, dic_param): + dic_header = {} + for proc in dic_param[ARRAY_FORMVAL]: + proc_name = proc[END_PROC] + end_cols = proc[GET02_VALS_SELECT] + if isinstance(end_cols, str): + end_cols = [end_cols] + + checked_cols = trace.proc_yaml.get_checked_columns(proc_name) + cols = [] + for col, col_detail in checked_cols.items(): + data_type = col_detail[YAML_DATA_TYPES] + # alias_name = col_detail[YAML_ALIASES] + if data_type == DataType.REAL.name or col in end_cols: + continue + + cols.append(col) + + dic_header[proc_name] = cols + return dic_header + + +@log_execution_time() +@notify_progress(75) +def csv_export_dispatch(dic_param): + proc_name = dic_param.get(COMMON).get(END_PROC) + time_conds = dic_param.get(TIME_CONDS) + compare_type = dic_param.get(COMMON).get(COMPARE_TYPE) + + if not proc_name or not time_conds: + return False + + # convert to array to query data for many sensors + convert_end_cols_to_array(dic_param) + cate_var = None + if compare_type == RL_CATEGORY: + cate_var = dic_param[COMMON].get(f'{CATE_VARIABLE}1') + if not cate_var: + return False + + if isinstance(cate_var, (list, tuple)): + cate_var = cate_var[0] + + dic_param = gen_trace_data_by_categorical_var(dic_param) + if compare_type == RL_CYCLIC_TERM: + cate_var = RL_PERIOD + dic_param = gen_trace_data_by_cyclic(dic_param) + elif compare_type == RL_DIRECT_TERM: + cate_var = RL_PERIOD + dic_param = gen_rlp_data_by_term(dic_param) + + # cate name for emd + cate_vals = [dic_ridge[RL_CATE_NAME] for dic_ridge in dic_param[ARRAY_PLOTDATA][0][RL_RIDGELINES]] + dic_data = {proc_name: {cate_var: cate_vals, **dict(zip(dic_param[COMMON][GET02_VALS_SELECT], dic_param[RL_EMD]))}} + + return dic_data + + +@log_execution_time() +def gen_csv_data(dic_param, dic_data, sensors, client_tz, delimiter=None): # get the most cover flows + """tracing data to show csv + 1 start point x n end point + filter by condition points that between start point and end_point + """ + + if delimiter: + csv_data = to_csv(dic_param, dic_data, sensors, client_tz, delimiter=delimiter) + else: + csv_data = to_csv(dic_param, dic_data, sensors, client_tz) + + return csv_data + + +@log_execution_time() +def to_csv(dic_param, dic_data, sensors, client_tz=None, newline='\n', delimiter=','): + """generate csv export string + + Arguments: + trace {[Trace]} -- [tracing information] + dic_data {[dictionary]} -- [export data] + + Keyword Arguments: + newline {str} -- [description] (default: {'\n'}) + delimiter {str} -- [description] (default: {','}) + + Returns: + [type] -- [description] + """ + out_str = '' + + graph_param = category_bind_dic_param_to_class(dic_param) + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + # get columns + cols = [] + for proc_id, data in dic_data.items(): + # get master name of proc + proc_cfg: CfgProcess = dic_proc_cfgs[int(proc_id)] + proc_master_name = proc_cfg.name + + cols = [] + for col in data: + if col == RL_PERIOD: + dt_frm, dt_to = col.split('|') + cols.append(f'{proc_cfg.name}|{dt_frm}') + cols.append(f'{proc_cfg.name}|{dt_to}') + else: + column = CfgProcessColumn.query.get(int(col)) + show_col = column.name + if col in sensors: + show_col += '|emd' + + # col_name = CfgProcessColumn.get_by_col_name(proc_id,col) + cols.append(f'{proc_cfg.name}|{show_col}') + + out_str += delimiter.join(cols) + out_str += newline + + # get rows + merged_rows = [] + for proc_id, proc_values in dic_data.items(): + for col_name, col_values in proc_values.items(): + if col_name == RL_PERIOD: + # get client timezone + client_timezone = pytz.timezone(client_tz) if client_tz else tz.tzlocal() + # client_timezone = tz.gettz(client_tz or None) or tz.tzlocal() + + arr_from = [] + arr_to = [] + for val in col_values: + dt_frm, dt_to = val.split('|') + arr_from.append(convert_dt_str_to_timezone(client_timezone, dt_frm)) + arr_to.append(convert_dt_str_to_timezone(client_timezone, dt_to)) + + merged_rows.append(arr_from) + merged_rows.append(arr_to) + else: + merged_rows.append(col_values) + + for row in zip(*merged_rows): + if row[0] == '': + continue + out_str += delimiter.join([str(i) for i in row]) + out_str += newline + + return out_str + + +@log_execution_time() +def gen_rlp_kde(dic_param): + # retrieve the ridge-lines from array_plotdata + array_plotdata = dic_param.get(ARRAY_PLOTDATA) + for _, plotdata in enumerate(array_plotdata): + # plotdata[RL_KDE] = {} + plotdata_rlp = plotdata.get(RL_RIDGELINES) + + bounds = get_bound(plotdata_rlp) + grid_points = get_grid_points(plotdata_rlp, bounds=bounds) + for num, ridgeline in enumerate(plotdata_rlp): + array_x = ridgeline.get(ARRAY_X) + ridgeline[RL_KDE] = calculate_kde_for_ridgeline(array_x, grid_points, height=3) + + res = transform_rlp_kde(dic_param) + return res + + +@log_execution_time() +def transform_rlp_kde(dic_param): + default_hist_bins = 128 + # retrieve the ridge-lines from array_plotdata + array_plotdata = dic_param.get(ARRAY_PLOTDATA) + + # scale ratio from the maximum value of RLP chart's x-axis, + # RLP line height, default is 2% chart + scale_ratio = 0.02 + + for _, plotdata in enumerate(array_plotdata): + plotdata_rlp = plotdata.get(RL_RIDGELINES) + + start_value = 0.1 + # calculate the step value between 2 line + total_lines = len(plotdata_rlp) + + if total_lines > 1: + line_steps = 1 / (total_lines - 1) + else: + # if data have one ridge line only, the first line will be draw from x=0.1 in xaxis + line_steps = 0.1 + + plotdata[RL_XAXIS] = [] + # distinct groups + plotdata[RL_CATES] = list(dict.fromkeys(plotdata[RL_GROUPS])) + # plotdata['categories'] = distinct_rlp_groups(plotdata['groups']) + rlp_range_min = [] + rlp_range_max = [] + + # get max value from kde, use to make new xaxis range + max_kde_list = [] + tmp_histlabel = [] + for num, ridgeline in enumerate(plotdata_rlp): + # calculate trans value from start_value and line_steps + trans_val = start_value + (num * line_steps) + kde_data = ridgeline.get(RL_KDE) + + if kde_data[RL_DEN_VAL]: + max_value = max(kde_data[RL_DEN_VAL]) + trans_val + max_kde_list.append(max_value) + + if len(kde_data[RL_HIST_LABELS]) > 1: + tmp_histlabel = kde_data[RL_HIST_LABELS] + + for num, ridgeline in enumerate(plotdata_rlp): + kde_data = ridgeline.get(RL_KDE) + + # calculate trans value from start_value and line_steps + trans_val = start_value + (num * line_steps) + trans_val_list = [trans_val] * len(kde_data[RL_DEN_VAL]) + trans_obj = {RL_ORG_DEN: kde_data[RL_DEN_VAL], RL_TRANS_VAL: trans_val_list} + trans_val_df = pd.DataFrame(trans_obj) + + # devide by maximum value of density, except max = 0 + max_den_val = trans_val_df[RL_ORG_DEN].max() + if max_den_val: + trans_kde_val = trans_val_df[RL_ORG_DEN] / max_den_val + else: + trans_kde_val = trans_val_df[RL_ORG_DEN] + + # convert to new value with line by steps and scale ratio + new_kde_df = (trans_kde_val * scale_ratio) + trans_val_df[RL_TRANS_VAL] + new_kde_val = new_kde_df.to_list() + + ridgeline[RL_TRANS_DEN] = new_kde_val + if (len(new_kde_val) == 1): + ridgeline[RL_TRANS_DEN] = trans_val_list * default_hist_bins + ridgeline[RL_KDE][RL_HIST_LABELS] = tmp_histlabel * default_hist_bins + + plotdata[RL_XAXIS].append(trans_val) + + # get min/max range from numpy array kde_data + if kde_data[RL_DEN_VAL]: + if kde_data[RL_HIST_LABELS]: + rlp_range_min.append(min(kde_data[RL_HIST_LABELS])) + rlp_range_max.append(max(kde_data[RL_HIST_LABELS])) + + # delete un-use params in rigdeline node + ridgeline[RL_DATA_COUNTS] = len(ridgeline[ARRAY_X]) + del ridgeline[ARRAY_X] + del ridgeline[RL_KDE][RL_HIST_COUNTS] + del ridgeline[RL_KDE][RL_DEN_VAL] + if rlp_range_min: + rlp_yaxis_min = round(min(rlp_range_min)) if len(rlp_range_min) > 1 else round(rlp_range_min[0]) + else: + rlp_yaxis_min = 0 + + if rlp_range_max: + rlp_yaxis_max = round(min(rlp_range_max)) if len(rlp_range_max) > 1 else round(rlp_range_max[0]) + else: + rlp_yaxis_max = 0 + plotdata[RL_YAXIS] = [rlp_yaxis_min, rlp_yaxis_max] + + # delete groups + del plotdata[RL_GROUPS] + + return dic_param + + +def distinct_rlp_groups(groups): + unique_groups = [] + for group_name in groups: + if group_name not in unique_groups: + unique_groups.append(group_name) + return unique_groups + + +def merge_multiple_dic_params(dic_params): + if len(dic_params) > 1: + merged_dic_params = merge_dict(*dic_params) + return merged_dic_params + return dic_params[0] + + +def gen_blank_rlp_plot(proc_name='', sensor_name='', group_name=''): + return {RL_DATA: [], RL_GROUPS: [], RL_RIDGELINES: [], RL_SENSOR_NAME: sensor_name, RL_PROC_NAME: proc_name, + CAT_EXP_BOX: group_name} diff --git a/histview2/api/sankey_plot/controllers.py b/histview2/api/sankey_plot/controllers.py new file mode 100644 index 0000000..f5880e9 --- /dev/null +++ b/histview2/api/sankey_plot/controllers.py @@ -0,0 +1,59 @@ +import json +import timeit + +from flask import Blueprint, request + +from histview2.api.ridgeline_plot.services \ + import customize_dict_param, convert_end_cols_to_array +from histview2.api.sankey_plot.sankey_glasso.sankey_services import gen_graph_sankey_group_lasso +from histview2.common.services import http_content +from histview2.common.services.form_env import parse_multi_filter_into_one +from histview2.common.services.import_export_config_n_data import get_dic_form_from_debug_info, \ + set_export_dataset_id_to_dic_param +from histview2.common.trace_data_log import save_input_data_to_file, EventType +from histview2.common.yaml_utils import * + +api_sankey_plot_blueprint = Blueprint( + 'api_sankey_plot', + __name__, + url_prefix='/histview2/api/skd' +) + + +@api_sankey_plot_blueprint.route('/index', methods=['POST']) +def trace_data(): + """ + Trace Data API + return dictionary + """ + start = timeit.default_timer() + dic_form = request.form.to_dict(flat=False) + + # save dic_form to pickle (for future debug) + save_input_data_to_file(dic_form, EventType.SKD) + + dic_param = parse_multi_filter_into_one(dic_form) + + # check if we run debug mode (import mode) + dic_param = get_dic_form_from_debug_info(dic_param) + + customize_dict_param(dic_param) + + proc_name = dic_param.get(COMMON).get(END_PROC) + time_conds = dic_param.get(TIME_CONDS) + + if not proc_name or not time_conds: + return {}, 200 + + # convert to array to query data for many sensors + convert_end_cols_to_array(dic_param) + + dic_param = gen_graph_sankey_group_lasso(dic_param) + stop = timeit.default_timer() + dic_param['backend_time'] = stop - start + + # export mode ( output for export mode ) + set_export_dataset_id_to_dic_param(dic_param) + + out_dict = json.dumps(dic_param, ensure_ascii=False, default=http_content.json_serial) + return out_dict, 200 diff --git a/histview2/api/sankey_plot/sankey_glasso/glasso.py b/histview2/api/sankey_plot/sankey_glasso/glasso.py new file mode 100644 index 0000000..b172a37 --- /dev/null +++ b/histview2/api/sankey_plot/sankey_glasso/glasso.py @@ -0,0 +1,222 @@ + +import numpy as np +from sklearn.covariance import graphical_lasso, empirical_covariance +from sklearn.preprocessing import StandardScaler + + +class GaussianGraphicalModel(): + ''' + Calculate sparse partial correlation matrix with GraphicalLASSO + This implementation is not a standard usage; + penalty factor alpha will be automatically selected based on + number of variables directly connected with target variables. + + alpha in which + minimum number of variables directly connected with targets exceed `num_directs` + is selected. + + For example, assume we have 2 targets and `num_directs` is set to 3. + We search alpha that gives more than 2 directly connected variables for both targets. + + By restricting the number of connection, + we seek to obtain more interpretable results. + Tuning based on ExtendedBIC was difficult to obtain desired results. + + Parameters: + ---------- + alpha (optional): float + Shrinkage paramteter alpha. High value returns more sparse result. + If this value is specified, do not search and force to use this value. + + num_directs (optional): int + Objective number of directly connected variables of target(s). + If not given, this will be automatically set. + + Attributes: + ---------- + scaler: StandardScaler object + Used for anomaly detection (when we want to use results for new data) + + parcor: NumpyArray of shape (X.shape[1], X.shape[1]) + Partial correlation matrix + ''' + + def __init__(self, alpha=None, num_directs=None): + self.alpha = alpha + self.num_directs = num_directs + self.scaler = None + self.pmat = None + self.parcor = None + + def fit(self, X, idx_tgt): + ''' + Fit GraphcialLASSO + + Inputs: + ---------- + X: 2d NumpyArray or pandas dataframe of size (sample_size, num_sensors) + sensor data + idx_tgt: list + column index of target variable(s) + ''' + + # scaling + scaler = StandardScaler().fit(X) + X = scaler.transform(X) + + # covariance matrix + emp_cov = empirical_covariance(X) + + # glasso (if alpha is given, force to use it) + if self.alpha is None: + parcor = self._calc_parcor_tuned(emp_cov, idx_tgt) + else: + parcor = self._calc_parcor(emp_cov, self.alpha) + + + self.scaler = scaler + self.parcor = parcor + + def _calc_parcor(self, emp_cov, alpha): + ''' + Calculate sparse partial correlation matrix with glasso + + Inputs: + ---------- + emp_cov: NumpyArray of shape (X.shape[1], X.shape[1]) + alpha: float + + Returns: + ---------- + parcor: [NumpyArray] of shape (X.shape[1], X.shape[1]) + ''' + # precision matrix + pmat = graphical_lasso(emp_cov, alpha)[1] + # presicion matrix and partial correlation matrix + parcor = - pmat / (np.sqrt(np.diag(pmat)).reshape(-1, 1) @ np.sqrt(np.diag(pmat)).reshape(1, -1)) + # no self-loops + np.fill_diagonal(parcor, 0.0) + return parcor + + def _calc_parcor_tuned(self, emp_cov, idx_tgt): + ''' + Automatic tuning of alpha based on number of variables firectly connected to target variable(s). + Search from high alpha, and stop if number of variables exceed `num_directs`. + + Inputs: + ---------- + emp_cov: NumpyArray of shape (X.shape[1], X.shape[1]) + idx_tgt: list + + Returns: + ---------- + parcor: [NumpyArray] of shape (X.shape[1], X.shape[1]) + ''' + # automatically set the objective of number of variables to extract + if self.num_directs is None: + num_sensors = emp_cov.shape[0] + num_targets = len(idx_tgt) + num_exploratory = num_sensors - num_targets + num_directs_cand = np.max([3, 1 + np.ceil(np.sqrt(num_targets))]) + self.num_directs = int(np.min([num_exploratory, num_directs_cand])) + + # search from high alpha + alphas = np.linspace(0.1, 1, 20)[::-1] + + for i in range(len(alphas)): + # sparse partial correlation matrix + parcor = self._calc_parcor(emp_cov, alphas[i]) + # count number of directly connected variables + num_dir_vars = self._count_direct_vars(parcor, idx_tgt) + # do we have enough variables? + if num_dir_vars >= self.num_directs: + print('Converged. alpha: {}, num_dir_vars: {}'.format(alphas[i], num_dir_vars)) + break + + self.alpha = alphas[i] + return parcor + + def _count_direct_vars(self, parcor, idx_tgt): + ''' + Calculate number of directly connected variables to targets. + If multiple targets are specified, return the minimum number of connections. + + Inputs: + ---------- + parcor: NumpyArray of shape (X.shape[1], X.shape[1]) + idx_tgt: list + + Returns: + ---------- + int of number of directly connected variables + ''' + num_directs = [] + for i in range(len(idx_tgt)): + num_directs.append(len(np.where(np.abs(parcor[idx_tgt[i], :]) > 0)[0])) + return min(num_directs) + + +def remove_unnecessary_edges(parcor, idx_tgt, num_layers=2): + ''' + Remove unnecessary edges from partial correlation matrix + + Inputs: + ---------- + parcor : 2d NumpyArray + partial correlation matrix (num_cols x num_cols) + + idx_tgt : list + index list of target column(s) + + num_layers : int, default=2 + number of layers (not including target variables) to extract + + Returns: + ---------- + adj_mat : 2d NumpyArray which indicate adjacency matrix. + This array is strictly upper triangular, and + (j, i) th element indicate partial correlation between (j, i). + ''' + + def extract_connected_idx(parcor, source_idx, from_idx): + ''' Extract index of directly connected nodes ''' + idx_connected = [] + for i in range(len(source_idx)): + idx_cand = np.where(np.abs(parcor[:, source_idx[i]]) > 0)[0] + idx_connected.extend(from_idx[idx_cand]) + idx_connected = np.unique(idx_connected) + return idx_connected + + def parcor2adj(parcor, dic_idx, num_layers): + ''' Convert partial correlation matrix to adjacency matrix ''' + # remove all paths inside each layer (including target) + adj_mat = parcor.copy() + for i in range(num_layers + 1): + idx_curr_layer = dic_idx['idx_layer' + str(i)] + if len(idx_curr_layer) > 0: + adj_mat[np.ix_(idx_curr_layer, idx_curr_layer)] = 0.0 + + # remove all path from/to remaining nodes + if len(dic_idx['idx_remain']) > 0: + adj_mat[dic_idx['idx_remain'], :] = 0.0 + adj_mat[:, dic_idx['idx_remain']] = 0.0 + + # we only need upper triangularelements + adj_mat = np.triu(adj_mat) + return adj_mat + + # recursively extract directly correlated variables starting from target variables + idx_all = np.arange(0, parcor.shape[0], 1) + dic_idx = {'idx_all': idx_all, + 'idx_layer0': idx_tgt, + 'idx_remain': np.setdiff1d(idx_all, idx_tgt)} + + for i in range(num_layers): + parcor_ = parcor[dic_idx['idx_remain'], :] + prev_layer = 'idx_layer' + str(i) + curr_layer = 'idx_layer' + str(i + 1) + dic_idx[curr_layer] = extract_connected_idx(parcor_, dic_idx[prev_layer], dic_idx['idx_remain']) + dic_idx['idx_remain'] = np.setdiff1d(dic_idx['idx_remain'], dic_idx[curr_layer]) + + adj_mat = parcor2adj(parcor, dic_idx, num_layers) + return adj_mat diff --git a/histview2/api/sankey_plot/sankey_glasso/grplasso.py b/histview2/api/sankey_plot/sankey_glasso/grplasso.py new file mode 100644 index 0000000..687c42e --- /dev/null +++ b/histview2/api/sankey_plot/sankey_glasso/grplasso.py @@ -0,0 +1,474 @@ +import numpy as np +import colorsys +from sklearn.linear_model import Ridge +from sklearn.preprocessing import StandardScaler +from group_lasso import GroupLasso + +# - preprocess_skdpage() +# # group lasso +# - calc_coef_and_group_order() +# - fit_grplasso() +# - calc_bic() +# - determine_group_order() +# - fit_ridge() +# # generate dict for skd and barchart +# - GroupSankeyDataProcessor + + +def preprocess_skdpage(X, + y, + groups: list, + colnames_x: list, + colname_y: str, + penalty_factors=[0.0, 10.0, 20.0, 50.0], + max_datapoints=5000, + verbose=False): + """ + Main function to generate data for SkD page (with group lasso) + + Parameters + ---------- + X : 2d NumpyArray + Explanatory variables. + y : 1d NumpyArray + Objective variable. + groups : list + A list of groups assigned to each explanatory variables. + Example: ["process0", "process0", "process1", "process2", ...] + colnames_x : list + A list of sensor names of explanatory variables. + colname_y : str + A string of the name of objective variable. + penalty_factors : list + Reguralization factor for group lasso. + max_datapoints : int + If X.shape[0] exceeds this value, take random samples from X to save time. + verbose : True/False + If True, print info + + Returns + ---------- + dic_skd : dict + A set of data used for sankey diagram + dic_bar : dict + A set of data used for barchart + """ + + # prepare group information + uniq_grps, idx_grps = np.unique(groups, return_inverse=True) + dic_groups = {'colnames_x': colnames_x, + 'colname_y': colname_y, + 'groups': groups, # group names (raw) + 'idx_grps': idx_grps, # group names (int) + 'uniq_grps': uniq_grps, # unique group names + 'num_grps': len(uniq_grps)} # number of unique groups + + # resample data if exceed max_datapoints + if X.shape[0] > max_datapoints: + idx = np.random.choice(X.shape[0], size=max_datapoints, replace=False) + X = X[idx, :].copy() + y = y[idx].copy() + if verbose: + print("Number of data points exceeded {}. Data is automatically resampled. ".format(max_datapoints)) + + # group lasso and ridge regression + coef, group_order = calc_coef_and_group_order(X, y, dic_groups, penalty_factors, verbose=verbose) + + # skd data + processor = GroupSankeyDataProcessor(coef, dic_groups, group_order, verbose=verbose) + dic_skd, dic_bar = processor.gen_dicts() + return dic_skd, dic_bar + + +def calc_coef_and_group_order(X, y, dic_groups, penalty_factors=[0, 0.5, 1.0, 5.0], verbose=False): + """ + Calculate connection strength from x to y, and importance order of groups. + If only single group is given, just fit with ridge regression. + + Parameters + ---------- + X : 2d NumpyArray + Explanatory variables. + y : 1d NumpyArray + Objective variable. + dic_groups : dict + A dictionary with group information + penalty_factors : list + Reguralization factor for group lasso. + verbose : True/False + + Returns + ---------- + coef : 1d numpy array + regression coefficients. + group_order : list + A list of order of groups, where less important is on the left. + """ + + coef = np.zeros(X.shape[1]) + group_order = np.arange(dic_groups["num_grps"]) + + X = StandardScaler().fit_transform(X) + y = StandardScaler().fit_transform(y) + + # groups lasso (if 2 or more groups are given) + idx_for_ridge = np.arange(X.shape[1]) + if dic_groups["num_grps"] > 1: + # fit group lasso with various penalty factors (no L1 penalty) + coef_history, bic = fit_grplasso(X, y.flatten(), dic_groups["idx_grps"], penalty_factors, verbose) + # determine order of groups + group_order = determine_group_order(coef_history, dic_groups) + # use selected columns ffor ridge regression + idx_for_ridge = np.where(np.abs(coef_history[np.argmin(bic), :]) > 0.0)[0] + + # re-calculate coefficients with ridge regression + if len(idx_for_ridge) == 0: + # just in case when all columns are deleted + idx_for_ridge = np.arange(X.shape[1]) + coef[idx_for_ridge] = fit_ridge(X[:, idx_for_ridge], y) + + if verbose: + print("==========") + print('Group order: {}'.format(dic_groups["uniq_grps"][group_order])) + print('Index used for ridge: {}'.format(idx_for_ridge)) + print('Coef: {}'.format(coef)) + + return coef, group_order + + +def fit_grplasso(X, y, grps, penalty_factors=[0.01, 0.1, 1.0, 10.0, 100.0], verbose=False): + """ + Fit group lasso in each penalty factor. + + Parameters + ---------- + X : 2d NumpyArray + Explanatory variables. + y : 1d NumpyArray + Objective variable. + grps : 1d NumpyArray + Group ID assigned to each explanatory variable. + penalty_factors : list + Lasso reguralization factor for group lasso. + + Returns + ---------- + coef_history: 2d NumpyArray + Regression coefficients. (len(penalty_facotrs) x X.shape[1]). + bic : 1d NumpyArray + BIC in each penalty_faactor. Smaller the better. + """ + + bic = np.empty(len(penalty_factors)) + coef_history = np.empty((len(penalty_factors), X.shape[1])) + + for i, rho in enumerate(penalty_factors): + if verbose: + print("==========") + print("Fitting with penalty: {}".format(rho)) + gl = GroupLasso( + groups=grps, + group_reg=rho, + l1_reg=0.0, + frobenius_lipschitz=False, + scale_reg="inverse_group_size", + supress_warning=True, + n_iter=200, + tol=1e-2) + gl.fit(X, y) + coef_history[i, :] = gl.coef_.flatten() + bic[i] = calc_bic(gl.predict(X).flatten(), y, gl.coef_) + if verbose: + print("BIC={}".format(np.round(bic[i], 2))) + print("Number of dropped columns: {}".format(np.sum(coef_history[i, :] == 0.0))) + + return coef_history, bic + + +def calc_bic(y_est, y_true, coef): + # calc_bic(mse, sample_size, coef): + """ + Calculate Bayesian Information Criteria (BIC). + + Parameters + ---------- + mse : float + Mean square error. + sample_size: int + Number of data points. + coef : 1d NumpyArray + Coefficients of linear regression. + + Returns + ---------- + bic : float + Calculated BIC. Smaller the better. + """ + + # from sklearn + # https://github.com/scikit-learn/scikit-learn/blob/0d378913b/sklearn/linear_model/_least_angle.py#L1957 + n_samples = len(y_est) + resid = y_est - y_true + mean_squared_error = np.mean(resid**2) + sigma2 = np.var(y_true) + eps64 = np.finfo("float64").eps + K = np.log(n_samples) + df = np.sum(np.abs(coef) > 0.0) + bic = n_samples * mean_squared_error / (sigma2 + eps64) + K * df + return bic + + +def determine_group_order(coef_history, dic_groups): + """ + Determine order of groups (from less important to important) + + Parameters + ---------- + coef_history: 2d NumpyArray + Regression coefficients. (len(penalty_facotrs) x X.shape[1]). + dic_groups : dict + A dictionary with group information + + Returns + ---------- + group_order : 1d NumpyArray + Order of groups, where less important is on the left. + """ + + group_order = [] + + num_groups = dic_groups["num_grps"] + num_penalties = coef_history.shape[0] + + sum_coef_per_groups_old = np.zeros(num_groups) + # add to group_order if coefficients shrink to zero + for i in range(num_penalties): + abs_coef = np.abs(coef_history[i, :]) + sum_coef_per_groups = np.bincount(dic_groups["idx_grps"], weights=abs_coef) + zero_coef_groups = np.where(sum_coef_per_groups == 0)[0] + new_zero_coef_groups = np.setdiff1d(zero_coef_groups, group_order) + ordered_new_zero_coef_groups = new_zero_coef_groups[np.argsort(sum_coef_per_groups_old[new_zero_coef_groups])] + group_order.extend(ordered_new_zero_coef_groups) + sum_coef_per_groups_old = sum_coef_per_groups + + # add remaining groups (order by sum of coefficients) + if len(group_order) < coef_history.shape[1]: + remain_groups = np.setdiff1d(np.arange(num_groups), group_order) + idx_sort_desc = np.argsort(sum_coef_per_groups[remain_groups])[::-1] + remain_groups = remain_groups[idx_sort_desc] + group_order.extend(remain_groups) + + return np.array(group_order)[::-1] + + +def fit_ridge(X, y, alpha=0.1): + """ + Fit ridge regression + + Parameters + ---------- + X : 2d NumpyArray + Explanatory variables. + y : 1d NumpyArray + Objective variable. + alpha: float + Regularization parameter for L2 + + Returns + ---------- + coef : 1d numpy array + regression coefficients. + """ + + model = Ridge(alpha=alpha) + model.fit(X, y) + coef = model.coef_ + return coef.flatten() + + +class GroupSankeyDataProcessor(): + def __init__(self, + coef, + dic_groups, + group_order, + color_y="lightgray", + color_link_positive='rgba(44, 160, 44, 0.4)', # green-like color + color_link_negative='rgba(214, 39, 40, 0.4)', # red-like color + limits_sensor={'xmin': 0.20, 'xmax': 0.00, 'ymin': 0.00, 'ymax': 1.00}, + limits_groups={'xmin': 0.75, 'xmax': 0.40, 'ymin': 0.00, 'ymax': 1.00}, + verbose=False + ): + + # group info and coefficients + self.dic_groups = dic_groups + self.dic_groups["group_order"] = group_order + self.coef_raw = coef + self.coef_grps = np.bincount(self.dic_groups["idx_grps"], weights=np.abs(coef)) + self.idx_grp_remained = np.where(np.abs(self.coef_grps) > 0.0)[0] + self.idx_col_remained = np.where(np.abs(coef) > 0.0)[0] + self.num_grp_remained = len(self.idx_grp_remained) + self.num_col_remained = len(self.idx_col_remained) + self.coef_remained = coef[self.idx_col_remained] + + # parameters for visualization + self.color_y = color_y + self.color_link_positive = color_link_positive + self.color_link_negative = color_link_negative + self.limits_sensor = limits_sensor + self.limits_groups = limits_groups + self.verbose = verbose + + def gen_dicts(self): + # generate dictionaries for skd and barchart + dic_skd = self._gen_sankey_data() + dic_bar = self._gen_barchart_data() + return dic_skd, dic_bar + + def _gen_sankey_data(self): + # Sankey data. node positions are determined by group order. + # dictionary for sankey + self.dic_skd = {'node_labels': np.hstack([self.dic_groups["colnames_x"][self.idx_col_remained], + self.dic_groups["uniq_grps"][self.idx_grp_remained], + self.dic_groups["colname_y"]]), + 'source': [], + 'target': [], + 'node_color': [], + 'edge_value': [], + 'edge_color': []} + + self._add_node_colors() + self._add_links_from_x_to_group() + self._add_links_from_group_to_y() + self._add_node_position() + return self.dic_skd + + def _gen_barchart_data(self): + # Barchart data. y-axis corresponds to sankey diagram. + ord_sort = np.argsort(self.dic_skd['node_y'][:self.num_col_remained]) + # ord_sort = np.concatenate([ord_sort[:np.sum(self.coef_raw == 0.0)], ord_sort[np.sum(self.coef_raw == 0.0):]]) + colors = [self.color_link_negative if x < 0 else self.color_link_positive for x in self.coef_remained[ord_sort]] + # from IPython.core.debugger import Pdb; Pdb().set_trace() + dic_bar = {"coef": self.coef_remained[ord_sort], + "sensor_names": self.dic_groups["colnames_x"][self.idx_col_remained][ord_sort], + "bar_colors": colors} + return dic_bar + + def _add_node_colors(self): + # Define node colors (x, groups, y) + palette = self._get_N_HexCol(len(self.dic_groups['uniq_grps'])) + for i in self.idx_col_remained: + self.dic_skd["node_color"].append(palette[self._sensor_id_to_group_id(i)]) + for i in self.idx_grp_remained: + self.dic_skd["node_color"].append(palette[i]) + self.dic_skd["node_color"].append(self.color_y) + + def _get_N_HexCol(self, N=5): + # Random color generator + # https://stackoverflow.com/questions/876853/generating-color-ranges-in-python + HSV_tuples = [(x * 1.0 / N, 0.5, 0.5) for x in range(N)] + hex_out = [] + for rgb in HSV_tuples: + rgb = map(lambda x: int(x * 255), colorsys.hsv_to_rgb(*rgb)) + hex_out.append('#%02x%02x%02x' % tuple(rgb)) + return hex_out + + def _add_links_from_x_to_group(self): + # Add links: x -> groups + edge_colors = [self.color_link_positive if x > 0 else self.color_link_negative for x in self.coef_remained] + for i in range(self.num_col_remained): + self.dic_skd['source'].append(i) + self.dic_skd['target'].append(self._sensor_node_id_to_group_node_id(i)) + self.dic_skd['edge_value'].append(np.abs(self.coef_remained[i])) + self.dic_skd['edge_color'].append(edge_colors[i]) + + def _add_links_from_group_to_y(self): + # Add links: groups -> y + for i in range(self.num_grp_remained): + self.dic_skd['source'].append(self.num_col_remained + i) + self.dic_skd['target'].append(self.num_col_remained + self.num_grp_remained) + self.dic_skd['edge_value'].append(self.coef_grps[self.idx_grp_remained[i]]) + self.dic_skd['edge_color'].append("#696969") + + def _sensor_id_to_group_id(self, sensor_id): + group_id = self.dic_groups["idx_grps"][sensor_id] + return int(group_id) + + def _sensor_id_to_sensor_node_id(self, sensor_id): + node_id = np.where(self.idx_col_remained == sensor_id)[0] + return int(node_id) + + def _sensor_node_id_to_group_node_id(self, node_id): + group_id = self.dic_groups["idx_grps"][self.idx_col_remained[node_id]] + node_id = self._group_id_to_group_node_id(group_id) + return int(node_id) + + def _group_id_to_group_node_id(self, group_id): + node_id = self.num_col_remained + np.where(self.idx_grp_remained == group_id)[0] + return int(node_id) + + def _add_node_position(self): + # Add node positon + # Node position of groups are generated according to selection process of GroupLASSO. + # What makes complicated is that, we have to create a list of length(number of nodes shown in graph). + # nodes shown/not shown is determined by edge values. + num_nodes = len(self.dic_skd['node_labels']) + node_x = np.array([np.nan] * num_nodes) + node_y = np.array([np.nan] * num_nodes) + + # generate node positions: groups + xvals_grp = np.linspace(self.limits_groups["xmin"], self.limits_groups["xmax"], self.num_grp_remained) + wt_groups = self.coef_grps + wt_sensor = np.abs(self.coef_raw) + + groups_y = self.limits_groups["ymin"] + sensor_y = self.limits_sensor["ymin"] + cnt_grp = 0 + # from IPython.core.debugger import Pdb; Pdb().set_trace() + # position of group nodes + for grp_idx in self.dic_groups["group_order"]: + + if grp_idx not in self.idx_grp_remained: + continue + + grp_name = self.dic_groups["uniq_grps"][grp_idx] + node_id = self._group_id_to_group_node_id(grp_idx) + node_label = self.dic_skd["node_labels"][node_id] + wt = np.max([0.05, wt_groups[grp_idx]]) + + node_x[node_id] = xvals_grp[cnt_grp] + node_y[node_id] = groups_y + (wt / 2) + groups_y += wt + + # position of sensor nodes + idx_sensors_in_group = self.idx_col_remained[self.dic_groups['idx_grps'][self.idx_col_remained] == grp_idx] + for j in idx_sensors_in_group: + wt = wt_sensor[j] + if self.verbose: + print("j={}, name={}, wt={}".format(j, self.dic_skd["node_labels"][j], wt)) + node_x[self._sensor_id_to_sensor_node_id(j)] = 0.05 + node_y[self._sensor_id_to_sensor_node_id(j)] = sensor_y + (wt / 2) + sensor_y += wt + cnt_grp += 1 + + # normalize y positions + node_groups_on_graph = [self._group_id_to_group_node_id(x) for x in self.idx_grp_remained] + node_sensor_on_graph = [self._sensor_id_to_sensor_node_id(x) for x in self.idx_col_remained] + node_y[node_groups_on_graph] = node_y[node_groups_on_graph] / np.max(node_y[node_groups_on_graph]) + node_y[node_sensor_on_graph] = node_y[node_sensor_on_graph] / np.max(node_y[node_sensor_on_graph]) + if self.num_grp_remained == 1: + node_y[node_groups_on_graph] = 0.5 + node_x[node_groups_on_graph] = 0.5 + + # position of objective variable + node_x[-1] = 0.90 + node_y[-1] = 0.50 + self.dic_skd['node_x'] = node_x + self.dic_skd['node_y'] = node_y + + if self.verbose: + print('num_nodes: {}'.format(num_nodes)) + print("Node x, y positions in Skd:\n{}".format( + np.vstack([self.dic_skd["node_labels"], + np.round(node_x, 2), + np.round(node_y, 2)]).T)) + diff --git a/histview2/api/sankey_plot/sankey_glasso/sankey_services.py b/histview2/api/sankey_plot/sankey_glasso/sankey_services.py new file mode 100644 index 0000000..88a843d --- /dev/null +++ b/histview2/api/sankey_plot/sankey_glasso/sankey_services.py @@ -0,0 +1,287 @@ +# GraphicalLASSO is implemented in glasso.py +# Let us define 2 more functions here +from collections import defaultdict + +import numpy as np +import pandas as pd + +from histview2.api.sankey_plot.sankey_glasso import glasso +from histview2.api.sankey_plot.sankey_glasso.grplasso import preprocess_skdpage +from histview2.api.trace_data.services.time_series_chart import get_procs_in_dic_param, get_data_from_db, \ + main_check_filter_detail_match_graph_data +from histview2.common.common_utils import gen_sql_label, zero_variance +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.memoize import memoize +from histview2.common.services.form_env import bind_dic_param_to_class +from histview2.common.trace_data_log import TraceErrKey, EventType, EventAction, Target, trace_log +from histview2.setting_module.models import CfgProcessColumn + +colors = [ + '#ec654a', '#915558', '#02c39a', '#c3b281', + '#f15bb5', '#e3c20b', '#ab2f1e', '#024381', + '#750704', '#0d639d', '#db1168', '#4895ef', + '#00f5d4', '#037003', '#510ca0', '#ff7aa2', + '#985d02', '#642902', '#70e000', '#9c5fea', +] + + +@log_execution_time() +def gen_sankeydata_from_adj(adj_mat): + ''' + Helper function to generate source/target/value/color lists from adjacency matrix + + Inputs: + ---------- + adj_mat: NumpyArray of shape (num of sensors, num of sensors) + Adjacency matrix. This matrix is strictly upper triangular, and + (j, i) th element indicate partial correlation between (j, i) + + Returns: + ---------- + see preprocess_for_sankey_glasso() + ''' + + # define colors for positive/negative correlation + color_positive = 'rgba(44, 160, 44, 0.2)' # green-like color + color_negative = 'rgba(214, 39, 40, 0.2)' # red-like color + + d = adj_mat.shape[0] + source = [] + target = [] + value = [] + color = [] + for i in range(d): + for j in range(d): + source.append(i) + target.append(j) + value.append(np.abs(adj_mat[j, i])) # sankey can not handle negative valued links + color.append(color_positive if adj_mat[j, i] > 0 else color_negative) + return source, target, value, color + + +@log_execution_time() +def preprocess_for_sankey_glasso(X, idx_target, num_layers=2): + ''' + Preprocessing for Sankey Diagrams + Generate source/target/value/color lists from given sensor data + + Inputs: + ---------- + X: NumpyArray or pandas dataframe of shape (num of records, num of sensors) + sensor data + + idx_target: list of integers + column index of target sensor(s) + + num_layers: integer of greater than 0. default=1 + maximum number of paths from target column(s) + + Returns: + ---------- + lists of integers to pass to sankey diagram (sankey diagram) + source, target, value, color + ''' + + # generate instance and fit glasso (large alpha returns more sparse result) + ggm = glasso.GaussianGraphicalModel() + ggm.fit(X, idx_target) + + # extract column index of (target/direct/indirect) sensors, + # and remove unnecessary edges + adj_mat = glasso.remove_unnecessary_edges(ggm.parcor, idx_target, num_layers) + + # convert data to pass to sankey + source, target, value, color = gen_sankeydata_from_adj(adj_mat) + return source, target, value, color + + +@log_execution_time() +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.SKD, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_graph_sankey_group_lasso(dic_param): + """tracing data to show graph + 1 start point x n end point + filter by condition point + https://files.slack.com/files-pri/TJHPR9BN3-F01GG67J84C/image.pngnts that between start point and end_point + """ + # bind dic_param + graph_param = bind_dic_param_to_class(dic_param) + + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + # add start proc + graph_param.add_start_proc_to_array_formval() + + # add category + graph_param.add_cate_procs_to_array_formval() + + # add condition procs + graph_param.add_cond_procs_to_array_formval() + + # get serials + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + serial_ids = [serial.id for serial in proc_cfg.get_serials(column_name_only=False)] + proc.add_cols(serial_ids) + + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = main_check_filter_detail_match_graph_data( + graph_param, df) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] = matched_filter_ids + dic_param[UNMATCHED_FILTER_IDS] = unmatched_filter_ids + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = not_exact_match_filter_ids + + # sensors + dic_param['plotly_data'] = gen_plotly_data(dic_skd={}, dic_bar={}) + if not df.empty: + orig_graph_param = bind_dic_param_to_class(dic_param) + dic_label_id, dic_id_name, dic_col_proc_id = get_sensors_objective_explanation(orig_graph_param) + df_sensors: pd.DataFrame = df[dic_label_id] + df_sensors = df_sensors.rename(columns=dic_label_id) + df_sensors, data_clean, errors = clean_input_data(df_sensors) + if data_clean and not errors: + # prepare column names and process names + y_id = graph_param.common.objective_var + y_col = (y_id, dic_id_name[y_id]) + x_cols = {key: val for key, val in dic_id_name.items() if key != y_id} + groups = [dic_proc_cfgs.get(proc_id).name for key, proc_id in dic_col_proc_id.items() if key != y_id] + + dic_skd, dic_bar = gen_sankey_grouplasso_plot_data(df_sensors, x_cols, y_col, groups) + dic_param['plotly_data'] = gen_plotly_data(dic_skd, dic_bar) + if errors: + dic_param['errors'] = errors + + dic_param[DATA_SIZE] = df.memory_usage(deep=True).sum() + dic_param[IS_RES_LIMITED] = is_res_limited + dic_param[ACTUAL_RECORD_NUMBER] = actual_record_number + + return dic_param + + +def gen_sensor_headers(orig_graph_param): + target_sensor_ids = [] + for proc in orig_graph_param.array_formval: + target_sensor_ids.extend(proc.col_ids) + return target_sensor_ids + + +@log_execution_time() +def clean_input_data(df: pd.DataFrame): + df = df.replace(dict.fromkeys([np.inf, -np.inf, np.nan], np.nan)).dropna(how='any') + print('shape: {}'.format(df.shape)) + data_clean = True + errors = [] + if zero_variance(df): + data_clean = False + errors.append(ErrorMsg.E_ZERO_VARIANCE.name) + if df.empty: + data_clean = False + errors.append(ErrorMsg.E_ALL_NA.name) + + return df, data_clean, errors + + +@log_execution_time() +def gen_sankey_grouplasso_plot_data(df: pd.DataFrame, x_cols, y_col, groups): + # names + y_col_id, y_col_name = y_col + x_col_names = np.array(list(x_cols.values())) + + # Inputs + x_2d = df[x_cols].values + y_1d = df[[y_col_id]].values + + # please set verbose=False if info should not be printed + dic_skd, dic_bar = preprocess_skdpage(x_2d, y_1d, groups, x_col_names, y_col_name, + penalty_factors=[0.0, 0.1, 0.3, 1.0], + max_datapoints=10000, + verbose=True) + + return dic_skd, dic_bar + + +def gen_plotly_data(dic_skd: dict, dic_bar: dict): + return dict( + sankey_trace=plot_sankey_grplasso(defaultdict(list, dic_skd)), + bar_trace=plot_barchart_grplasso(defaultdict(list, dic_bar)), + ) + + +def plot_sankey_grplasso(dic_skd: defaultdict): + sankey_trace = dict(arrangement="snap", + node=dict( + pad=20, + thickness=20, + label=dic_skd["node_labels"], + color=dic_skd["node_color"], + x=dic_skd["node_x"], + y=dic_skd["node_y"] + ), + link=dict( + source=dic_skd["source"], + target=dic_skd["target"], + value=dic_skd["edge_value"], + color=dic_skd["edge_color"] + )) + return sankey_trace + + +def plot_barchart_grplasso(dic_bar: defaultdict): + bar_trace = dict( + y=dic_bar["sensor_names"], + x=np.abs(dic_bar["coef"]), + name=None, + orientation="h", + marker_color=dic_bar["bar_colors"], + hovertemplate="%{text}", + text=np.round(dic_bar["coef"], 5) + ) + return bar_trace + + +@log_execution_time() +def gen_sankey_plot_data(x: pd.DataFrame, idx_tgt, num_layers, dic_label_id, dic_proc_cfgs, target_proc): + # preprocess + source, target, value, color = preprocess_for_sankey_glasso(x, idx_tgt, num_layers) + + # sensor names are also required for sankey diagram + col_ids = [dic_label_id.get(c) for c in x.columns.values] + cols = CfgProcessColumn.get_by_ids(col_ids) or [] + dic_cols = {col.id: '{} | {}'.format(dic_proc_cfgs.get(col.process_id).name, col.name) for col in cols} + node_labels = [dic_cols.get(col_id) for col_id in col_ids] + dic_proc_color = {} + + for idx, proc_id in enumerate(dic_proc_cfgs.keys()): + dic_proc_color[proc_id] = SKD_TARGET_PROC_CLR if (proc_id == target_proc) else colors[idx % len(colors)] + dic_col_color = {col.id: dic_proc_color.get(col.process_id) for col in cols} + node_colors = [dic_col_color.get(col_id) for col_id in col_ids] + + return { + 'source': source, + 'target': target, + 'value': value, + 'color': color, + 'node_labels': node_labels, + 'node_colors': node_colors, + } + + +def get_sensors_objective_explanation(orig_graph_param): + dic_label_id = {} + dic_id_name = {} + dic_col_proc_id = {} + for proc in orig_graph_param.array_formval: + for col_id, col_name in zip(proc.col_ids, proc.col_names): + label = gen_sql_label(col_id, col_name) + dic_label_id[label] = col_id + dic_id_name[col_id] = col_name + dic_col_proc_id[col_id] = proc.proc_id + + return dic_label_id, dic_id_name, dic_col_proc_id diff --git a/histview2/api/sankey_plot/services.py b/histview2/api/sankey_plot/services.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/api/scatter_plot/controllers.py b/histview2/api/scatter_plot/controllers.py new file mode 100644 index 0000000..80b5448 --- /dev/null +++ b/histview2/api/scatter_plot/controllers.py @@ -0,0 +1,55 @@ +import timeit + +import simplejson +from flask import Blueprint, request + +from histview2.api.scatter_plot.services import gen_scatter_plot +from histview2.common.pysize import get_size +from histview2.common.services import http_content +from histview2.common.services.form_env import parse_multi_filter_into_one +from histview2.common.services.import_export_config_n_data import get_dic_form_from_debug_info, \ + set_export_dataset_id_to_dic_param +from histview2.common.trace_data_log import is_send_google_analytics, save_input_data_to_file, EventType + +api_scatter_blueprint = Blueprint( + 'api_scatter_module', + __name__, + url_prefix='/histview2/api/scp' +) + + +@api_scatter_blueprint.route('/plot', methods=['POST']) +def trace_data(): + """ + Trace Data API + return dictionary + """ + start = timeit.default_timer() + dic_form = request.form.to_dict(flat=False) + save_input_data_to_file(dic_form, EventType.SCP) + dic_param = parse_multi_filter_into_one(dic_form) + + # check if we run debug mode (import mode) + dic_param = get_dic_form_from_debug_info(dic_param) + + # if universal call gen_dframe else gen_results + orig_send_ga_flg = is_send_google_analytics + dic_param = gen_scatter_plot(dic_param) + + # send Google Analytics changed flag + if orig_send_ga_flg and not is_send_google_analytics: + dic_param.update({'is_send_ga_off': True}) + + # calculate data size to send gtag + data_size = get_size(dic_param) + dic_param['data_size'] = data_size + + stop = timeit.default_timer() + dic_param['backend_time'] = stop - start + + # export mode ( output for export mode ) + set_export_dataset_id_to_dic_param(dic_param) + + out_dict = simplejson.dumps(dic_param, ensure_ascii=False, default=http_content.json_serial, ignore_nan=True) + + return out_dict, 200 diff --git a/histview2/api/scatter_plot/services.py b/histview2/api/scatter_plot/services.py new file mode 100644 index 0000000..ae9b378 --- /dev/null +++ b/histview2/api/scatter_plot/services.py @@ -0,0 +1,1101 @@ +import math +import re +from collections import Counter +from copy import deepcopy +from typing import List + +import numpy as np +import pandas as pd +from numpy import matrix +from pandas import Series, RangeIndex, Index + +from histview2.api.categorical_plot.services import produce_cyclic_terms, gen_dic_param_terms, gen_time_conditions +from histview2.api.trace_data.services.time_series_chart import (get_data_from_db, + main_check_filter_detail_match_graph_data, + calc_raw_common_scale_y, + calc_scale_info, get_procs_in_dic_param, + gen_unique_data, filter_df, + customize_dic_param_for_reuse_cache, + get_chart_info_detail) +from histview2.common.common_utils import gen_sql_label +from histview2.common.constants import ACTUAL_RECORD_NUMBER, \ + IS_RES_LIMITED, ARRAY_Y, MATCHED_FILTER_IDS, UNMATCHED_FILTER_IDS, NOT_EXACT_MATCH_FILTER_IDS, ARRAY_X, \ + TIMES, COLORS, H_LABEL, V_LABEL, DataType, CHART_TYPE, CYCLIC_DIV_NUM, COMMON, START_DATE, \ + START_TM, END_DATE, END_TM, ELAPSED_TIME, ARRAY_Z, ChartType, SCALE_COLOR, END_COL_ID, END_PROC_ID, \ + SCALE_COMMON, SCALE_THRESHOLD, SCALE_AUTO, SCALE_FULL, SCALE_Y, SCALE_X, TIME_MIN, TIME_MAX, \ + ORIG_ARRAY_Z, SUMMARIES, N_TOTAL, UNIQUE_CATEGORIES, UNIQUE_DIV, UNIQUE_COLOR, CAT_EXP_BOX, X_THRESHOLD, \ + Y_THRESHOLD, SCALE_SETTING, \ + CHART_INFOS, X_SERIAL, Y_SERIAL, ARRAY_PLOTDATA, IS_DATA_LIMITED, ColorOrder, TIME_NUMBERINGS, SORT_KEY, \ + VAR_TRACE_TIME +from histview2.common.memoize import memoize +from histview2.common.services.form_env import bind_dic_param_to_class +from histview2.common.services.sse import notify_progress +from histview2.common.services.statistics import calc_summary_elements +from histview2.common.trace_data_log import * +from histview2.setting_module.models import CfgProcessColumn +from histview2.trace_data.models import Cycle + +DATA_COUNT_COL = '__data_count_col__' +MATRIX = 7 +SCATTER_PLOT_MAX_POINT = 500_000 +HEATMAP_COL_ROW = 100 +TOTAL_VIOLIN_PLOT = 200 + + +@log_execution_time('[SCATTER PLOT]') +@notify_progress(60) +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.SCP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_scatter_plot(dic_param): + """tracing data to show graph + 1 start point x n end point + filter by condition points that between start point and end_point + """ + recent_flg = False + for key in dic_param[COMMON]: + if str(key).startswith(VAR_TRACE_TIME): + if dic_param[COMMON][key] == 'recent': + recent_flg = True + break + + is_data_limited = False + # for caching + dic_param, cat_exp, _, dic_cat_filters, use_expired_cache, temp_serial_column, temp_serial_order, *_, matrix_col, \ + color_order = customize_dic_param_for_reuse_cache(dic_param) + matrix_col = matrix_col if matrix_col else MATRIX + + # cyclic + terms = None + if dic_param[COMMON].get(CYCLIC_DIV_NUM): + produce_cyclic_terms(dic_param) + terms = gen_dic_param_terms(dic_param) + + # get x,y,color, levels, cat_div information + orig_graph_param = bind_dic_param_to_class(dic_param) + threshold_filter_detail_ids = orig_graph_param.common.threshold_boxes + dic_proc_cfgs = get_procs_in_dic_param(orig_graph_param) + scatter_xy_ids = [] + scatter_xy_names = [] + scatter_proc_ids = [] + for proc in orig_graph_param.array_formval: + scatter_proc_ids.append(proc.proc_id) + scatter_xy_ids = scatter_xy_ids + proc.col_ids + scatter_xy_names = scatter_xy_names + proc.col_names + + x_proc_id = scatter_proc_ids[0] + y_proc_id = scatter_proc_ids[-1] + x_id = scatter_xy_ids[0] + y_id = scatter_xy_ids[-1] + x_name = scatter_xy_names[0] + y_name = scatter_xy_names[-1] + x_label = gen_sql_label(x_id, x_name) + y_label = gen_sql_label(y_id, y_name) + + color_id = orig_graph_param.common.color_var + cat_div_id = orig_graph_param.common.div_by_cat + level_ids = cat_exp if cat_exp else orig_graph_param.common.cat_exp + col_ids = [col for col in list(set([x_id, y_id, color_id, cat_div_id] + level_ids)) if col] + dic_cols = {cfg_col.id: cfg_col for cfg_col in CfgProcessColumn.get_by_ids(col_ids)} + + color_label = gen_sql_label(color_id, dic_cols[color_id].column_name) if color_id else None + level_labels = [gen_sql_label(id, dic_cols[id].column_name) for id in level_ids] + cat_div_label = gen_sql_label(cat_div_id, dic_cols[cat_div_id].column_name) if cat_div_id else None + if orig_graph_param.common.compare_type == 'directTerm': + matched_filter_ids = [] + unmatched_filter_ids = [] + not_exact_match_filter_ids = [] + actual_record_number = 0 + is_res_limited = False + + dic_dfs = {} + terms = gen_time_conditions(dic_param) + df = None + for term in terms: + # create dic_param for each term from original dic_param + term_dic_param = deepcopy(dic_param) + term_dic_param[COMMON][START_DATE] = term[START_DATE] + term_dic_param[COMMON][START_TM] = term[START_TM] + term_dic_param[COMMON][END_DATE] = term[END_DATE] + term_dic_param[COMMON][END_TM] = term[END_TM] + h_keys = (term[START_DATE], term[START_TM], term[END_DATE], term[END_TM]) + + # query data and gen df + df_term, graph_param, record_number, _is_res_limited = gen_df(term_dic_param, + _use_expired_cache=use_expired_cache) + if df is None: + df = df_term.copy() + else: + df = pd.concat([df, df_term]) + + # filter list + df_term = filter_df(df_term, dic_cat_filters) + + if _is_res_limited: + is_res_limited = _is_res_limited + + # check filter match or not ( for GUI show ) + filter_ids = main_check_filter_detail_match_graph_data(graph_param, df_term) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids, actual records + actual_record_number += record_number + matched_filter_ids += filter_ids[0] + unmatched_filter_ids += filter_ids[1] + not_exact_match_filter_ids += filter_ids[2] + + dic_dfs[h_keys] = df_term + + # gen scatters + output_graphs, output_times = gen_scatter_by_direct_term(matrix_col, dic_dfs, x_proc_id, y_proc_id, x_label, + y_label, color_label, level_labels) + else: + # query data and gen df + df, graph_param, actual_record_number, is_res_limited = gen_df(dic_param, + _use_expired_cache=use_expired_cache) + + # filter list + df_sub = filter_df(df, dic_cat_filters) + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = \ + main_check_filter_detail_match_graph_data(graph_param, df_sub) + + if orig_graph_param.common.div_by_data_number: + output_graphs, output_times = gen_scatter_data_count(matrix_col, df_sub, x_proc_id, y_proc_id, x_label, + y_label, orig_graph_param.common.div_by_data_number, + color_label, level_labels, recent_flg) + elif orig_graph_param.common.cyclic_div_num: + output_graphs, output_times = gen_scatter_by_cyclic(matrix_col, df_sub, x_proc_id, y_proc_id, x_label, + y_label, terms, color_label, level_labels) + else: + output_graphs, output_times = gen_scatter_cat_div(matrix_col, df_sub, x_proc_id, y_proc_id, x_label, + y_label, cat_div_label, color_label, level_labels) + + # check graphs + if not output_graphs: + return dic_param + + # chart type + series_keys = [ARRAY_X, ARRAY_Y, COLORS, TIMES] + chart_type = get_chart_type(x_id, y_id, dic_cols) + dic_param[CHART_TYPE] = chart_type + if chart_type == ChartType.HEATMAP.value: + # get unique data + dic_unique_cate = gen_unique_data(df, dic_proc_cfgs, [id for id in (x_id, y_id) if id]) + + # get color for filter + dic_unique_color = gen_unique_data(df, dic_proc_cfgs, []) + + # get div for filter + dic_unique_div = gen_unique_data(df, dic_proc_cfgs, [id for id in [cat_div_id] if id]) + + # gen matrix + all_x, all_y = get_heatmap_distinct(output_graphs) + for graph in output_graphs: + # handle x, y, z data + array_x = graph[ARRAY_X] + array_y = graph[ARRAY_Y] + unique_x = set(array_x.drop_duplicates().tolist()) + unique_y = set(array_y.drop_duplicates().tolist()) + + missing_x = all_x - unique_x + missing_y = all_y - unique_y + array_z = pd.crosstab(array_y, array_x) + for key in missing_x: + array_z[key] = None + + sorted_cols = sorted(array_z.columns) + array_z = array_z[sorted_cols] + + missing_data = [None] * len(missing_y) + df_missing = pd.DataFrame({col: missing_data for col in array_z.columns}, index=missing_y) + array_z = pd.concat([array_z, df_missing]) + array_z.sort_index(inplace=True) + + # limit 10K cells + if array_z.size > HEATMAP_COL_ROW * HEATMAP_COL_ROW: + array_z = array_z[:HEATMAP_COL_ROW][array_z.columns[:HEATMAP_COL_ROW]] + + graph[ARRAY_X] = array_z.columns + graph[ARRAY_Y] = array_z.index + graph[ORIG_ARRAY_Z] = matrix(array_z) + + # ratio + z_count = len(array_x) + array_z = array_z * 100 // z_count + graph[ARRAY_Z] = matrix(array_z) + + # reduce sending data to browser + graph[COLORS] = [] + graph[X_SERIAL] = [] + graph[Y_SERIAL] = [] + graph[TIMES] = [] + graph[ELAPSED_TIME] = [] + + elif chart_type == ChartType.SCATTER.value: + # get unique data + dic_unique_cate = gen_unique_data(df, dic_proc_cfgs, []) + + # get color for filter + dic_unique_color = gen_unique_data(df, dic_proc_cfgs, [id for id in {color_id} if id]) + + # get div for filter + dic_unique_div = gen_unique_data(df, dic_proc_cfgs, [id for id in {cat_div_id} if id]) + + # gen scatter matrix + data_per_graph = SCATTER_PLOT_MAX_POINT / len(output_graphs) + color_scale = [] + for graph, (x_times, y_times) in zip(output_graphs, output_times): + # limit and sort by color + df_graph, _is_data_limited = gen_df_limit_data(graph, series_keys, data_per_graph) + if _is_data_limited: + is_data_limited = True + + # sort by color (high frequency first) + color_col = ELAPSED_TIME + df_graph[color_col] = calc_elapsed_times(df_graph, TIMES) + if color_order is ColorOrder.DATA: + color_col = COLORS if COLORS in df_graph else ARRAY_X + df_graph = sort_df(df_graph, [color_col]) + elif color_order is ColorOrder.TIME: + color_col = TIME_NUMBERINGS + df_graph[color_col] = pd.to_datetime(df_graph[TIMES]).rank().convert_dtypes() + + color_scale += df_graph[color_col].tolist() + + # group by and count frequency + df_graph['__count__'] = df_graph.groupby(color_col)[color_col].transform('count') + df_graph.sort_values('__count__', inplace=True, ascending=False) + df_graph.drop('__count__', axis=1, inplace=True) + + for key in df_graph.columns: + graph[key] = df_graph[key].tolist() + + # chart infos + x_chart_infos, _ = get_chart_info_detail(x_times or graph[TIMES], x_id, threshold_filter_detail_ids) + graph[X_THRESHOLD] = x_chart_infos[-1] if x_chart_infos else None + y_chart_infos, _ = get_chart_info_detail(y_times or graph[TIMES], y_id, threshold_filter_detail_ids) + graph[Y_THRESHOLD] = y_chart_infos[-1] if y_chart_infos else None + else: + group_by_cols = [] + unique_data_cols = [cat_div_id, color_id] + if DataType[dic_cols[x_id].data_type] is DataType.TEXT: + str_col = ARRAY_X + number_col = ARRAY_Y + group_by_cols.append(str_col) + unique_data_cols.append(x_id) + dic_param['string_axis'] = 'x' + if color_id and color_id != x_id: + group_by_cols.append(COLORS) + else: + str_col = ARRAY_Y + number_col = ARRAY_X + group_by_cols.append(str_col) + unique_data_cols.append(x_id) + dic_param['string_axis'] = 'y' + if color_id and color_id != y_id: + group_by_cols.append(COLORS) + + number_of_graph = min(len(output_graphs), matrix_col ** 2) or 1 + limit_violin_per_graph = math.floor(TOTAL_VIOLIN_PLOT / number_of_graph) + most_vals, is_reduce_violin_number = get_most_common_in_graphs(output_graphs, group_by_cols, + limit_violin_per_graph) + number_of_violin = (len(most_vals) * number_of_graph) or 1 + max_n_per_violin = math.floor(10_000 / number_of_violin) + + # for show message reduced number of violin chart + dic_param['is_reduce_violin_number'] = is_reduce_violin_number + + # get unique data + dic_unique_cate = gen_unique_data(df, dic_proc_cfgs, []) + + # get color for filter + dic_unique_color = gen_unique_data(df, dic_proc_cfgs, [id for id in {color_id} if id]) + + # get div for filter + dic_unique_div = gen_unique_data(df, dic_proc_cfgs, [id for id in {cat_div_id} if id]) + + # gen violin data + for graph, (x_times, y_times) in zip(output_graphs, output_times): + # limit and sort by color + df_graph, _is_data_limited = gen_df_limit_data(graph, series_keys) + if _is_data_limited: + is_data_limited = True + + df_graph = filter_violin_df(df_graph, group_by_cols, most_vals) + df_graph = sort_df(df_graph, group_by_cols) + + # get hover information + dic_summaries = {} + str_col_vals = [] + num_col_vals = [] + for key, df_sub in df_graph.groupby(group_by_cols): + if isinstance(key, (list, tuple)): + key = '|'.join(key) + + vals = df_sub[number_col].tolist() + dic_summaries[key] = calc_summary_elements( + {ARRAY_X: df_sub[TIMES].tolist(), ARRAY_Y: vals}) + + # resample_data = df_sub[number_col] + # resample_data = resample_by_sort(df_sub[number_col], max_n_per_violin) + resample_data = resample_preserve_min_med_max(df_sub[number_col], max_n_per_violin) + # todo: remove q2 computing after demonstration + # if df_sub[number_col].size: + # q2_raw_data = np.quantile(df_sub[number_col], [0.5]) + # q2_new_data = np.quantile(resample_data, [0.5]) + # q2_old_data = np.quantile(resample_data_old, [0.5]) + + if resample_data is not None: + vals = resample_data.tolist() + is_data_limited = True + + str_col_vals.append(key) + num_col_vals.append(vals) + + graph[str_col] = str_col_vals + graph[number_col] = num_col_vals + graph[SUMMARIES] = dic_summaries + + # reduce sending data to browser + graph[COLORS] = [] + graph[X_SERIAL] = [] + graph[Y_SERIAL] = [] + graph[TIMES] = [] + graph[ELAPSED_TIME] = [] + + if number_col == ARRAY_X: + x_chart_infos, _ = get_chart_info_detail(x_times or graph[TIMES], x_id, threshold_filter_detail_ids) + graph[X_THRESHOLD] = x_chart_infos[-1] if x_chart_infos else None + else: + y_chart_infos, _ = get_chart_info_detail(y_times or graph[TIMES], y_id, threshold_filter_detail_ids) + graph[Y_THRESHOLD] = y_chart_infos[-1] if y_chart_infos else None + + # TODO : we should calc box plot and kde before send to front end to improve performance + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] = matched_filter_ids + dic_param[UNMATCHED_FILTER_IDS] = unmatched_filter_ids + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = not_exact_match_filter_ids + + # flag to show that trace result was limited + dic_param[ACTUAL_RECORD_NUMBER] = actual_record_number + dic_param[IS_RES_LIMITED] = is_res_limited + + # check show col and row labels + is_show_h_label = False + is_show_v_label = False + is_show_first_h_label = False + v_labels = list({graph[V_LABEL] for graph in output_graphs}) + h_labels = list({graph[H_LABEL] for graph in output_graphs}) + if len(h_labels) > 1 or (h_labels and h_labels[0]): + is_show_h_label = True + + if len(output_graphs) > len(h_labels): + is_show_first_h_label = True + + if len(v_labels) > 1 or (v_labels and v_labels[0]): + is_show_v_label = True + + # column names + dic_param['x_name'] = dic_cols[x_id].name if x_id else None + dic_param['y_name'] = dic_cols[y_id].name if y_id else None + dic_param['color_name'] = dic_cols[color_id].name if color_id else None + dic_param['color_type'] = dic_cols[color_id].data_type if color_id else None + dic_param['div_name'] = dic_cols[cat_div_id].name if cat_div_id else None + dic_param['div_data_type'] = dic_cols[cat_div_id].data_type if cat_div_id else None + dic_param['level_names'] = [dic_cols[level_id].name for level_id in level_ids] if level_ids else None + dic_param['is_show_v_label'] = is_show_v_label + dic_param['is_show_h_label'] = is_show_h_label + dic_param['is_show_first_h_label'] = is_show_first_h_label + dic_param['is_filtered'] = True if dic_cat_filters else False + + # add proc name for x and y column + dic_param['x_proc'] = dic_proc_cfgs[dic_cols[x_id].process_id].name if x_id else None + dic_param['y_proc'] = dic_proc_cfgs[dic_cols[y_id].process_id].name if y_id else None + + # min, max color + # TODO: maybe we need to get chart infor for color to get ymax ymin of all chart infos + if color_order is ColorOrder.DATA: + dic_scale_color = calc_scale(df, color_id, color_label, dic_cols) + else: + df_color_scale = pd.DataFrame({color_col: color_scale}) + dic_scale_color = calc_scale(df_color_scale, None, color_col, dic_cols) + + dic_param[SCALE_COLOR] = dic_scale_color + + # y scale + y_chart_configs = [graph[Y_THRESHOLD] for graph in output_graphs if graph.get(Y_THRESHOLD)] + dic_scale_y = calc_scale(df, y_id, y_label, dic_cols, y_chart_configs) + dic_param[SCALE_Y] = dic_scale_y + + # x scale + x_chart_configs = [graph[X_THRESHOLD] for graph in output_graphs if graph.get(X_THRESHOLD)] + dic_scale_x = calc_scale(df, x_id, x_label, dic_cols, x_chart_configs) + dic_param[SCALE_X] = dic_scale_x + + # output graphs + dic_param[ARRAY_PLOTDATA] = [convert_series_to_list(graph) for graph in output_graphs] + + dic_cat_exp_unique = gen_unique_data(df, dic_proc_cfgs, level_ids) + dic_param[CAT_EXP_BOX] = list(dic_cat_exp_unique.values()) + dic_param[UNIQUE_CATEGORIES] = list(dic_unique_cate.values()) + dic_param[UNIQUE_DIV] = list(dic_unique_div.values()) + dic_param[UNIQUE_COLOR] = list(dic_unique_color.values()) + dic_param[IS_DATA_LIMITED] = is_data_limited + return dic_param + + +@log_execution_time() +def calc_scale(df, col_id, col_label, dic_cols, chart_configs=None): + if not col_id and not col_label: + return None + + if col_id: + cfg_col = dic_cols.get(col_id) + if not cfg_col: + return None + + if df is None or not len(df): + return None + + if DataType[cfg_col.data_type] not in (DataType.REAL, DataType.INTEGER): + return None + + plot = {END_PROC_ID: cfg_col.process_id, END_COL_ID: col_id, ARRAY_X: df[Cycle.time.key], + ARRAY_Y: df[col_label]} + else: + plot = {END_PROC_ID: None, END_COL_ID: None, ARRAY_X: [None], + ARRAY_Y: df[col_label]} + + if chart_configs: + plot[CHART_INFOS] = chart_configs + + min_max_list, all_min, all_max = calc_raw_common_scale_y([plot]) + calc_scale_info([plot], min_max_list, all_min, all_max) + + dic_scale = {scale_name: plot.get(scale_name) for scale_name in + (SCALE_SETTING, SCALE_COMMON, SCALE_THRESHOLD, SCALE_AUTO, SCALE_FULL)} + return dic_scale + + +@log_execution_time() +def convert_series_to_list(graph): + for key, series in graph.items(): + if isinstance(series, (Series, np.ndarray, RangeIndex, Index)): + graph[key] = series.tolist() + + return graph + + +@log_execution_time() +@memoize(is_save_file=True) +def gen_df(dic_param, _use_expired_cache=False): + # bind dic_param + graph_param = bind_dic_param_to_class(dic_param) + + # target procs + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + # add start proc + graph_param.add_start_proc_to_array_formval() + # add condition procs + graph_param.add_cond_procs_to_array_formval() + # add level + graph_param.add_cat_exp_to_array_formval() + # add color, cat_div + graph_param.add_column_to_array_formval([graph_param.common.color_var, graph_param.common.div_by_cat]) + + # get serials + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + serial_ids = [serial.id for serial in proc_cfg.get_serials(column_name_only=False)] + proc.add_cols(serial_ids) + + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + return df, graph_param, actual_record_number, is_res_limited + + +@log_execution_time() +def get_chart_type(x_id, y_id, dic_cols): + number_types = (DataType.INTEGER, DataType.REAL) + x_type = DataType[dic_cols[x_id].data_type] + y_type = DataType[dic_cols[y_id].data_type] + if x_type in number_types and y_type in number_types: + return ChartType.SCATTER.value + elif x_type is DataType.TEXT and y_type is DataType.TEXT: + return ChartType.HEATMAP.value + else: + return ChartType.VIOLIN.value + + +@log_execution_time() +def split_data_by_number(df: DataFrame, count): + df[DATA_COUNT_COL] = df.reset_index().index // count + return df + + +def sort_data_count_key(key): + new_key = re.match(r'^\d+', str(key)) + if new_key is not None: + return int(new_key[0]) + + return key + + +@log_execution_time() +def group_by_df(df: DataFrame, cols, max_group=None, max_record_per_group=None, sort_key_func=None, reverse=True, + get_from_last=None): + dic_groups = {} + if not len(df): + return dic_groups + + if not cols: + dic_groups[None] = df.head(max_record_per_group) + return dic_groups + + df_groups = df.groupby(cols) + max_group = max_group or len(df_groups.groups) + + # sort desc + if sort_key_func: + sort_func = lambda x: sort_key_func(x[0]) + else: + sort_func = lambda x: int(x[0]) if str(x[0]).isnumeric() else x[0] + + groups = sorted([(key, df_group) for key, df_group in df_groups], key=sort_func, reverse=reverse) + groups = groups[:max_group] + if get_from_last: + groups.reverse() + + for key, df_group in groups: + dic_groups[key] = df_group.head(max_record_per_group) + + return dic_groups + + +@log_execution_time() +def split_df_by_time_range(dic_df_chunks, max_group=None, max_record_per_group=None): + dic_groups = {} + max_group = max_group or len(dic_df_chunks) + + count = 0 + for key, df_group in dic_df_chunks.items(): + # for key, df_group in df_groups.groups.items(): + if count >= max_group: + break + + dic_groups[key] = df_group.head(max_record_per_group) + count += 1 + + return dic_groups + + +@log_execution_time() +def drop_missing_data(df: DataFrame, cols): + if len(df): + df.dropna(subset=[col for col in cols if col], inplace=True) + df = df.convert_dtypes() + return df + + +@log_execution_time() +def get_v_keys_str(v_keys): + if v_keys is None: + v_keys_str = None + elif isinstance(v_keys, (list, tuple)): + v_keys_str = '|'.join([str(key) for key in v_keys]) + else: + v_keys_str = v_keys + return v_keys_str + + +@log_execution_time() +def calc_elapsed_times(df_data, time_col): + elapsed_times = pd.to_datetime(df_data[time_col]).sort_values() + elapsed_times = elapsed_times.diff().dt.total_seconds().fillna(0) + elapsed_times = elapsed_times.sort_index() + elapsed_times = elapsed_times.convert_dtypes() + return elapsed_times + + +@log_execution_time() +def gen_scatter_data_count(matrix_col, df: DataFrame, x_proc_id, y_proc_id, x, y, data_count_div, color=None, + levels=None, recent_flg=None): + """ + spit by data count + :param matrix_col: + :param df: + :param x_proc_id: + :param y_proc_id: + :param x: + :param y: + :param data_count_div: + :param color: + :param levels: + :return: + """ + if levels is None: + levels = [] + + # time column + time_col = Cycle.time.key + # time_col = [col for col in df.columns if col.startswith(Cycle.time.key)][0] + + # remove missing data + df = drop_missing_data(df, [x, y, color] + levels) + + h_group_col = DATA_COUNT_COL + + v_group_cols = [col for col in levels if col and col != h_group_col] + + # graph number is depend on facet + max_graph = matrix_col if v_group_cols else matrix_col * matrix_col + + # facet + dic_groups = {} + dic_temp_groups = group_by_df(df, v_group_cols) + facet_keys = [key for key, _ in Counter(dic_temp_groups.keys()).most_common(matrix_col)] + for key, df_group in dic_temp_groups.items(): + if key not in facet_keys: + continue + + df_group = split_data_by_number(df_group, data_count_div) + df_group = reduce_data_by_number(df_group, max_graph, recent_flg) + + dic_groups[key] = group_by_df(df_group, h_group_col, max_graph, sort_key_func=sort_data_count_key, + reverse=recent_flg, get_from_last=recent_flg) + + facet_keys.append(key) + + # serials + x_serial_cols = CfgProcessColumn.get_serials(x_proc_id) + if y_proc_id == x_proc_id: + y_serial_cols = None + else: + y_serial_cols = CfgProcessColumn.get_serials(y_proc_id) + + output_graphs = [] + output_times = [] + for v_keys, dic_group in dic_groups.items(): + if v_keys not in facet_keys: + continue + + for idx, (h_key, df_data) in enumerate(dic_group.items()): + if recent_flg: + h_key = idx + + h_keys_str = f'{data_count_div * h_key + 1} – {data_count_div * (h_key + 1)}' + + v_keys_str = get_v_keys_str(v_keys) + # elapsed_times = calc_elapsed_times(df_data, time_col) + + # v_label : name ( not id ) + dic_data = gen_dic_graphs(df_data, x, y, h_keys_str, v_keys_str, color, time_col, sort_key=h_key) + + # serial + dic_data[X_SERIAL] = get_proc_serials(df_data, x_serial_cols) + dic_data[Y_SERIAL] = get_proc_serials(df_data, y_serial_cols) + + output_times.append((get_proc_times(df_data, x_proc_id), get_proc_times(df_data, y_proc_id))) + output_graphs.append(dic_data) + + return output_graphs, output_times + + +@log_execution_time() +def get_proc_times(df, proc_id): + col_name = f'{Cycle.time.key}_{proc_id}' + if col_name in df.columns: + return df[col_name].tolist() + + return [] + + +@log_execution_time() +def gen_scatter_by_cyclic(matrix_col, df: DataFrame, x_proc_id, y_proc_id, x, y, terms, color=None, levels=None): + """ + split by terms + :param matrix_col: + :param df: + :param x_proc_id: + :param y_proc_id: + :param x: + :param y: + :param terms: + :param color: + :param levels: + :return: + """ + if levels is None: + levels = [] + + # time column + time_col = Cycle.time.key + # time_col = [col for col in df.columns if col.startswith(Cycle.time.key)][0] + + # remove missing data + df = drop_missing_data(df, [x, y, color] + levels) + + dic_df_chunks = {} + df.set_index(Cycle.time.key, inplace=True, drop=False) + for term_id, term in enumerate(terms): + start_dt = term['start_dt'] + end_dt = term['end_dt'] + df_chunk = df[(df.index >= start_dt) & (df.index < end_dt)] + dic_df_chunks[(start_dt, end_dt)] = df_chunk + + v_group_cols = [col for col in levels if col] + + # graph number is depend on facet + max_graph = matrix_col if v_group_cols else matrix_col * matrix_col + dic_groups = split_df_by_time_range(dic_df_chunks, max_graph) + + # facet + dic_groups = {key: group_by_df(df_group, v_group_cols) for key, df_group in dic_groups.items()} + facet_keys = [key for key, _ in + Counter([val for vals in dic_groups.values() for val in vals]).most_common(matrix_col)] + + # serials + x_serial_cols = CfgProcessColumn.get_serials(x_proc_id) + if y_proc_id == x_proc_id: + y_serial_cols = None + else: + y_serial_cols = CfgProcessColumn.get_serials(y_proc_id) + + output_graphs = [] + output_times = [] + for h_key, dic_group in dic_groups.items(): + h_keys_str = f'{h_key[0]} – {h_key[1]}' + + for v_keys, df_data in dic_group.items(): + if v_keys not in facet_keys: + continue + + v_keys_str = get_v_keys_str(v_keys) + # elapsed_times = calc_elapsed_times(df_data, time_col) + + # v_label : name ( not id ) + dic_data = gen_dic_graphs(df_data, x, y, h_keys_str, v_keys_str, color, time_col) + + # serial + dic_data[X_SERIAL] = get_proc_serials(df_data, x_serial_cols) + dic_data[Y_SERIAL] = get_proc_serials(df_data, y_serial_cols) + + output_times.append((get_proc_times(df_data, x_proc_id), get_proc_times(df_data, y_proc_id))) + output_graphs.append(dic_data) + + return output_graphs, output_times + + +@log_execution_time() +def gen_scatter_cat_div(matrix_col, df: DataFrame, x_proc_id, y_proc_id, x, y, cat_div=None, color=None, levels=None): + """ + category divide + :param matrix_col: + :param df: + :param x_proc_id: + :param y_proc_id: + :param x: + :param y: + :param cat_div: + :param color: + :param levels: + :return: + """ + if levels is None: + levels = [] + + # time column + time_col = Cycle.time.key + # time_col = [col for col in df.columns if col.startswith(Cycle.time.key)][0] + + # remove missing data + df = drop_missing_data(df, [x, y, cat_div, color] + levels) + + h_group_col = cat_div + if not h_group_col: + if len(levels) > 1: + h_group_col = levels[-1] + + v_group_cols = [col for col in levels if col and col != h_group_col] + + # graph number is depend on facet + max_graph = matrix_col if v_group_cols else matrix_col * matrix_col + + dic_groups = group_by_df(df, h_group_col, max_graph) + + # facet + dic_groups = {key: group_by_df(df_group, v_group_cols) for key, df_group in dic_groups.items()} + facet_keys = [key for key, _ in + Counter([val for vals in dic_groups.values() for val in vals]).most_common(matrix_col)] + + # serials + x_serial_cols = CfgProcessColumn.get_serials(x_proc_id) + if y_proc_id == x_proc_id: + y_serial_cols = None + else: + y_serial_cols = CfgProcessColumn.get_serials(y_proc_id) + + output_graphs = [] + output_times = [] + for h_key, dic_group in dic_groups.items(): + h_keys_str = h_key + + for v_keys, df_data in dic_group.items(): + if v_keys not in facet_keys: + continue + + v_keys_str = get_v_keys_str(v_keys) + # elapsed_times = calc_elapsed_times(df_data, time_col) + + # v_label : name ( not id ) + dic_data = gen_dic_graphs(df_data, x, y, h_keys_str, v_keys_str, color, time_col) + + # serial + dic_data[X_SERIAL] = get_proc_serials(df_data, x_serial_cols) + dic_data[Y_SERIAL] = get_proc_serials(df_data, y_serial_cols) + + output_times.append((get_proc_times(df_data, x_proc_id), get_proc_times(df_data, y_proc_id))) + output_graphs.append(dic_data) + + return output_graphs, output_times + + +@log_execution_time() +def gen_scatter_by_direct_term(matrix_col, dic_df_chunks, x_proc_id, y_proc_id, x, y, color=None, levels=None): + """ + split by terms + :param matrix_col: + :param dic_df_chunks + :param x_proc_id: + :param y_proc_id: + :param x: + :param y: + :param color: + :param levels: + :return: + """ + if levels is None: + levels = [] + + # time column + time_col = Cycle.time.key + + # remove missing data + for key, df in dic_df_chunks.items(): + dic_df_chunks[key] = drop_missing_data(df, [x, y, color] + levels) + + v_group_cols = [col for col in levels if col] + + # graph number is depend on facet + max_graph = matrix_col if v_group_cols else matrix_col * matrix_col + dic_groups = split_df_by_time_range(dic_df_chunks, max_graph) + + # facet + dic_groups = {key: group_by_df(df_group, v_group_cols) for key, df_group in dic_groups.items()} + facet_keys = [key for key, _ in + Counter([val for vals in dic_groups.values() for val in vals]).most_common(matrix_col)] + + # serials + x_serial_cols = CfgProcessColumn.get_serials(x_proc_id) + if y_proc_id == x_proc_id: + y_serial_cols = None + else: + y_serial_cols = CfgProcessColumn.get_serials(y_proc_id) + + output_graphs = [] + output_times = [] + for h_key, dic_group in dic_groups.items(): + h_keys_str = f'{h_key[0]} {h_key[1]} – {h_key[2]} {h_key[3]}' + + for v_keys, df_data in dic_group.items(): + if v_keys not in facet_keys: + continue + + v_keys_str = get_v_keys_str(v_keys) + # elapsed_times = calc_elapsed_times(df_data, time_col) + + # v_label : name ( not id ) + dic_data = gen_dic_graphs(df_data, x, y, h_keys_str, v_keys_str, color, time_col) + + # serial + dic_data[X_SERIAL] = get_proc_serials(df_data, x_serial_cols) + dic_data[Y_SERIAL] = get_proc_serials(df_data, y_serial_cols) + + output_times.append((get_proc_times(df_data, x_proc_id), get_proc_times(df_data, y_proc_id))) + output_graphs.append(dic_data) + + return output_graphs, output_times + + +@log_execution_time() +def gen_dic_graphs(df_data, x, y, h_keys_str, v_keys_str, color, time_col, sort_key=None): + times = df_data[time_col] + n = times.dropna().size + time_min = np.nanmin(times) if n else None + time_max = np.nanmax(times) if n else None + + dic_data = {H_LABEL: h_keys_str, + V_LABEL: v_keys_str, + ARRAY_X: df_data[x], + ARRAY_Y: df_data[y], + COLORS: df_data[color] if color else [], + TIMES: times, + TIME_MIN: time_min, + TIME_MAX: time_max, + N_TOTAL: n, + SORT_KEY: h_keys_str if sort_key is None else sort_key, + } + return dic_data + + +@log_execution_time() +def gen_df_limit_data(graph, keys, limit=None): + is_limit = False + dic_data = {} + for key in keys: + count = len(graph[key]) + if not count: + continue + + if limit is None or count <= limit: + dic_data[key] = graph[key] + else: + is_limit = True + dic_data[key] = graph[key][:limit] + + return pd.DataFrame(dic_data), is_limit + + +@log_execution_time() +def sort_df(df, columns): + cols = [col for col in columns if col in df.columns] + df.sort_values(by=cols, inplace=True) + + return df + + +@log_execution_time() +def get_most_common_in_graphs(graphs, columns, first_most_common): + data = [] + for graph in graphs: + vals = pd.DataFrame({col: graph[col] for col in columns}).drop_duplicates().to_records(index=False).tolist() + data += vals + + original_vals = [key for key, _ in Counter(data).most_common(None)] + most_vals = [key for key, _ in Counter(data).most_common(first_most_common)] + if len(columns) == 1: + most_vals = [vals[0] for vals in most_vals] + + is_reduce_violin_number = len(original_vals) > len(most_vals) + + return most_vals, is_reduce_violin_number + + +@log_execution_time() +def filter_violin_df(df, cols, most_vals): + df_result = df[df.set_index(cols).index.isin(most_vals)] + return df_result + + +@log_execution_time() +def get_heatmap_distinct(graphs): + array_x = [] + array_y = [] + for graph in graphs: + array_x += graph[ARRAY_X].drop_duplicates().tolist() + array_y += graph[ARRAY_Y].drop_duplicates().tolist() + + return set(array_x), set(array_y) + + +@log_execution_time() +def get_proc_serials(df: DataFrame, serial_cols: List[CfgProcessColumn]): + if not serial_cols: + return None + + if df is None or len(df) == 0: + return None + + # serials + serials = [] + for col in serial_cols: + sql_label = gen_sql_label(col.id, col.column_name) + if sql_label in df.columns: + dic_serial = {'col_name': col.name, 'data': df[sql_label].tolist()} + serials.append(dic_serial) + + return serials + + +@log_execution_time() +def resample_by_sort(x, max_n=10): + """ + Sort data first, then extract rows (equal interval) + Inputs: + x: Pandas series + max_n: Maximum number of rows + Returns: + x: pandas series of length min(x.shape[0], max_n) + """ + if x.shape[0] > max_n: + x = np.sort(x) + idx = np.linspace(0, len(x) - 1, max_n, dtype=int) + x = x[idx] + return x + + + +@log_execution_time() +def reduce_data_by_number(df, max_graph, recent_flg=None): + if not len(df): + return df + + if recent_flg: + first_num = df[DATA_COUNT_COL].iloc[-1] - max_graph + if first_num >= 0: + df = df[df[DATA_COUNT_COL] > first_num] + else: + first_num = df[DATA_COUNT_COL].iloc[0] + max_graph + if first_num >= 0: + df = df[df[DATA_COUNT_COL] < first_num] + + return df + +@log_execution_time() +def resample_preserve_min_med_max(x, n_after: int): + """ Resample x, but preserve (minimum, median, and maximum) values + Inputs: + x (1D-NumpyArray or a list) + n_after (int) Length of x after resampling. Must be < len(x) + Return: + x (1D-NumpyArray) Resampled data + """ + if x.shape[0] > n_after: + # walkaround: n_after with odd number is easier + if n_after % 2 == 0: + n_after += 1 + + n = len(x) + n_half = int((n_after - 1) / 2) + + # index around median + x = np.sort(x) + idx_med = (n + 1) / 2 - 1 # median + idx_med_l = int(np.ceil(idx_med - 1)) # left of median + idx_med_r = int(np.floor(idx_med + 1)) # right of median + + # resampled index + idx_low = np.linspace(0, idx_med_l - 1, num=n_half, dtype=int) + idx_upp = np.linspace(idx_med_r, n - 1, num=n_half, dtype=int) + + # resampling + if n % 2 == 1: + med = x[int(idx_med)] + x = np.concatenate((x[idx_low], [med], x[idx_upp])) + else: + med = 0.5 * (x[idx_med_l] + x[idx_med_r]) + x = np.concatenate((x[idx_low], [med], x[idx_upp])) + return x \ No newline at end of file diff --git a/histview2/api/setting_module/__init__.py b/histview2/api/setting_module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/api/setting_module/controllers.py b/histview2/api/setting_module/controllers.py new file mode 100644 index 0000000..c2b57a0 --- /dev/null +++ b/histview2/api/setting_module/controllers.py @@ -0,0 +1,767 @@ +import json +import os +import traceback +from datetime import datetime + +from apscheduler.triggers.date import DateTrigger +from flask import Blueprint, request, jsonify, Response +from flask_babel import gettext as _ +from loguru import logger +from pytz import utc + +from histview2 import background_jobs +from histview2.api.setting_module.services.common import is_local_client, save_user_settings, get_all_user_settings, \ + delete_user_setting_by_id, get_setting, get_page_top_setting, is_title_exist, parse_user_setting +from histview2.api.setting_module.services.data_import import add_shutdown_app_job +from histview2.api.setting_module.services.data_import import update_or_create_constant_by_type, check_db_con +from histview2.api.setting_module.services.filter_settings import save_filter_config, delete_cfg_filter_from_db +from histview2.api.setting_module.services.polling_frequency import change_polling_all_interval_jobs, add_import_job +from histview2.api.setting_module.services.process_delete import delete_proc_cfg_and_relate_jobs, del_data_source +from histview2.api.setting_module.services.save_load_user_setting import map_form, transform_settings +from histview2.api.setting_module.services.show_latest_record import get_latest_records, save_master_vis_config, \ + get_last_distinct_sensor_values, preview_csv_data, gen_preview_data_check_dict +from histview2.api.trace_data.services.proc_link import gen_global_id_job, show_proc_link_info +from histview2.api.trace_data.services.proc_link_simulation import sim_gen_global_id +from histview2.common.backup_db import add_backup_dbs_job +from histview2.common.common_utils import is_empty, \ + parse_int_value +from histview2.common.constants import WITH_IMPORT_OPTIONS, CfgConstantType, RelationShip, ProcessCfgConst, UI_ORDER_DB, \ + Action, appENV +from histview2.common.cryptography_utils import encrypt +from histview2.common.scheduler import JobType, scheduler +from histview2.common.services import http_content +from histview2.common.services.jp_to_romaji_utils import to_romaji +from histview2.common.services.sse import background_announcer +from histview2.common.yaml_utils import BasicConfigYaml +from histview2.setting_module.models import AppLog, CfgDataSource, make_session, \ + insert_or_update_config, crud_config, CfgCsvColumn, CfgDataSourceCSV, CfgProcess, CfgUserSetting +from histview2.setting_module.schemas import DataSourceSchema, ProcessSchema, CfgUserSettingSchema +from histview2.setting_module.services.background_process import get_job_detail_service +from histview2.setting_module.services.process_config import create_or_update_process_cfg, get_process_cfg, \ + query_database_tables, get_process_columns, get_process_filters, get_process_visualizations +from histview2.setting_module.services.trace_config import get_all_processes_traces_info, save_trace_config_to_db, \ + gen_cfg_trace + +api_setting_module_blueprint = Blueprint( + 'api_setting_module', + __name__, + url_prefix='/histview2/api/setting' +) + + +@api_setting_module_blueprint.route('/update_polling_freq', methods=['POST']) +def update_polling_freq(): + data_update = json.loads(request.data) + with_import_option = data_update.get(WITH_IMPORT_OPTIONS) + freq_min = parse_int_value(data_update.get(CfgConstantType.POLLING_FREQUENCY.name)) or 0 + + # save/update POLLING_FREQUENCY to db + freq_sec = freq_min * 60 + update_or_create_constant_by_type(const_type=CfgConstantType.POLLING_FREQUENCY.name, value=freq_sec) + + # re-set trigger time for all jobs + change_polling_all_interval_jobs(interval_sec=freq_sec, run_now=with_import_option) + + message = {'message': _('Database Setting saved.'), 'is_error': False} + + return jsonify(flask_message=message), 200 + + +@api_setting_module_blueprint.route('/data_source_save', methods=['POST']) +def save_datasource_cfg(): + """ + Expected: ds_config = {"db_0001": {"master-name": name, "host": localhost, ...}} + """ + try: + data_src: CfgDataSource = DataSourceSchema().load(request.json) + + with make_session() as meta_session: + # data source + data_src_rec = insert_or_update_config(meta_session, data_src, exclude_columns=[CfgDataSource.order.key]) + + # csv detail + csv_detail = data_src.csv_detail + if csv_detail: + csv_columns = data_src.csv_detail.csv_columns + csv_columns = [col for col in csv_columns if not is_empty(col.column_name)] + data_src.csv_detail.csv_columns = csv_columns + csv_detail_rec = insert_or_update_config(meta_session, csv_detail, + parent_obj=data_src_rec, + parent_relation_key=CfgDataSource.csv_detail.key, + parent_relation_type=RelationShip.ONE) + + # CRUD + csv_columns = csv_detail.csv_columns + crud_config(meta_session, csv_columns, CfgCsvColumn.data_source_id.key, + CfgCsvColumn.column_name.key, + parent_obj=csv_detail_rec, + parent_relation_key=CfgDataSourceCSV.csv_columns.key, + parent_relation_type=RelationShip.MANY) + + # db detail + db_detail = data_src.db_detail + if db_detail: + # encrypt password + db_detail.password = encrypt(db_detail.password) + db_detail.hashed = True + # avoid blank string + db_detail.port = db_detail.port or None + db_detail.schema = db_detail.schema or None + insert_or_update_config(meta_session, db_detail, + parent_obj=data_src_rec, + parent_relation_key=CfgDataSource.db_detail.key, + parent_relation_type=RelationShip.ONE) + except Exception as e: + logger.exception(e) + message = {'message': _('Database Setting failed to save'), 'is_error': True} + return jsonify(flask_message=message), 500 + + message = {'message': _('Database Setting saved.'), 'is_error': False} + ds = None + if data_src_rec and data_src_rec.id: + ds_schema = DataSourceSchema() + ds = CfgDataSource.get_ds(data_src_rec.id) + ds = ds_schema.dumps(ds) + return jsonify(id=data_src_rec.id, data_source=ds, flask_message=message), 200 + + +# TODO: refactoring check connection without this function +@api_setting_module_blueprint.route('/database_tables', methods=['GET']) +def get_database_tables(): + db_tables = CfgDataSource.get_all() + ds_schema = DataSourceSchema(many=True) + dump_data = ds_schema.dumps(db_tables) + return dump_data, 200 if db_tables else 500 + + +@api_setting_module_blueprint.route('/database_tables_source', methods=['GET']) +def get_database_tables_source(): + db_source = CfgDataSource.get_all_db_source() + ds_schema = DataSourceSchema(many=True) + dump_data = ds_schema.dumps(db_source) + return dump_data, 200 if db_source else 500 + + +@api_setting_module_blueprint.route('/database_table/', methods=['GET']) +def get_database_table(db_id): + if not db_id: + return jsonify({'tables': [], 'msg': 'Invalid data source id'}), 400 + + tables = query_database_tables(db_id) + + if tables is None: + return jsonify({'tables': [], 'msg': 'Invalid data source id'}), 400 + else: + return jsonify(tables), 200 + + +@api_setting_module_blueprint.route('/register_basic_config', methods=['POST']) +def regist_basic_config(): + params = json.loads(request.data) + + basic_config_yaml = BasicConfigYaml() + + # Get Port number from YML Config + if params["info"]["port-no"] is None: + params["info"]["port-no"] = basic_config_yaml.dic_config['info'].get("port-no", 80) + + # Set to show setting page as default + if params["info"]["hide-setting-page"] is None: + params["info"]["hide-setting-page"] = basic_config_yaml.dic_config['info'].get("hide-setting-page", False) + + result = basic_config_yaml.write_json_to_yml_file(params) + + if result: + message = {'message': _('Basic Config saved'), 'is_error': False} + else: + message = {'message': _('Basic Config failed to save'), 'is_error': True} + + return jsonify(flask_message=message), 200 + + +@api_setting_module_blueprint.route('/check_db_connection', methods=['POST']) +def check_db_connection(): + """Check if we can connect to database. Supported databases: SQLite, PostgreSQL, MSSQLServer. + Returns: + HTTP Response - (True + OK message) if connection can be established, return (False + NOT OK message) otherwise. + """ + params = json.loads(request.data).get("db") + db_type = params.get("db_type") + host = params.get("host") + port = params.get("port") + dbname = params.get("dbname") + schema = params.get("schema") + username = params.get("username") + password = params.get("password") + + result = check_db_con(db_type, host, port, dbname, schema, username, password) + + if result: + message = {"db_type": db_type, 'message': _("Connected"), 'connected': True} + else: + message = {"db_type": db_type, 'message': _("Failed to connect"), 'connected': False} + + return jsonify(flask_message=message), 200 + + +@api_setting_module_blueprint.route('/show_latest_records', methods=['POST']) +def show_latest_records(): + """[summary] + Show 5 latest records + Returns: + [type] -- [description] + """ + dic_form = request.form.to_dict() + data_source_id = dic_form.get("databaseName") + table_name = dic_form.get("tableName") + limit = parse_int_value(dic_form.get("limit")) or 5 + cols_with_types, rows, cols_duplicated, previewed_files = get_latest_records(data_source_id, table_name, limit) + dic_preview_limit = gen_preview_data_check_dict(rows, previewed_files) + result = {'cols': cols_with_types, + 'rows': rows, + 'cols_duplicated': cols_duplicated, + 'fail_limit': dic_preview_limit, + } + + return json.dumps(result, ensure_ascii=False, default=http_content.json_serial) + + +@api_setting_module_blueprint.route('/get_csv_resources', methods=['POST']) +def get_csv_resources(): + folder_url = request.json.get('url') + etl_func = request.json.get('etl_func') + csv_delimiter = request.json.get('delimiter') + + dic_output = preview_csv_data(folder_url, etl_func, csv_delimiter, 5) + rows = dic_output['content'] + previewed_files = dic_output['previewed_files'] + dic_preview_limit = gen_preview_data_check_dict(rows, previewed_files) + dic_output['fail_limit'] = dic_preview_limit + + return jsonify(dic_output) + + +@api_setting_module_blueprint.route('/job', methods=['POST']) +def get_background_jobs(): + return jsonify(background_jobs), 200 + + +@api_setting_module_blueprint.route('/listen_background_job', methods=['GET']) +def listen_background_job(): + def stream(): + messages = background_announcer.listen() # returns a queue.Queue + while True: + msg = messages.get() + yield msg + + return Response(stream(), mimetype='text/event-stream') + + +@api_setting_module_blueprint.route('/check_folder', methods=['POST']) +def check_folder(): + try: + data = request.json.get('url') + return jsonify({ + 'status': 200, + 'url': data, + 'is_exists': os.path.isdir(data) and os.path.exists(data), + 'dir': os.path.dirname(data) + }) + except Exception: + # raise + return jsonify({ + 'status': 500, + }) + + +@api_setting_module_blueprint.route('/job_detail/', methods=['GET']) +def get_job_detail(job_id): + """[Summary] Get get job details + Returns: + [json] -- [job details content] + """ + job_details = get_job_detail_service(job_id=job_id) + return jsonify(job_details), 200 + + +@api_setting_module_blueprint.route('/delete_process', methods=['POST']) +def delete_proc_from_db(): + # get proc_id + params = json.loads(request.data) + proc_id = params.get('proc_id') + + # delete config and add job to delete data + delete_proc_cfg_and_relate_jobs(proc_id) + + return jsonify(result=dict()), 200 + + +@api_setting_module_blueprint.route('/save_order/', methods=['POST']) +def save_order(order_name): + """[Summary] Save orders to DB + Returns: 200/500 + """ + try: + orders = json.loads(request.data) + with make_session() as meta_session: + if order_name == UI_ORDER_DB: + for key, val in orders.items(): + CfgDataSource.update_order(meta_session, key, val) + else: + for key, val in orders.items(): + CfgProcess.update_order(meta_session, key, val) + + except Exception: + traceback.print_exc() + return jsonify({}), 500 + + return jsonify({}), 200 + + +@api_setting_module_blueprint.route('/delete_datasource_cfg', methods=['POST']) +def delete_datasource_cfg(): + params = json.loads(request.data) + data_source_id = params.get('db_code') + if data_source_id: + del_data_source(data_source_id) + + return jsonify(id=data_source_id), 200 + + +@api_setting_module_blueprint.route('/stop_job', methods=['POST']) +def stop_jobs(): + try: + if not is_local_client(request): + return jsonify({}), 403 + + # save log to db + with make_session() as meta_session: + t_app_log = AppLog() + t_app_log.ip = request.environ.get('X-Forwarded-For') or request.remote_addr + t_app_log.action = Action.SHUTDOWN_APP.name + t_app_log.description = request.user_agent.string + meta_session.add(t_app_log) + except Exception as ex: + traceback.print_exc() + logger.error(ex) + + # backup database now + add_backup_dbs_job(True) + + # add a job to check for shutdown time + add_shutdown_app_job() + + return jsonify({}), 200 + + +@api_setting_module_blueprint.route('/shutdown', methods=['POST']) +def shutdown(): + if not is_local_client(request): + return jsonify({}), 403 + + logger.info('SHUTTING DOWN...') + os._exit(14) + + return jsonify({}), 200 + + +@api_setting_module_blueprint.route('/proc_config', methods=['POST']) +def post_proc_config(): + process_schema = ProcessSchema() + proc_data = process_schema.load(request.json.get('proc_config')) + should_import_data = request.json.get('import_data') + + try: + # get exists process from id + proc_id = proc_data.get(ProcessCfgConst.PROC_ID.value) + if proc_id: + process = get_process_cfg(int(proc_id)) + if not process: + return jsonify({ + 'status': 404, + 'message': 'Not found {}'.format(proc_id), + }), 200 + + process = create_or_update_process_cfg(proc_data) + + # create process json + process_schema = ProcessSchema() + process_json = process_schema.dump(process) or {} + + # import data + if should_import_data: + add_import_job(process, run_now=True) + + return jsonify({ + 'status': 200, + 'data': process_json, + }), 200 + except Exception as ex: + traceback.print_exc() + return jsonify({ + 'status': 500, + 'message': str(ex), + }), 500 + + +@api_setting_module_blueprint.route('/trace_config', methods=['GET']) +def get_trace_configs(): + """[Summary] Save orders to DB + Returns: 200/500 + """ + try: + procs = get_all_processes_traces_info() + return {'trace_config': json.dumps({'procs': procs})}, 200 + except Exception: + traceback.print_exc() + return jsonify({}), 500 + + +@api_setting_module_blueprint.route('/trace_config', methods=['POST']) +def save_trace_configs(): + """[Summary] Save trace_configs to DB + Returns: 200/500 + """ + try: + params = json.loads(request.data) + save_trace_config_to_db(params) + + job_id = JobType.GEN_GLOBAL.name + scheduler.add_job(job_id, gen_global_id_job, replace_existing=True, + trigger=DateTrigger(datetime.now().astimezone(utc), timezone=utc), + kwargs=dict(_job_id=job_id, _job_name=job_id, is_new_data_check=False)) + except Exception: + traceback.print_exc() + return jsonify({}), 500 + + return jsonify({}), 200 + + +@api_setting_module_blueprint.route('/ds_load_detail/', methods=['GET']) +def ds_load_detail(ds_id): + ds_schema = DataSourceSchema() + ds = CfgDataSource.get_ds(ds_id) + return ds_schema.dumps(ds), 200 + + +@api_setting_module_blueprint.route('/proc_config/', methods=['DELETE']) +def del_proc_config(proc_id): + return jsonify({ + 'status': 200, + 'data': { + 'proc_id': proc_id, + } + }), 200 + + +@api_setting_module_blueprint.route('/proc_config/', methods=['GET']) +def get_proc_config(proc_id): + process = get_process_cfg(proc_id) + if process: + tables = query_database_tables(process['data_source_id']) + return jsonify({ + 'status': 200, + 'data': process, + 'tables': tables + }), 200 + else: + return jsonify({ + 'status': 404, + 'data': 'Not found' + }), 200 + + +@api_setting_module_blueprint.route('/proc_filter_config/', methods=['GET']) +def get_proc_config_filter_data(proc_id): + process = get_process_cfg(proc_id) + # filter_col_data = get_filter_col_data(process) or {} + filter_col_data = {} + if process: + return jsonify({ + 'status': 200, + 'data': process, + 'filter_col_data': filter_col_data, + }), 200 + else: + return jsonify({ + 'status': 404, + 'data': {}, + 'filter_col_data': {}, + }), 200 + + +@api_setting_module_blueprint.route('/proc_config//columns', methods=['GET']) +def get_proc_column_config(proc_id): + columns = get_process_columns(proc_id) + if columns: + return jsonify({ + 'status': 200, + 'data': columns, + }), 200 + else: + return jsonify({ + 'status': 404, + 'data': [] + }), 200 + + +@api_setting_module_blueprint.route('/proc_config//filters', methods=['GET']) +def get_proc_filter_config(proc_id): + filters = get_process_filters(proc_id) + if filters: + return jsonify({ + 'status': 200, + 'data': filters, + }), 200 + else: + return jsonify({ + 'status': 404, + 'data': [] + }), 200 + + +@api_setting_module_blueprint.route('/proc_config//visualizations', methods=['GET']) +def get_proc_visualization_config(proc_id): + proc_with_visual_settings = get_process_visualizations(proc_id) + if proc_with_visual_settings: + return jsonify({ + 'status': 200, + 'data': proc_with_visual_settings, + }), 200 + else: + return jsonify({ + 'status': 404, + 'data': [] + }), 200 + + +@api_setting_module_blueprint.route('/filter_config', methods=['POST']) +def save_filter_config_configs(): + """[Summary] Save filter_config to DB + Returns: 200/500 + """ + try: + params = json.loads(request.data) + filter_id = save_filter_config(params) + + proc_id = params.get('processId') + process = get_process_cfg(proc_id) + except Exception: + traceback.print_exc() + return jsonify({}), 500 + + return jsonify({'proc': process, 'filter_id': filter_id}), 200 + + +@api_setting_module_blueprint.route('/filter_config/', methods=['DELETE']) +def delete_filter_config(filter_id): + """[Summary] delete filter_config from DB + Returns: 200/500 + """ + try: + delete_cfg_filter_from_db(filter_id) + except Exception: + traceback.print_exc() + return jsonify({}), 500 + + return jsonify({}), 200 + + +@api_setting_module_blueprint.route('/distinct_sensor_values/', methods=['GET']) +def get_sensor_distinct_values(cfg_col_id): + sensor_data = get_last_distinct_sensor_values(cfg_col_id) + if sensor_data: + return jsonify({ + 'data': sensor_data, + }), 200 + else: + return jsonify({ + 'data': [] + }), 200 + + +@api_setting_module_blueprint.route('/proc_config//visualizations', methods=['POST']) +def post_master_visualizations_config(proc_id): + try: + save_master_vis_config(proc_id, request.json) + proc_with_visual_settings = get_process_visualizations(proc_id) + return jsonify({ + 'status': 200, + 'data': proc_with_visual_settings, + }), 200 + except Exception as ex: + traceback.print_exc() + return jsonify({ + 'status': 500, + 'message': str(ex), + }), 500 + + +@api_setting_module_blueprint.route('/simulate_proc_link', methods=['POST']) +def simulate_proc_link(): + """[Summary] simulate proc link id + Returns: 200/500 + """ + traces = json.loads(request.data) + cfg_traces = [gen_cfg_trace(trace) for trace in traces] + + dic_proc_cnt, dic_edge_cnt = sim_gen_global_id(cfg_traces) + + # if there is no key in dic, set zero + for cfg_trace in cfg_traces: + + self_proc_id = cfg_trace.self_process_id + target_proc_id = cfg_trace.target_process_id + edge_id = f'{self_proc_id}-{target_proc_id}' + + if dic_proc_cnt.get(self_proc_id) is None: + dic_proc_cnt[self_proc_id] = 0 + + if dic_proc_cnt.get(target_proc_id) is None: + dic_proc_cnt[target_proc_id] = 0 + + if dic_edge_cnt.get(edge_id) is None: + dic_edge_cnt[edge_id] = 0 + + return jsonify(nodes=dic_proc_cnt, edges=dic_edge_cnt), 200 + + +@api_setting_module_blueprint.route('/count_proc_link', methods=['POST']) +def count_proc_link(): + """[Summary] count proc link id + Returns: 200/500 + """ + dic_proc_cnt, dic_edge_cnt = show_proc_link_info() + return jsonify(nodes=dic_proc_cnt, edges=dic_edge_cnt), 200 + + +@api_setting_module_blueprint.route('/to_eng', methods=['POST']) +def to_eng(): + request_col = request.json + col_english_name = to_romaji(request_col['colname']) + return jsonify({'status': 200, 'data': col_english_name}), 200 + + +@api_setting_module_blueprint.route('/list_to_english', methods=['POST']) +def list_to_english(): + request_json = request.json + raw_english_names = request_json.get('english_names') or [] + romaji_english_names = [to_romaji(raw_name) for raw_name in raw_english_names] + + return jsonify({'status': 200, 'data': romaji_english_names}), 200 + + +@api_setting_module_blueprint.route('/user_setting', methods=['POST']) +def save_user_setting(): + """[Summary] Save user settings to DB + Returns: 200/500 + """ + try: + params = json.loads(request.data) + save_user_settings(params) + + # find setting id after creating a new setting + setting = parse_user_setting(params) + if not setting.id: + setting = CfgUserSetting.get_by_title(setting.title)[0] + setting = CfgUserSettingSchema().dump(setting) + except Exception as ex: + logger.exception(ex) + return jsonify({'status': 'error'}), 500 + + return jsonify({'status': 200, 'data': setting}), 200 + + +@api_setting_module_blueprint.route('/user_settings', methods=['GET']) +def get_user_settings(): + settings = get_all_user_settings() + return jsonify({'status': 200, 'data': settings}), 200 + + +@api_setting_module_blueprint.route('/user_setting/', methods=['GET']) +def get_user_setting(setting_id): + setting_id = parse_int_value(setting_id) + setting = get_setting(setting_id) + if not setting: + return jsonify({}), 404 + + return jsonify({'status': 200, 'data': setting}), 200 + + +@api_setting_module_blueprint.route('/user_setting_page_top', methods=['GET']) +def get_user_setting_page_top(): + page = request.args.get("page") + if not page: + return jsonify({}), 400 + + setting = get_page_top_setting(page) or {} + + return jsonify({'status': 200, 'data': setting}), 200 + + +@api_setting_module_blueprint.route('/user_setting/', methods=['DELETE']) +def delete_user_setting(setting_id): + """[Summary] delete user_setting from DB + Returns: 200/500 + """ + try: + setting_id = parse_int_value(setting_id) + if not setting_id: + return jsonify({}), 400 + + delete_user_setting_by_id(setting_id) + + except Exception as ex: + logger.exception(ex) + return jsonify({}), 500 + + return jsonify({}), 200 + + +@api_setting_module_blueprint.route('/get_env', methods=['GET']) +def get_current_env(): + current_env = os.environ.get('ANALYSIS_INTERFACE_ENV', appENV.DEVELOPMENT.value) + return jsonify({'status': 200, 'env': current_env}), 200 + + +@api_setting_module_blueprint.route('/load_user_setting', methods=['POST']) +def load_user_setting(): + request_data = json.loads(request.data) + setting_id = request_data.get('setting_id') + dic_orig_settings = request_data.get('dic_original_setting') + active_form = request_data.get('active_form') + shared_setting = request_data.get('shared_user_setting') + if setting_id: + setting_id = parse_int_value(setting_id) + dic_setting = get_setting(setting_id) + if not dic_setting: + return jsonify({}), 404 + + else: + dic_setting = {} + dic_src_settings = {'dataForm': shared_setting} + + dic_des_setting = dic_orig_settings + if active_form and active_form in dic_orig_settings: + dic_des_setting = {active_form: dic_orig_settings[active_form]} + + mapping_groups = map_form(dic_src_settings, dic_des_setting) + + dic_setting['settings'] = transform_settings(mapping_groups) + + return jsonify({'status': 200, 'data': dic_setting}), 200 + + +@api_setting_module_blueprint.route('/check_exist_title_setting', methods=['POST']) +def check_exist_title_setting(): + """[Summary] Check input title setting is exist on DB or not + Returns: status: 200/500 and is_exist: True/False + """ + try: + params = json.loads(request.data) + is_exist = is_title_exist(params.get('title')) + except Exception as ex: + logger.exception(ex) + return jsonify({'status': 'error'}), 500 + + return jsonify({'status': 'ok', 'is_exist': is_exist}), 200 diff --git a/histview2/api/setting_module/services/__init__.py b/histview2/api/setting_module/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/api/setting_module/services/common.py b/histview2/api/setting_module/services/common.py new file mode 100644 index 0000000..931d954 --- /dev/null +++ b/histview2/api/setting_module/services/common.py @@ -0,0 +1,83 @@ +from loguru import logger + +from histview2.common.common_utils import parse_int_value +from histview2.setting_module.models import make_session, CfgUserSetting, insert_or_update_config +from histview2.setting_module.schemas import CfgUserSettingSchema + + +def is_local_client(req): + try: + client_ip = req.environ.get('X-Forwarded-For') or req.remote_addr + accepted_ips = ['127.0.0.1', 'localhost'] + if client_ip in accepted_ips: + return True + except Exception as ex: + logger.exception(ex) + + return False + + +def save_user_settings(request_params): + with make_session() as meta_session: + cfg_user_setting = parse_user_setting(request_params) + insert_or_update_config(meta_session, cfg_user_setting) + meta_session.commit() + return True + + +def parse_user_setting(params): + setting_id = parse_int_value(params.get('id')) + title = params.get('title') or '' + page = params.get('page') or '' + key = '{}|{}'.format(page, title) # TODO use page + title for now + created_by = params.get('created_by') or '' + priority = parse_int_value(params.get('priority')) or 1 + use_current_time = bool(params.get('use_current_time')) + description = params.get('description') or '' + share_info = bool(params.get('share_info')) + settings = params.get('settings') or '[]' + + cfg_user_setting = CfgUserSetting(**{ + 'id': setting_id, + 'key': key, + 'title': title, + 'page': page, + 'created_by': created_by, + 'priority': priority, + 'use_current_time': use_current_time, + 'description': description, + 'share_info': share_info, + 'settings': settings, + }) + + return cfg_user_setting + + +def get_all_user_settings(): + user_setting_schema = CfgUserSettingSchema(exclude=[CfgUserSetting.settings.key]) + user_settings = CfgUserSetting.get_all() or [] + # TODO push current page setting to top + return [user_setting_schema.dump(user_setting) for user_setting in user_settings] + + +def get_setting(setting_id): + user_setting_schema = CfgUserSettingSchema() + user_setting = CfgUserSetting.get_by_id(setting_id) or [] + # TODO push current page setting to top + return user_setting_schema.dump(user_setting) + + +def get_page_top_setting(page): + user_setting_schema = CfgUserSettingSchema() + user_setting = CfgUserSetting.get_top(page) or [] + return user_setting_schema.dump(user_setting) + + +def delete_user_setting_by_id(setting_id): + with make_session() as mss: + CfgUserSetting.delete_by_id(mss, setting_id) + + +def is_title_exist(title): + user_settings = CfgUserSetting.get_by_title(title) + return True if user_settings else False diff --git a/histview2/api/setting_module/services/csv_import.py b/histview2/api/setting_module/services/csv_import.py new file mode 100644 index 0000000..b383499 --- /dev/null +++ b/histview2/api/setting_module/services/csv_import.py @@ -0,0 +1,595 @@ +import math +from datetime import datetime +from io import BytesIO +from typing import List + +import pandas as pd +from dateutil import tz +from pandas import DataFrame + +from histview2.api.efa.services.etl import csv_transform, detect_file_delimiter +from histview2.api.parallel_plot.services import gen_dic_sensors +from histview2.api.setting_module.services.data_import import csv_data_with_headers, import_data, \ + save_sensors, \ + RECORD_PER_COMMIT, get_new_adding_columns, gen_import_job_info, NA_VALUES, \ + data_pre_processing, gen_substring_column_info, gen_dic_sensor_n_cls, add_new_col_to_df, convert_df_col_to_utc, \ + convert_df_datetime_to_str, validate_datetime, get_sensor_values, INDEX_COL, \ + gen_error_output_df, get_df_first_n_last, write_error_trace, write_error_import, get_latest_records, FILE_IDX_COL, \ + gen_duplicate_output_df, write_duplicate_import +from histview2.api.trace_data.services.proc_link import add_gen_proc_link_job +from histview2.common.common_utils import get_files, get_csv_delimiter, detect_encoding, get_file_modify_time, chunks, \ + get_current_timestamp, detect_file_encoding, convert_time, DATE_FORMAT_STR_ONLY_DIGIT, get_ip_address, get_basename +from histview2.common.constants import JobStatus, DataType +from histview2.common.logger import log_execution_time +from histview2.common.scheduler import scheduler_app_context, JobType +from histview2.common.services.csv_content import read_data, is_normal_csv +from histview2.common.services.normalization import normalize_str, normalize_list +from histview2.common.timezone_utils import detect_timezone, get_utc_offset +from histview2.setting_module.models import CsvImport, CfgProcess, CfgDataSourceCSV, CfgProcessColumn, JobManagement +from histview2.setting_module.services.background_process import send_processing_info, JobInfo +from histview2.trace_data.models import Process, find_cycle_class + +pd.options.mode.chained_assignment = None # default='warn' + + +@scheduler_app_context +def import_csv_job(_job_id, _job_name, _db_id, _proc_id, _proc_name, *args, **kwargs): + """ scheduler job import csv + + Keyword Arguments: + _job_id {[type]} -- [description] (default: {None}) + _job_name {[type]} -- [description] (default: {None}) + """ + gen = import_csv(*args, **kwargs) + send_processing_info(gen, JobType.CSV_IMPORT, db_code=_db_id, process_id=_proc_id, process_name=_proc_name, + after_success_func=add_gen_proc_link_job) + + +@log_execution_time() +def import_csv(proc_id, record_per_commit=RECORD_PER_COMMIT): + """ csv files import + + Keyword Arguments: + proc_id {[type]} -- [description] (default: {None}) + db_id {[type]} -- [description] (default: {None}) + + Raises: + e: [description] + + Yields: + [type] -- [description] + """ + # start job + yield 0 + + # get db info + proc_cfg: CfgProcess = CfgProcess.query.get(proc_id) + data_src: CfgDataSourceCSV = CfgDataSourceCSV.query.get(proc_cfg.data_source_id) + + # create or get process + proc = Process.get_or_create_proc(proc_id=proc_id, proc_name=proc_cfg.name) + + # get import files + import_targets, no_data_files = get_import_target_files(proc_id, data_src) + + # csv delimiter + csv_delimiter = get_csv_delimiter(data_src.delimiter) + + # get header + headers = data_src.get_column_names_with_sorted() + dic_use_cols = {col.column_name: col.data_type for col in proc_cfg.columns} + + # job 100% with zero row + if not import_targets: + yield 100 + return + + # cycle class + cycle_cls = find_cycle_class(proc_id) + + # check new adding column, save. + missing_sensors = get_new_adding_columns(proc, dic_use_cols) + save_sensors(missing_sensors) + + # sensor classes + dic_sensor, dic_sensor_cls = gen_dic_sensor_n_cls(proc_id, dic_use_cols) + + # substring sensors info + dic_substring_sensors = gen_substring_column_info(proc_id, dic_sensor) + + # get GET_DATE + get_date_col = proc_cfg.get_date_col() + + # depend on file type (efa1,2,3,4 or normal) , choose right header + default_csv_param = {} + use_col_names = [] + if not data_src.etl_func and import_targets and not is_normal_csv(import_targets[-1][0], csv_delimiter): + is_abnormal = True + default_csv_param['names'] = headers + use_col_names = headers + data_first_row = data_src.skip_head + 1 + head_skips = list(range(0, data_first_row)) + else: + is_abnormal = False + data_first_row = data_src.skip_head + 1 + head_skips = list(range(0, data_src.skip_head)) + + total_percent = 0 + percent_per_file = 100 / len(import_targets) + dic_imported_row = {} + df = pd.DataFrame() + + # init job information object + job_info = JobInfo() + job_info.empty_files = [] + + # file can not transform by R script + transformed_file_delimiter = csv_delimiter + for csv_file_name in no_data_files: + job_info.status = JobStatus.DONE + job_info.empty_files = [csv_file_name] + yield from yield_job_info(job_info, csv_file_name) + job_info.empty_files = [] + + # get current job id + t_job_management: JobManagement = JobManagement.get_last_job_of_process(proc_id, JobType.CSV_IMPORT.name) + job_id = str(t_job_management.id) if t_job_management else '' + + df_db_latest_records = None + for idx, (csv_file_name, transformed_file) in enumerate(import_targets): + job_info.target = csv_file_name + + if not dic_imported_row: + job_info.start_tm = get_current_timestamp() + + # R error check + if isinstance(transformed_file, Exception): + yield from yield_job_info(job_info, csv_file_name, err_msgs=str(transformed_file)) + continue + + # delimiter check + transformed_file_delimiter = detect_file_delimiter(transformed_file, csv_delimiter) + + # check missing columns + if is_abnormal is False: + # check missing columns + check_file = read_data(transformed_file, skip_head=data_src.skip_head, end_row=1, + delimiter=transformed_file_delimiter, + do_normalize=False) + csv_cols = next(check_file) + csv_cols_normalized = normalize_list(csv_cols) + + check_file.close() + missing_cols = set(dic_use_cols).difference(csv_cols_normalized) + if missing_cols: + err_msg = f"File {transformed_file} doesn't contain expected columns: {missing_cols}" + + df_one_file = csv_to_df(transformed_file, data_src, head_skips, data_first_row, 0, + transformed_file_delimiter) + + if df_db_latest_records is None: + df_db_latest_records = get_latest_records(proc_id, dic_sensor, get_date_col) + df_error_trace = gen_error_output_df(csv_file_name, dic_sensor, get_df_first_n_last(df_one_file), + df_db_latest_records, err_msg) + + write_error_trace(df_error_trace, proc_cfg.name, csv_file_name) + write_error_import(df_one_file, proc_cfg.name, csv_file_name, transformed_file_delimiter, + data_src.directory) + + yield from yield_job_info(job_info, csv_file_name, err_msgs=err_msg) + continue + + default_csv_param['usecols'] = [col for col in csv_cols if col] + use_col_names = csv_cols + + # read csv file + default_csv_param['dtype'] = {col: 'string' for col, data_type in dic_use_cols.items() if + col in use_col_names and data_type == DataType.TEXT.name} + + df_one_file = csv_to_df(transformed_file, data_src, head_skips, data_first_row, 0, transformed_file_delimiter, + default_csv_param=default_csv_param) + + # validate column name + validate_columns(dic_use_cols, df_one_file.columns) + + file_record_count = len(df_one_file) + + # no records + if not file_record_count: + job_info.status = JobStatus.DONE + job_info.empty_files = [csv_file_name] + yield from yield_job_info(job_info, csv_file_name) + job_info.empty_files = [] + continue + + dic_imported_row[idx] = (csv_file_name, file_record_count) + + # add 3 columns machine, line, process for efa 1,2,4 + if is_abnormal: + cols, vals = csv_data_with_headers(csv_file_name, data_src) + df_one_file[cols] = vals + + # remove unused columns + df_one_file = df_one_file[list(dic_use_cols)] + + # mark file + df_one_file[FILE_IDX_COL] = idx + + # merge df + df = df.append(df_one_file, ignore_index=True) + + # 10K records + if len(df) * len(df.columns) < record_per_commit * 100: + continue + + # calc percent + percent_per_commit = percent_per_file * len(dic_imported_row) + + # do import + save_res, df_error, df_duplicate = import_df(proc_id, df, dic_use_cols, get_date_col, cycle_cls, dic_sensor, + dic_sensor_cls, dic_substring_sensors) + + df_error_cnt = len(df_error) + if df_error_cnt: + if df_db_latest_records is None: + df_db_latest_records = get_latest_records(proc_id, dic_sensor, get_date_col) + write_invalid_records_to_file(df_error, dic_imported_row, dic_sensor, df_db_latest_records, + proc_cfg, transformed_file_delimiter, data_src.directory) + + if df_duplicate is not None and len(df_duplicate): + write_duplicate_records_to_file(df_duplicate, dic_imported_row, dic_use_cols, proc_cfg.name, job_id) + + total_percent = set_csv_import_percent(job_info, total_percent, percent_per_commit) + for _idx, (_csv_file_name, _imported_row) in dic_imported_row.items(): + yield from yield_job_info(job_info, _csv_file_name, _imported_row, save_res, df_error_cnt) + + # reset df (important!!!) + df = pd.DataFrame() + dic_imported_row = {} + + # do last import + if len(df): + save_res, df_error, df_duplicate = import_df(proc_id, df, dic_use_cols, get_date_col, cycle_cls, dic_sensor, + dic_sensor_cls, dic_substring_sensors) + + df_error_cnt = len(df_error) + if df_error_cnt: + if df_db_latest_records is None: + df_db_latest_records = get_latest_records(proc_id, dic_sensor, get_date_col) + write_invalid_records_to_file(df_error, dic_imported_row, dic_sensor, df_db_latest_records, + proc_cfg, transformed_file_delimiter, data_src.directory) + + if df_duplicate is not None and len(df_duplicate): + write_duplicate_records_to_file(df_duplicate, dic_imported_row, dic_use_cols, proc_cfg.name, job_id) + + for _idx, (_csv_file_name, _imported_row) in dic_imported_row.items(): + yield from yield_job_info(job_info, _csv_file_name, _imported_row, save_res, df_error_cnt) + + yield 100 + + +def set_csv_import_percent(job_info, total_percent, percent_per_chunk): + total_percent += percent_per_chunk + job_info.percent = math.floor(total_percent) + if job_info.percent >= 100: + job_info.percent = 99 + + return total_percent + + +@log_execution_time() +def get_last_csv_import_info(process_id): + """ get latest csv import info + """ + + latest_import_files = CsvImport.get_latest_done_files(process_id) + dic_imported_file = {rec.file_name: rec.start_tm for rec in latest_import_files} + csv_fatal_imports = CsvImport.get_last_fatal_import(process_id) + dic_fatal_file = {rec.file_name: rec.start_tm for rec in csv_fatal_imports} + + return dic_imported_file, dic_fatal_file + + +@log_execution_time() +def filter_import_target_file(proc_id, all_files, dic_success_file: dict, dic_error_file: dict, is_transform=False): + """filter import target file base on last import job + + Arguments: + all_files {[type]} -- [description] + dic_success_file {dict} -- [description] + dic_error_file {dict} -- [description] + + Returns: + [type] -- [description] + """ + + has_transform_targets = [] + no_transform_targets = [] + for file_name in all_files: + if file_name in dic_error_file: + pass + elif file_name in dic_success_file: + modified_date = get_file_modify_time(file_name) + imported_datetime = dic_success_file[file_name] + if modified_date <= imported_datetime: + continue + + # count all rows + transformed_file = file_name + if is_transform: + transformed_file = csv_transform(proc_id, file_name) + + if transformed_file: + has_transform_targets.append((file_name, transformed_file)) + else: + no_transform_targets.append(file_name) + + return has_transform_targets, no_transform_targets + + +def validate_columns(checked_cols, csv_cols): + """ + check if checked column exists in csv file + :param checked_cols: + :param csv_cols: + :return: + """ + ng_cols = set(checked_cols) - set(csv_cols) + if ng_cols: + raise Exception('CSVファイルの列名・列数が正しくないです。') + + +@log_execution_time() +def csv_to_df(transformed_file, data_src, head_skips, data_first_row, skip_row, csv_delimiter, default_csv_param=None, + from_file=False): + # read csv file + read_csv_param = {} + if default_csv_param: + read_csv_param.update(default_csv_param) + + read_csv_param.update(dict(skiprows=head_skips + list(range(data_first_row, skip_row + data_first_row)))) + + # get encoding + if from_file: + encoding = detect_file_encoding(transformed_file) + transformed_file = BytesIO(transformed_file) + else: + encoding = detect_encoding(transformed_file) + + # load csv data to dataframe + df = pd.read_csv(transformed_file, sep=csv_delimiter, skipinitialspace=True, na_values=NA_VALUES, + error_bad_lines=False, encoding=encoding, skip_blank_lines=True, **read_csv_param) + df.dropna(how='all', inplace=True) + col_names = {col: normalize_str(col) for col in df.columns} + df = df.rename(columns=col_names) + + # skip tail + if data_src.skip_tail and len(df): + df.drop(df.tail(data_src.skip_tail).index, inplace=True) + + return df + + +@log_execution_time() +def get_import_target_files(proc_id, data_src): + dic_success_file, dic_error_file = get_last_csv_import_info(proc_id) + csv_files = get_files(data_src.directory, depth_from=1, depth_to=100, extension=['csv', 'tsv']) + + # transform csv files (pre-processing) + is_transform = False + if data_src.etl_func: + is_transform = True + + # filter target files + has_trans_targets, no_trans_targets = filter_import_target_file(proc_id, csv_files, dic_success_file, + dic_error_file, is_transform) + return has_trans_targets, no_trans_targets + + +def strip_quote(val): + try: + return val.strip("'").strip() + except AttributeError: + return val + + +@log_execution_time() +def strip_quote_in_df(df: DataFrame): + """ + strip quote and space + :param df: + :return: + """ + # strip quote + cols = df.select_dtypes(include=['string', 'object']).columns.tolist() + df[cols] = df[cols].apply(strip_quote) + + return df + + +@log_execution_time() +def get_datetime_val(datetime_col): + """ + Gets a random datetime value support to convert UTC + :return: + """ + # Check one by one until get well-formatted datetime string + valid_datetime_idx = datetime_col.first_valid_index() + datetime_val = datetime_col.loc[valid_datetime_idx] if valid_datetime_idx is not None else None + return datetime_val + + +@log_execution_time() +def copy_df(df): + orig_df = df.copy() + return orig_df + + +@log_execution_time() +def remove_duplicates(df: DataFrame, df_origin: DataFrame, proc_id, get_date_col): + # get columns that use to check duplicate + # df_cols = list(set(df.columns.tolist()) - set([INDEX_COL, FILE_IDX_COL])) + + # remove duplicate in csv files + # df.drop_duplicates(subset=df_cols, keep='last', inplace=True) + df.drop_duplicates(keep='last', inplace=True) + index_col = add_new_col_to_df(df, '__df_index_column__', df.index) + + # get min max time of df + start_tm, end_tm = get_min_max_date(df, get_date_col) + + # get sensors + cfg_columns: List[CfgProcessColumn] = CfgProcessColumn.get_all_columns(proc_id) + cfg_columns.sort(key=lambda c: c.is_serial_no + c.is_get_date + c.is_auto_increment, reverse=True) + + col_names = [cfg_col.column_name for cfg_col in cfg_columns] + dic_sensors = gen_dic_sensors(proc_id, col_names) + + cycle_cls = find_cycle_class(proc_id) + idxs = None + for cols in chunks(col_names, 10): + # get data from database + records = get_sensor_values(proc_id, cols, dic_sensors, cycle_cls, start_tm=start_tm, end_tm=end_tm) + if not records: + break + + df_db = pd.DataFrame(records) + df_db.drop(INDEX_COL, axis=1, inplace=True) + df_db.drop_duplicates(inplace=True) + + # remove duplicate df vs df_db + _idxs = get_duplicate_info(df, df_db, index_col, idxs) + + # can not check duplicate with these columns + # no column : it is ok , no dupl + if _idxs is None: + continue + + # filter idxs + idxs = _idxs + + # no duplicate + if not len(idxs): + break + + if idxs: + df.drop(idxs, inplace=True) + df.drop(index_col, axis=1, inplace=True) + + # duplicate data + df_duplicate = df_origin[~df_origin.index.isin(df.index)] + + return df_duplicate + + +@log_execution_time() +def get_min_max_date(df: DataFrame, get_date_col): + return df[get_date_col].min(), df[get_date_col].max() + + +@log_execution_time() +def get_duplicate_info(df_csv: DataFrame, df_db: DataFrame, df_index_col, idxs): + col_names = df_db.columns.tolist() + all_cols = col_names + [df_index_col] + if idxs: + df = df_csv.loc[idxs][all_cols].copy() + else: + df = df_csv[all_cols].copy() + + for col in col_names: + if df[col].dtype.name != df_db[col].dtype.name: + df[col] = df[col].astype(object) + df_db[col] = df_db[col].astype(object) + + df_merged = pd.merge(df, df_db, on=col_names) + idxs = df_merged[df_index_col].to_list() + return idxs + + +@log_execution_time() +def import_df(proc_id, df, dic_use_cols, get_date_col, cycle_cls, dic_sensor, dic_sensor_cls, dic_substring_sensors): + if not len(df): + return 0, None, None + + # convert types + df = df.convert_dtypes() + + # original df + orig_df = copy_df(df) + + # remove FILE INDEX col + if FILE_IDX_COL in df.columns: + df.drop(FILE_IDX_COL, axis=1, inplace=True) + + # Convert UTC time + for col, dtype in dic_use_cols.items(): + if DataType[dtype] is not DataType.DATETIME: + continue + + null_is_error = False + if col == get_date_col: + null_is_error = True + + validate_datetime(df, col, null_is_error=null_is_error) + convert_csv_timezone(df, col) + + # data pre-processing + df_error = data_pre_processing(df, orig_df, dic_use_cols, exclude_cols=[get_date_col, FILE_IDX_COL, INDEX_COL]) + + # no records + if not len(df): + return 0, df_error, None + + # remove duplicate records in csv file which exists in csv or DB + df_duplicate = remove_duplicates(df, orig_df, proc_id, get_date_col) + + save_res = import_data(df, proc_id, get_date_col, cycle_cls, dic_sensor, dic_sensor_cls, dic_substring_sensors) + return save_res, df_error, df_duplicate + + +def yield_job_info(job_info, csv_file_name, imported_row=0, save_res=0, df_error_cnt=0, err_msgs=None): + job_info.target = csv_file_name + job_info.err_msg = None + job_info.status = JobStatus.DONE + gen_import_job_info(job_info, save_res, end_time=get_current_timestamp(), imported_count=imported_row, + err_cnt=df_error_cnt, err_msgs=err_msgs) + yield job_info + + +@log_execution_time() +def convert_csv_timezone(df, get_date_col): + datetime_val = get_datetime_val(df[get_date_col]) + is_tz_inside = bool(detect_timezone(datetime_val)) + time_offset = get_utc_offset(tz.tzlocal()) if not is_tz_inside else None + df[get_date_col] = convert_df_col_to_utc(df, get_date_col, is_tz_inside, time_offset) + df[get_date_col] = convert_df_datetime_to_str(df, get_date_col) + + +def write_invalid_records_to_file(df_error: DataFrame, dic_imported_row, dic_sensor, df_db, proc_cfg, + transformed_file_delimiter, data_src_folder, err_msg=None): + idxs = df_error[FILE_IDX_COL].unique() + for idx in idxs: + csv_file_name, *_ = dic_imported_row[idx] + df_error_one_file = df_error[df_error[FILE_IDX_COL] == idx] + df_error_one_file.drop(FILE_IDX_COL, axis=1, inplace=True) + df_error_trace = gen_error_output_df(csv_file_name, dic_sensor, + get_df_first_n_last(df_error_one_file), df_db, err_msg) + write_error_trace(df_error_trace, proc_cfg.name, csv_file_name) + write_error_import(df_error_one_file, proc_cfg.name, csv_file_name, transformed_file_delimiter, data_src_folder) + return True + + +def write_duplicate_records_to_file(df_duplicate: DataFrame, dic_imported_row, dic_use_cols, proc_name, job_id=None): + error_msg = 'Duplicate Record' + time_str = convert_time(datetime.now(), format_str=DATE_FORMAT_STR_ONLY_DIGIT)[4:-3] + ip_address = get_ip_address() + + for idx, df in df_duplicate.groupby(FILE_IDX_COL): + csv_file_path_name, *_ = dic_imported_row[idx] + csv_file_name = get_basename(csv_file_path_name) if csv_file_path_name else '' + + df.drop(FILE_IDX_COL, axis=1, inplace=True) + df_output = gen_duplicate_output_df(dic_use_cols, get_df_first_n_last(df), + csv_file_name=csv_file_path_name, error_msgs=error_msg) + + write_duplicate_import(df_output, [proc_name, csv_file_name, 'Duplicate', job_id, time_str, ip_address]) diff --git a/histview2/api/setting_module/services/data_import.py b/histview2/api/setting_module/services/data_import.py new file mode 100644 index 0000000..733bdb1 --- /dev/null +++ b/histview2/api/setting_module/services/data_import.py @@ -0,0 +1,1269 @@ +import os.path +import re +import traceback +from collections import defaultdict +from datetime import datetime +from typing import List + +import numpy as np +import pandas as pd +from apscheduler.triggers.date import DateTrigger +from dateutil import tz +from loguru import logger +from pandas import DataFrame +from pytz import utc +from sqlalchemy import and_ + +from histview2 import db, scheduler +from histview2.common.common_utils import ( + parse_int_value, + make_dir_from_file_path, get_current_timestamp, get_csv_delimiter, DATE_FORMAT_STR, convert_time, + DATE_FORMAT_STR_ONLY_DIGIT, split_path_to_list, get_error_trace_path, get_error_import_path, get_basename, + get_ip_address, chunks +) +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.pydn.dblib.db_proxy import DbProxy, gen_data_source_of_universal_db +from histview2.common.memoize import set_all_cache_expired +from histview2.common.scheduler import scheduler_app_context, JobType, lock +from histview2.common.services import csv_header_wrapr as chw +from histview2.common.services.csv_content import read_data +from histview2.common.services.normalization import normalize_df, normalize_str +from histview2.common.services.sse import background_announcer, AnnounceEvent +from histview2.common.timezone_utils import calc_offset_between_two_tz +from histview2.setting_module.models import CfgConstant, CfgDataSource, CfgDataSourceDB, CfgProcess +from histview2.setting_module.services.background_process import send_processing_info +from histview2.trace_data.models import Sensor, find_sensor_class, SensorType, find_cycle_class, CYCLE_CLASSES, Cycle + +# csv_import : max id of cycles +# ( because of csv import performance, we make a deposit/a guess of cycle id number +# to avoid conflict of other csv import thread/job ) +csv_import_cycle_max_id = None + +# index column in df +INDEX_COL = '__INDEX__' +CYCLE_TIME_COL = '__time__' + +# file index col in df +FILE_IDX_COL = '__FILE_INDEX__' + +# max insert record per job +RECORD_PER_COMMIT = 10_000 + +# range of time per sql + +# N/A value lists +PANDAS_DEFAULT_NA = {'#N/A', '#N/A N/A', '#NA', '-1.#IND', '-1.#QNAN', '-NaN', '-nan', '1.#IND', '1.#QNAN', '', + 'N/A', 'NA', 'NULL', 'NaN', 'n/a', 'nan', 'null'} +NA_VALUES = {'na', '-', '--', '---', '#NULL!', '#REF!', '#VALUE!', '#NUM!', '#NAME?', '0/0'} +INF_VALUES = {'Inf', 'Infinity', '1/0', '#DIV/0!'} +INF_NEG_VALUES = {'-Inf', '-Infinity', '-1/0'} + +ALL_SYMBOLS = set(PANDAS_DEFAULT_NA | NA_VALUES | INF_VALUES | INF_NEG_VALUES) +SPECIAL_SYMBOLS = ALL_SYMBOLS - {'-'} +IS_ERROR_COL = '___ERR0R___' + + +@log_execution_time('[DATA IMPORT]') +def import_data(df, proc_id, get_date_col, cycle_cls, dic_sensor, dic_sensor_cls, dic_substring_sensors): + cycles_len = len(df) + if not cycles_len: + return 0 + + # get available cycle ids from db + current_id = set_cycle_max_id(cycles_len) + + # set ids for df + start_cycle_id = current_id + 1 + df = set_cycle_ids_to_df(df, start_cycle_id) + + cycle_vals = gen_insert_cycle_values(df, proc_id, cycle_cls, get_date_col) + + # insert cycles + # get cycle and sensor columns for insert sql + cycle_sql_params = get_insert_params(get_cycle_columns()) + sql_insert_cycle = gen_bulk_insert_sql(cycle_cls.__table__.name, *cycle_sql_params) + + # run in threads + # pipeline = queue.Queue() + # q_output = queue.Queue() + # with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: + # executor.submit(gen_sensor_data_in_thread, pipeline, df, dic_sensor, dic_sensor_cls, dic_substring_sensors) + # executor.submit(insert_data_in_thread, pipeline, q_output, cycle_vals, sql_insert_cycle) + # + # commit_error = q_output.get() + + # run in main thread + sensor_vals = [] + for col_name, sensor in dic_sensor.items(): + sensor_vals += gen_sensor_data(df, sensor, col_name, dic_sensor_cls, dic_substring_sensors) + + commit_error = insert_data_to_db(sensor_vals, cycle_vals, sql_insert_cycle) + + if isinstance(commit_error, Exception): + set_cycle_max_id(-cycles_len) + return commit_error + + return cycles_len + + +@log_execution_time() +def gen_dic_sensor_n_cls(proc_id, dic_use_cols): + # sensor classes + dic_sensor = {} + dic_sensor_cls = {} + sensors = Sensor.get_sensor_by_col_names(proc_id, dic_use_cols) + for sensor in sensors: + dic_sensor[sensor.column_name] = sensor + dic_sensor_cls[sensor.column_name] = find_sensor_class(sensor.id, sensor.type) + + return dic_sensor, dic_sensor_cls + + +@log_execution_time() +def gen_substring_column_info(proc_id, dic_sensor): + # substring dic + dic_substring_sensors = defaultdict(list) + for col_name, sensor in dic_sensor.items(): + candidates = Sensor.get_substring_sensors(proc_id, sensor.id, col_name) + for candidate in candidates: + substr_check_res = get_from_to_substring_col(candidate) + if substr_check_res: + dic_substring_sensors[col_name].append(candidate) + + return dic_substring_sensors + + +@log_execution_time() +def gen_data_type_list(columns, data_types, get_date_col, auto_increment_col=None): + ints = [] + reals = [] + dates = [] + texts = [] + for col, data_type in zip(columns, data_types): + if DataType[data_type] is DataType.INTEGER: + ints.append(col) + elif DataType[data_type] is DataType.REAL: + reals.append(col) + elif DataType[data_type] is DataType.DATETIME: + dates.append(col) + else: + texts.append(col) + + return {'get_date_col': get_date_col, + 'auto_increment_col': auto_increment_col or get_date_col, + 'int_type_cols': ints, + 'real_type_cols': reals, + 'date_type_cols': dates, + 'text_type_cols': texts, + } + + +# -------------------------- Factory data import ----------------------------- + + +@log_execution_time() +def gen_cols_info(proc_cfg: CfgProcess): + """generate import columns, data types prepare for factory data import + :rtype: dict + :param proc_cfg: + :return: + """ + + columns = [] + data_types = [] + get_date_col = None + auto_increment_col = None + for col in proc_cfg.columns: + columns.append(col.column_name) + data_types.append(col.data_type) + + # get date + if col.is_get_date: + get_date_col = col.column_name + + # auto incremental column + if col.is_auto_increment: + auto_increment_col = col.column_name + + # generate data type list + dic_cols_info = gen_data_type_list(columns, data_types, get_date_col, auto_increment_col) + return dic_cols_info + + +@log_execution_time() +def update_or_create_constant_by_type(const_type, value=0): + try: + CfgConstant.create_or_update_by_type(const_type=const_type, const_value=value) + except Exception: + traceback.print_exc() + return False + + return True + + +# -------------------------- Factory past 1 days data import ----------------------------- + + +@log_execution_time() +def check_db_con(db_type, host, port, dbname, schema, username, password): + parsed_int_port = parse_int_value(port) + if parsed_int_port is None and db_type.lower() != DBType.SQLITE.name.lower(): + return False + + #  オブジェクトを初期化する + db_source_detail = CfgDataSourceDB() + db_source_detail.host = host + db_source_detail.port = parsed_int_port + db_source_detail.dbname = dbname + db_source_detail.schema = schema + db_source_detail.username = username + db_source_detail.password = password + + db_source = CfgDataSource() + db_source.db_detail = db_source_detail + db_source.type = db_type + + # 戻り値の初期化 + result = False + + # コネクションをチェックする + with DbProxy(db_source) as db_instance: + if db_instance.is_connected: + result = True + + return result + + +@log_execution_time() +def write_error_trace(df_error: DataFrame, proc_name, file_path=None, ip_address=None): + if not len(df_error): + return df_error + + time_str = convert_time(datetime.now(), format_str=DATE_FORMAT_STR_ONLY_DIGIT)[4:-3] + ip_address = get_ip_address() + ip_address = f'_{ip_address}' if ip_address else '' + + base_name = f'_{get_basename(file_path)}' if file_path else '' + + file_name = f'{proc_name}{base_name}_{time_str}{ip_address}.txt' + full_path = os.path.join(get_error_trace_path(), file_name) + make_dir_from_file_path(full_path) + + df_error.to_csv(full_path, sep=CsvDelimiter.TSV.value, header=None, index=False) + + return df_error + + +@log_execution_time() +def write_duplicate_import(df: DataFrame, file_name_elements: List): + if not len(df): + return df + + file_name = '_'.join([element for element in file_name_elements if element]) + export_file_name = f'{file_name}.txt' + full_path = os.path.join(get_error_trace_path(), export_file_name) + # make folder + make_dir_from_file_path(full_path) + + df.to_csv(full_path, sep=CsvDelimiter.TSV.value, header=None, index=False) + + return df + + +@log_execution_time() +def write_error_import(df_error: DataFrame, proc_name, file_path=None, error_file_delimiter=CsvDelimiter.CSV.value, + csv_directory=None): + if not len(df_error): + return df_error + + if csv_directory: + file_paths = split_path_to_list(file_path) + csv_directories = split_path_to_list(csv_directory) + file_name = file_paths[-1] + folders = file_paths[len(csv_directories):-1] + else: + time_str = convert_time(format_str=DATE_FORMAT_STR_ONLY_DIGIT)[4:-3] + file_name = time_str + error_file_delimiter + folders = [] + + full_path = os.path.join(get_error_import_path(), proc_name, *folders, file_name) + make_dir_from_file_path(full_path) + + df_error.to_csv(full_path, sep=error_file_delimiter, index=False) + + return df_error + + +def get_latest_records(proc_id, dic_sensors, get_date_col): + cycle_cls = find_cycle_class(proc_id) + cycle_ids = cycle_cls.get_latest_cycle_ids(proc_id) + df_blank = pd.DataFrame({col: [] for col in [INDEX_COL] + list(dic_sensors)}) + if not cycle_ids: + return df_blank + + is_first = True + col_names = list(dic_sensors) + dfs = [] + for cols in chunks(col_names, 50): + records = get_sensor_values(proc_id, cols, dic_sensors, cycle_cls, cycle_ids=cycle_ids, get_time_col=is_first) + is_first = False + + if not records: + return df_blank + + df = pd.DataFrame(records) + dfs.append(df) + + dfs = [_df.set_index(INDEX_COL) for _df in dfs] + df = pd.concat(dfs, ignore_index=False, axis=1) + df.sort_values(CYCLE_TIME_COL, inplace=True) + if get_date_col in col_names: + df.drop(CYCLE_TIME_COL, axis=1, inplace=True) + else: + df.rename({CYCLE_TIME_COL: get_date_col}, inplace=True) + + return df + + +def gen_error_output_df(csv_file_name, dic_sensors, df_error, df_db, error_msgs=None): + db_len = len(df_db) + df_db = df_db.append(df_error, ignore_index=True) + columns = df_db.columns.tolist() + + # error data + new_row = columns + df_db = add_row_to_df(df_db, columns, new_row, db_len) + + new_row = ('column name/sample data (first 10 & last 10)',) + df_db = add_row_to_df(df_db, columns, new_row, db_len) + + new_row = ('Data File', csv_file_name) + df_db = add_row_to_df(df_db, columns, new_row, db_len) + + new_row = ('',) + df_db = add_row_to_df(df_db, columns, new_row, db_len) + + # data in db + new_row = columns + df_db = add_row_to_df(df_db, columns, new_row) + + new_row = ('column name/sample data (latest 5)',) + df_db = add_row_to_df(df_db, columns, new_row) + + new_row = [DataType(dic_sensors[col_name].type).name for col_name in columns if col_name in dic_sensors] + df_db = add_row_to_df(df_db, columns, new_row) + + new_row = ('data type',) + df_db = add_row_to_df(df_db, columns, new_row) + + new_row = ('Database',) + df_db = add_row_to_df(df_db, columns, new_row) + + new_row = ('',) + df_db = add_row_to_df(df_db, columns, new_row) + + new_row = ('',) + df_db = add_row_to_df(df_db, columns, new_row) + + if isinstance(error_msgs, (list, tuple)): + error_msg = '|'.join(error_msgs) + else: + error_msg = error_msgs + + new_row = ('Error Type', error_msg or DATA_TYPE_ERROR_MSG) + df_db = add_row_to_df(df_db, columns, new_row) + + return df_db + + +def gen_duplicate_output_df(dic_use_cols, df_duplicate, csv_file_name=None, table_name=None, error_msgs=None): + # db_name: if factory db -> db name + # else if csv -> file name + columns = df_duplicate.columns.tolist() + + # duplicate data + new_row = columns + df_output = add_row_to_df(df_duplicate, columns, new_row) + + new_row = (f'column name/duplicate data (total: {len(df_duplicate)} rows)',) + df_output = add_row_to_df(df_output, columns, new_row) + + new_row = ('',) + df_output = add_row_to_df(df_output, columns, new_row) + + new_row = [dic_use_cols[col_name] for col_name in columns if col_name in dic_use_cols] + df_output = add_row_to_df(df_output, columns, new_row) + + new_row = ('data type',) + df_output = add_row_to_df(df_output, columns, new_row) + + new_row = ('',) + df_output = add_row_to_df(df_output, columns, new_row) + + if csv_file_name: + new_row = ('Data File', csv_file_name) + df_output = add_row_to_df(df_output, columns, new_row) + + if table_name: + new_row = ('Table name', table_name) + df_output = add_row_to_df(df_output, columns, new_row) + + new_row = ('',) + df_output = add_row_to_df(df_output, columns, new_row) + + new_row = ('',) + df_output = add_row_to_df(df_output, columns, new_row) + + if isinstance(error_msgs, (list, tuple)): + error_msg = '|'.join(error_msgs) + else: + error_msg = error_msgs + + new_row = ('Error Type', error_msg or DATA_TYPE_DUPLICATE_MSG) + df_output = add_row_to_df(df_output, columns, new_row) + + return df_output + + +def add_row_to_df(df, columns, new_row, pos=0): + df_temp = pd.DataFrame({columns[i]: new_row[i] for i in range(len(new_row))}, index=[pos]) + df = pd.concat([df.iloc[0:pos], df_temp, df.iloc[pos:]]).reset_index(drop=True) + + return df + + +@log_execution_time() +def get_new_adding_columns(proc, dic_use_cols): + proc_id = proc.id + + # exist sensors + created_at = get_current_timestamp() + dic_exist_sensor = {s.column_name: s for s in proc.sensors} + missing_sensors = [] + for col_name, data_type in dic_use_cols.items(): + # already exist + if dic_exist_sensor.get(col_name): + continue + + data_type_obj = DataType[data_type] + if data_type_obj is DataType.DATETIME: + data_type_obj = DataType.TEXT + + sensor = dict(process_id=proc_id, column_name=col_name, type=data_type_obj.value, created_at=created_at) + missing_sensors.append(sensor) + + return missing_sensors + + +@log_execution_time() +def commit_db_instance(db_instance): + # commit changes to db + db_instance.connection.commit() + + # clear cache + set_all_cache_expired() + + +def csv_data_with_headers(csv_file_name, data_src): + efa_header_exists = CfgConstant.get_efa_header_flag(data_src.id) + read_directly_ok = True + if efa_header_exists: + try: + # csv delimiter + csv_delimiter = get_csv_delimiter(data_src.delimiter) + + # read file directly to get Line, Machine, Process + csv_reader = read_data(csv_file_name, end_row=5, delimiter=csv_delimiter, do_normalize=False) + next(csv_reader) + + row_line = next(csv_reader) # 2nd row + line = normalize_str(row_line[1]) # 2nd cell + + row_process = next(csv_reader) # 3rd row + process = normalize_str(row_process[1]) # 2nd cell + + row_machine = next(csv_reader) # 4th row + machine = normalize_str(row_machine[1]) # 2nd cell + + etl_headers = { + WR_HEADER_NAMES: [EFAColumn.Line.name, EFAColumn.Process.name, EFAColumn.Machine.name], + WR_VALUES: [line, process, machine], + } + return etl_headers[WR_HEADER_NAMES], etl_headers[WR_VALUES] + except Exception: + read_directly_ok = False + traceback.print_exc() + + # if there is no flag in DB or failed to read file directly -> call R script + save flag + if not efa_header_exists or not read_directly_ok: + csv_inst, _ = chw.get_file_info_py(csv_file_name) + if isinstance(csv_inst, Exception): + return csv_inst + + if csv_inst is None: + return [], [] + + etl_headers = chw.get_etl_headers(csv_inst) + + # save flag to db if header exists + efa_header_exists = chw.get_efa_header_flag(csv_inst) + if efa_header_exists: + CfgConstant.create_or_update_by_type(const_type=CfgConstantType.EFA_HEADER_EXISTS.name, + const_name=data_src.id, + const_value=EFA_HEADER_FLAG) + + return etl_headers[WR_HEADER_NAMES], etl_headers[WR_VALUES] + + +# -------------------------- shutdown app job ----------------------------- +@log_execution_time() +def add_shutdown_app_job(): + # delete process data from universal db + shutdown_app_job_id = JobType.SHUTDOWN_APP.name + scheduler.add_job( + shutdown_app_job_id, shutdown_app_job, + trigger=DateTrigger(run_date=datetime.now().astimezone(utc), timezone=utc), + replace_existing=True, + kwargs=dict( + _job_id=shutdown_app_job_id, + _job_name=JobType.SHUTDOWN_APP.name, + ) + ) + + +@scheduler_app_context +def shutdown_app_job(_job_id=None, _job_name=None, *args, **kwargs): + """ scheduler job to shutdown app + + Keyword Arguments: + _job_id {[type]} -- [description] (default: {None}) + _job_name {[type]} -- [description] (default: {None}) + """ + gen = waiting_for_job_done(*args, **kwargs) + send_processing_info(gen, JobType.SHUTDOWN_APP, is_check_disk=False) + + +@log_execution_time() +def waiting_for_job_done(): + """pause scheduler and wait for all other jobs done. + + Arguments: + proc_id {[type]} -- [description] + + Keyword Arguments: + db_id {[type]} -- [description] (default: {None}) + + Yields: + [type] -- [description] + """ + yield 0 + + from histview2.common.scheduler import dic_running_job, scheduler + import time + + with lock: + try: + scheduler.pause() + except Exception: + pass + + start_time = time.time() + percent = 0 + shutdown_job = {JobType.SHUTDOWN_APP.name} + while True: + running_jobs = set(dic_running_job.keys()) + if not running_jobs.difference(shutdown_job): + print('///////////// ELIGIBLE TO SHUTDOWN APP ///////////') + # notify frontend to stop main thread + background_announcer.announce(True, AnnounceEvent.SHUT_DOWN.name) + break + + # show progress + percent = min(percent + 5, 99) + yield percent + + # check timeout: 2 hours + if time.time() - start_time > 7200: + break + + # sleep 5 seconds and wait + time.sleep(5) + + yield 100 + + +@log_execution_time() +def save_sensors(sensors): + # sensor commit + if not sensors: + return + + with lock: + db.session.execute(Sensor.__table__.insert(), sensors) + # db.session.bulk_insert_mappings(Sensor, sensors) + db.session.commit() + + +@log_execution_time() +def strip_special_symbol(data, is_dict=False): + # TODO: convert to dataframe than filter is faster , but care about generation purpose , + # we just need to read some rows + iter_func = lambda x: x + if is_dict: + iter_func = lambda x: x.values() + + for row in data: + is_ng = False + if not row: + continue + for val in iter_func(row): + if str(val).lower() in SPECIAL_SYMBOLS: + is_ng = True + break + + if not is_ng: + yield row + + +@log_execution_time() +def set_cycle_ids_to_df(df: DataFrame, start_cycle_id): + """ + reset new cycle id to save to db + :param df: + :param start_cycle_id: + :return: + """ + df.reset_index(drop=True, inplace=True) + df.index = df.index + start_cycle_id + return df + + +def gen_cycle_data(cycle_id, proc_id, cycle_time, created_at): + """ + vectorize function , do not use decorator + :param cycle_id: + :param cycle_time: + :param proc_id: + :param created_at: + :return: + """ + return dict(id=cycle_id, process_id=proc_id, time=cycle_time, created_at=created_at) + + +def gen_sensors_data(cycle_id, sensor_id, sensor_val, created_at): + """ + vectorize function , do not use decorator + :param cycle_id: + :param sensor_id: + :param sensor_val: + :param created_at: + :return: + """ + return dict(cycle_id=cycle_id, sensor_id=sensor_id, value=sensor_val, created_at=created_at) + + +@log_execution_time() +def gen_import_job_info(job_info, save_res, start_time=None, end_time=None, imported_count=0, err_cnt=0, err_msgs=None): + # start time + if job_info.last_cycle_time: + job_info.first_cycle_time = job_info.last_cycle_time + else: + job_info.first_cycle_time = start_time + + # end time + job_info.last_cycle_time = end_time + + if isinstance(save_res, Exception): + job_info.exception = save_res + job_info.status = JobStatus.FATAL + else: + if imported_count: + job_info.row_count = imported_count + job_info.committed_count = imported_count + else: + job_info.row_count = save_res + job_info.committed_count = save_res + + if job_info.err_msg or err_cnt > 0 or err_msgs: + job_info.status = JobStatus.FAILED + else: + job_info.status = JobStatus.DONE + + # set msg + if job_info.status == JobStatus.FAILED: + if not err_msgs: + msg = DATA_TYPE_ERROR_MSG + job_info.data_type_error_cnt += err_cnt + elif isinstance(err_msgs, (list, tuple)): + msg = ','.join(err_msgs) + else: + msg = err_msgs + + if job_info.err_msg: + job_info.err_msg += msg + else: + job_info.err_msg = msg + + return job_info + + +@log_execution_time() +def validate_data(df: DataFrame, dic_use_cols, na_vals, exclude_cols=None): + """ + validate data type, NaN values... + :param df: + :param dic_use_cols: + :param na_vals: + :param exclude_cols: + :return: + """ + + init_is_error_col(df) + + if exclude_cols is None: + exclude_cols = [] + + exclude_cols.append(IS_ERROR_COL) + + # string + object + category + float_cols = df.select_dtypes(include=['float']).columns.tolist() + int_cols = df.select_dtypes(include=['integer']).columns.tolist() + for col_name in df.columns: + if col_name in exclude_cols: + continue + + if col_name not in dic_use_cols: + continue + + # do nothing with int column + if col_name in int_cols: + continue + + # data type that user chose + user_data_type = dic_use_cols[col_name] + + # do nothing with float column + if col_name in float_cols and user_data_type != DataType.INTEGER.name: + continue + + # convert inf , -inf to Nan + nan, inf_neg_val, inf_val = return_inf_vals(user_data_type) + if col_name in float_cols and user_data_type == DataType.INTEGER.name: + df.loc[df[col_name].isin([float('inf'), float('-inf')]), col_name] = nan + non_na_vals = df[col_name].dropna() + if len(non_na_vals): + df.loc[non_na_vals.index, col_name] = df.loc[non_na_vals.index, col_name].astype('Int64') + + continue + + # strip quotes and spaces + dtype_name = df[col_name].dtype.name + if user_data_type in [DataType.INTEGER.name, DataType.REAL.name]: + vals = df[col_name].copy() + + # convert numeric values + numerics = pd.to_numeric(vals, errors='coerce') + df[col_name] = numerics + + # strip quote space then convert non numeric values + non_num_idxs = numerics.isna() + non_numerics = vals.loc[non_num_idxs].dropna() + if len(non_numerics): + non_num_idxs = non_numerics.index + non_numerics = non_numerics.astype(str).str.strip("'").str.strip() + + # convert non numeric again + numerics = pd.to_numeric(non_numerics, errors='coerce') + df.loc[non_num_idxs, col_name] = numerics + + # set error for non numeric values + non_num_idxs = numerics.isna() + for idx, is_true in non_num_idxs.items(): + if not is_true: + continue + + if vals.at[idx] in na_vals: + df.at[idx, col_name] = nan + elif vals.at[idx] in INF_VALUES: + df.at[idx, col_name] = inf_val + elif vals.at[idx] in INF_NEG_VALUES: + df.at[idx, col_name] = inf_neg_val + else: + df.at[idx, IS_ERROR_COL] = 1 + + try: + if len(non_num_idxs): + pd.to_numeric(df.loc[non_num_idxs.index, col_name], errors='raise') + except Exception as ex: + logger.exception(ex) + + # replace Inf --> None + if user_data_type == DataType.INTEGER.name: + df.loc[df[col_name].isin([float('inf'), float('-inf')]), col_name] = nan + + elif user_data_type == DataType.TEXT.name: + idxs = df[col_name].dropna().index + if dtype_name == 'object': + df.loc[idxs, col_name] = df.loc[idxs, col_name].astype(str).str.strip("'").str.strip() + elif dtype_name == 'string': + df.loc[idxs, col_name] = df.loc[idxs, col_name].str.strip("'").str.strip() + else: + # convert to string before insert to database + df.loc[idxs, col_name] = df.loc[idxs, col_name].astype(str) + continue + + if len(idxs): + conditions = [df[col_name].isin(na_vals), + df[col_name].isin(INF_VALUES), + df[col_name].isin(INF_NEG_VALUES)] + return_vals = [nan, inf_val, inf_neg_val] + + df[col_name] = np.select(conditions, return_vals, df[col_name]) + + +@log_execution_time() +def add_new_col_to_df(df: DataFrame, col_name, value): + """ + add new value as a new column in dataframe , but avoid duplicate column name. + :param df: + :param col_name: + :param value: + :return: + """ + columns = list(df.columns) + # avoid duplicate column name + while col_name in columns: + col_name = '_' + col_name + + df[col_name] = value + + return col_name + + +def return_inf_vals(data_type): + if data_type == DataType.REAL.name: + return np.nan, float('-inf'), float('inf') + elif data_type == DataType.INTEGER.name: + return pd.NA, pd.NA, pd.NA + + return None, '-inf', 'inf' + + +@log_execution_time() +def data_pre_processing(df, orig_df, dic_use_cols, na_values=None, exclude_cols=None): + if na_values is None: + na_values = PANDAS_DEFAULT_NA | NA_VALUES + + # string parse + cols = get_object_cols(df) + df[cols] = df[cols].astype(str) + cols += get_string_cols(df) + + # normalization + for col in cols: + normalize_df(df, col) + + # parse data type + validate_data(df, dic_use_cols, na_values, exclude_cols) + + # write to file + df_error = orig_df.loc[df.eval(f'{IS_ERROR_COL} == 1')] + + # remove status column ( no need anymore ) + df.drop(df[df[IS_ERROR_COL] == 1].index, inplace=True) + df.drop(IS_ERROR_COL, axis=1, inplace=True) + + return df_error + + +@log_execution_time() +def get_from_to_substring_col(sensor): + substr_regex = re.compile(SUB_STRING_REGEX) + from_to_pos = substr_regex.match(sensor.column_name) + if not from_to_pos: + return None + + from_char = int(from_to_pos[1]) - 1 + to_char = int(from_to_pos[2]) + substr_cls = find_sensor_class(sensor.id, DataType(sensor.type)) + + return substr_cls, from_char, to_char + + +@log_execution_time() +def gen_substr_data(substr_sensor, df, col_name): + """ + generate data for sub string column from original data + :param substr_sensor: + :param df: + :param col_name: + :return: + """ + substr_check_res = get_from_to_substring_col(substr_sensor) + if not substr_check_res: + return None, None + + substr_cls, from_char, to_char = substr_check_res + + sub_col_name = add_new_col_to_df(df, f'{col_name}_{from_char}_{to_char}', df[col_name].str[from_char:to_char]) + + # # remove blank values (we need to insert the same with proclink, so do not move blank) + # df_insert = df[df[sub_col_name] != ''] + # if not len(df_insert): + # return None, None + + # gen insert data + sensor_vals = gen_insert_sensor_values(df, substr_sensor.id, sub_col_name) + + return substr_cls, sensor_vals + + +@log_execution_time() +def get_data_without_na(data): + valid_rows = [] + for row in data: + # exclude_na = False not in [col not in ALL_SYMBOLS for col in row] + if any([val in ALL_SYMBOLS for val in row]): + continue + + valid_rows.append(row) + return valid_rows + + +def get_string_cols(df: DataFrame): + return [col for col in df.columns if df[col].dtype.name.lower() == 'string'] + + +def get_object_cols(df: DataFrame): + return [col for col in df.columns if df[col].dtype.name.lower() == 'object'] + + +@log_execution_time('[CONVERT DATE TIME TO UTC') +def convert_df_col_to_utc(df, get_date_col, is_tz_inside, utc_time_offset): + if is_tz_inside: + return df[get_date_col].dt.tz_convert('UTC') + + return df[get_date_col] - utc_time_offset + + +@log_execution_time() +def convert_df_datetime_to_str(df: DataFrame, get_date_col): + return df[get_date_col].dt.strftime(DATE_FORMAT_STR) + + +@log_execution_time() +def validate_datetime(df: DataFrame, date_col, is_strip=True, add_is_error_col=True, null_is_error=True): + dtype_name = df[date_col].dtype.name + if dtype_name == 'object': + df[date_col] = df[date_col].astype(str) + elif dtype_name != 'string': + return + + # for csv data + if is_strip: + df[date_col] = df[date_col].str.strip("'").str.strip() + + # convert to datetime value + if not null_is_error: + idxs = df[date_col].notna() + + df[date_col] = pd.to_datetime(df[date_col], errors='coerce') # failed records -> pd.NaT + + # mark error records + if add_is_error_col: + init_is_error_col(df) + + if null_is_error: + df[IS_ERROR_COL] = np.where(pd.isna(df[date_col]), 1, df[IS_ERROR_COL]) + else: + df_temp = df.loc[idxs, [date_col, IS_ERROR_COL]] + # df.loc[idxs, IS_ERROR_COL] = np.where(pd.isna(df.loc[idxs, date_col]), 1, df.loc[idxs, IS_ERROR_COL]) + df_temp[IS_ERROR_COL] = np.where(pd.isna(df_temp[date_col]), 1, df_temp[IS_ERROR_COL]) + df.loc[idxs, IS_ERROR_COL] = df_temp + + +def init_is_error_col(df: DataFrame): + if IS_ERROR_COL not in df.columns: + df[IS_ERROR_COL] = 0 + + +@log_execution_time() +def set_cycle_max_id(next_use_id_count): + """ get cycle max id to avoid conflict cycle id + """ + global csv_import_cycle_max_id + with lock: + # when app start get max id of all tables + if csv_import_cycle_max_id is None: + csv_import_cycle_max_id = 0 + max_id = max([cycle_cls.get_max_id() for cycle_cls in CYCLE_CLASSES]) + else: + max_id = csv_import_cycle_max_id + + csv_import_cycle_max_id = max_id + next_use_id_count + return max_id + + +@log_execution_time() +def check_update_time_by_changed_tz(proc_cfg: CfgProcess, time_zone=None): + if time_zone is None: + time_zone = tz.tzutc() + + use_os_tz = proc_cfg.data_source.db_detail.use_os_timezone + # check use ose time zone + if check_timezone_changed(proc_cfg.id, use_os_tz): + # convert to local or convert from local + if use_os_tz: + # calculate offset +/-HH:MM + tz_offset = calc_offset_between_two_tz(time_zone, tz.tzlocal()) + else: + tz_offset = calc_offset_between_two_tz(tz.tzlocal(), time_zone) + + if tz_offset is None: + return None + + # update time to new time zone + cycle_cls = find_cycle_class(proc_cfg.id) + with lock: + cycle_cls.update_time_by_tzoffset(proc_cfg.id, tz_offset) + date_sensor = Sensor.get_sensor_by_col_name(proc_cfg.id, proc_cfg.get_date_col()) + + sensor_cls = find_sensor_class(date_sensor.id, DataType(date_sensor.type)) + sensor_cls.update_time_by_tzoffset(proc_cfg.id, date_sensor.id, tz_offset) + db.session.commit() + + # save latest use os time zone flag to db + save_use_os_timezone_to_db(proc_cfg.id, use_os_tz) + + return True + + +@log_execution_time() +def check_timezone_changed(proc_id, yml_use_os_timezone): + """check if use os timezone was changed by user + + Args: + proc_id ([type]): [description] + yml_use_os_timezone ([type]): [description] + + Returns: + [type]: [description] + """ + if yml_use_os_timezone is None: + return False + + db_use_os_tz = CfgConstant.get_value_by_type_name( + CfgConstantType.USE_OS_TIMEZONE.name, proc_id, lambda x: bool(int(x))) + if db_use_os_tz is None: + return False + + if db_use_os_tz == yml_use_os_timezone: + return False + + return True + + +@log_execution_time() +def save_use_os_timezone_to_db(proc_id, yml_use_os_timezone): + """save os timezone to constant table + + Args: + proc_id ([type]): [description] + yml_use_os_timezone ([type]): [description] + + Returns: + [type]: [description] + """ + if not yml_use_os_timezone: + yml_use_os_timezone = False + + CfgConstant.create_or_update_by_type( + const_type=CfgConstantType.USE_OS_TIMEZONE.name, + const_value=yml_use_os_timezone, + const_name=proc_id) + + return True + + +@log_execution_time() +def gen_insert_cycle_values(df, proc_id, cycle_cls, get_date_col): + # created time + created_at = get_current_timestamp() + created_at_col_name = add_new_col_to_df(df, cycle_cls.created_at.key, created_at) + + proc_id_col_name = add_new_col_to_df(df, cycle_cls.process_id.key, proc_id) + is_outlier_col_name = add_new_col_to_df(df, cycle_cls.is_outlier.key, 0) + cycle_vals = df[[proc_id_col_name, get_date_col, is_outlier_col_name, created_at_col_name]].to_records().tolist() + return cycle_vals + + +@log_execution_time() +def insert_data(db_instance, sql, vals): + db_instance.execute_sql_in_transaction(sql, vals) + return True + + +@log_execution_time() +def gen_insert_sensor_values(df_insert, sensor_id, col_name): + sensor_id_col = add_new_col_to_df(df_insert, SensorType.sensor_id.key, sensor_id) + sensor_vals = df_insert[[sensor_id_col, col_name]].to_records().tolist() + + return sensor_vals + + +@log_execution_time() +def gen_bulk_insert_sql(tblname, cols_str, params_str): + sql = f'INSERT INTO {tblname} ({cols_str}) VALUES ({params_str})' + + return sql + + +@log_execution_time() +def get_cycle_columns(): + return Cycle.id.key, Cycle.process_id.key, Cycle.time.key, Cycle.is_outlier.key, Cycle.created_at.key + + +@log_execution_time() +def get_sensor_columns(): + return SensorType.cycle_id.key, SensorType.sensor_id.key, SensorType.value.key + + +@log_execution_time() +def get_insert_params(columns): + cols_str = ','.join(columns) + params_str = ','.join(['?'] * len(columns)) + + return cols_str, params_str + + +@log_execution_time() +def gen_sensor_data(df, sensor, col_name, dic_sensor_cls, dic_substring_sensors): + data = [] + sensor_cls = dic_sensor_cls[col_name] + + df_insert = df.dropna(subset=[col_name])[[col_name]] + if not df_insert.size: + return data + + sensor_vals = gen_insert_sensor_values(df_insert, sensor.id, col_name) + data.append((sensor_cls.__table__.name, sensor_vals)) + + # insert substring columns + substr_sensors = dic_substring_sensors.get(col_name, []) + if substr_sensors: + df_insert[col_name] = df_insert[col_name].astype(str) + + for substr_sensor in substr_sensors: + substr_cls, substr_rows = gen_substr_data(substr_sensor, df_insert, col_name) + if substr_cls and substr_rows: + data.append((substr_cls.__table__.name, substr_rows)) + + return data + + +@log_execution_time() +def insert_data_to_db(sensor_values, cycle_vals, sql_insert_cycle): + try: + with lock: + with DbProxy(gen_data_source_of_universal_db(), True) as db_instance: + # insert cycle + insert_data(db_instance, sql_insert_cycle, cycle_vals) + + # insert sensor + sensor_sql_params = get_insert_params(get_sensor_columns()) + for tblname, vals in sensor_values: + sql_insert_sensor = gen_bulk_insert_sql(tblname, *sensor_sql_params) + insert_data(db_instance, sql_insert_sensor, vals) + + # commit data to database + commit_db_instance(db_instance) + + return None + except Exception as e: + return e + + +# def gen_sensor_data_in_thread(pipeline: Queue, df, dic_sensor, dic_sensor_cls, dic_substring_sensors): +# for col_name, sensor in dic_sensor.items(): +# sensor_cls = dic_sensor_cls[col_name] +# sensor_vals = gen_insert_sensor_values(df, sensor, col_name) +# if sensor_vals: +# pipeline.put((sensor_cls.__table__.name, sensor_vals)) +# # insert substring columns +# for substr_sensor in dic_substring_sensors.get(col_name, []): +# substr_cls, substr_rows = gen_substr_data(substr_sensor, sensor_vals) +# if substr_cls and substr_rows: +# pipeline.put((sensor_cls.__table__.name, sensor_vals)) +# +# # Stop flag +# pipeline.put(None) +# +# +# def insert_data_in_thread(pipeline: Queue, q_output: Queue, cycle_vals, sql_insert_cycle): +# try: +# with lock: +# with DbProxy(gen_data_source_of_universal_db(), True) as db_instance: +# # insert cycle +# insert_data(db_instance, sql_insert_cycle, cycle_vals) +# +# # insert sensor +# sensor_sql_params = get_insert_params(get_sensor_columns()) +# while True: +# data = pipeline.get() +# if data is None: +# break +# +# tblname, vals = data +# +# sql_insert_sensor = gen_bulk_insert_sql(tblname, *sensor_sql_params) +# insert_data(db_instance, sql_insert_sensor, vals) +# +# # commit data to database +# commit_db_instance(db_instance) +# +# q_output.put(None) +# except Exception as e: +# q_output.put(e) + +@log_execution_time() +def get_sensor_values(proc_id, col_names, dic_sensors, cycle_cls, start_tm=None, end_tm=None, cycle_ids=None, + sort_by_time=False, get_time_col=None): + cols = [cycle_cls.id.label(INDEX_COL)] + if get_time_col: + cols.append(cycle_cls.time.label(CYCLE_TIME_COL)) + + data_query = db.session.query(*cols) + data_query = data_query.filter(cycle_cls.process_id == proc_id) + + for col_name in col_names: + sensor = dic_sensors[col_name] + sensor_val_cls = find_sensor_class(sensor.id, DataType(sensor.type), auto_alias=True) + sensor_val = sensor_val_cls.value.label(col_name) + + data_query = data_query.outerjoin( + sensor_val_cls, + and_(sensor_val_cls.cycle_id == cycle_cls.id, sensor_val_cls.sensor_id == sensor.id) + ) + + data_query = data_query.add_columns(sensor_val) + + # chunk + if cycle_ids: + data_query = data_query.filter(cycle_cls.id.in_(cycle_ids)) + else: + data_query = data_query.filter(cycle_cls.time >= start_tm) + data_query = data_query.filter(cycle_cls.time <= end_tm) + + if sort_by_time: + data_query = data_query.order_by(cycle_cls.time) + + records = data_query.all() + return records + + +def get_df_first_n_last(df: DataFrame, first_count=10, last_count=10): + if len(df) <= first_count + last_count: + return df + + return df.loc[df.head(first_count).index.append(df.tail(last_count).index)] diff --git a/histview2/api/setting_module/services/factory_import.py b/histview2/api/setting_module/services/factory_import.py new file mode 100644 index 0000000..12528de --- /dev/null +++ b/histview2/api/setting_module/services/factory_import.py @@ -0,0 +1,663 @@ +from datetime import datetime + +import pandas as pd +from loguru import logger +from pandas import DataFrame + +from histview2.api.setting_module.services.csv_import import remove_duplicates +from histview2.api.setting_module.services.data_import import import_data, save_sensors, get_new_adding_columns, \ + gen_import_job_info, \ + data_pre_processing, convert_df_col_to_utc, convert_df_datetime_to_str, validate_datetime, \ + gen_dic_sensor_n_cls, gen_substring_column_info, check_update_time_by_changed_tz, gen_error_output_df, \ + get_df_first_n_last, write_error_import, write_error_trace, gen_duplicate_output_df, write_duplicate_import +from histview2.api.trace_data.services.proc_link import add_gen_proc_link_job +from histview2.common.common_utils import add_days, convert_time, DATE_FORMAT_STR_FACTORY_DB, add_double_quotes, \ + add_years, DATE_FORMAT_STR_ONLY_DIGIT, get_ip_address +from histview2.common.constants import MSG_DB_CON_FAILED, JobStatus, DataType +from histview2.common.logger import log_execution_time +from histview2.common.pydn.dblib import mysql, mssqlserver, oracle +from histview2.common.pydn.dblib.db_proxy import DbProxy +from histview2.common.scheduler import scheduler_app_context, JobType +from histview2.common.timezone_utils import get_db_timezone, gen_sql, get_time_info, detect_timezone +from histview2.setting_module.models import FactoryImport, CfgProcess, CfgDataSourceDB, JobManagement +from histview2.setting_module.services.background_process import send_processing_info, JobInfo, \ + format_factory_date_to_meta_data +from histview2.trace_data.models import Process, find_cycle_class + +MAX_RECORD = 1_000_000 +SQL_FACTORY_LIMIT = 5_000_000 +SQL_DAY = 8 +SQL_DAYS_AGO = 30 +FETCH_MANY_SIZE = 10_000 + +pd.options.mode.chained_assignment = None # default='warn' + + +@scheduler_app_context +def import_factory_job(_job_id, _job_name, _db_id, _proc_id, _proc_name, *args, **kwargs): + """ scheduler job import factory data + + Keyword Arguments: + _job_id {[type]} -- [description] (default: {None}) + _job_name {[type]} -- [description] (default: {None}) + """ + gen = import_factory(*args, **kwargs) + send_processing_info(gen, JobType.FACTORY_IMPORT, db_code=_db_id, process_id=_proc_id, process_name=_proc_name, + after_success_func=add_gen_proc_link_job) + + +@log_execution_time() +def import_factory(proc_id): + """transform data and then import from factory db to universal db + + Arguments: + proc_id {[type]} -- [description] + + Yields: + [type] -- [description] + """ + # start job + yield 0 + + # get process id in edge db + proc_cfg: CfgProcess = CfgProcess.query.get(proc_id) + data_src: CfgDataSourceDB = CfgDataSourceDB.query.get(proc_cfg.data_source_id) + + # check db connection + check_db_connection(data_src) + + # process info + proc = Process.get_or_create_proc(proc_id=proc_id, proc_name=proc_cfg.name) + + # columns info + proc_name = proc_cfg.name + column_names = [col.column_name for col in proc_cfg.columns] + auto_increment_col = proc_cfg.get_auto_increment_col_else_get_date() + auto_increment_idx = column_names.index(auto_increment_col) + dic_use_cols = {col.column_name: col.data_type for col in proc_cfg.columns} + + # cycle class + cycle_cls = find_cycle_class(proc_id) + + # check new adding column, save. + missing_sensors = get_new_adding_columns(proc, dic_use_cols) + save_sensors(missing_sensors) + + # sensor classes + dic_sensor, dic_sensor_cls = gen_dic_sensor_n_cls(proc_id, dic_use_cols) + + # substring sensors info + dic_substring_sensors = gen_substring_column_info(proc_id, dic_sensor) + + # get date time column + get_date_col = proc_cfg.get_date_col() + + # last import date + last_import = FactoryImport.get_last_import(proc.id, JobType.FACTORY_IMPORT.name) + + if last_import: + filter_time = last_import.import_to + else: + # the first time import data : get minimum time of factory db + filter_time = None + + # convert utc function + dic_tz_info = {col: handle_time_zone(proc_cfg, col) + for col, dtype in dic_use_cols.items() if DataType[dtype] is DataType.DATETIME} + + # get factory max date + fac_max_date, is_tz_col = get_factory_max_date(proc_cfg) + + inserted_row_count = 0 + calc_range_days_func = calc_sql_range_days() + sql_day = SQL_DAY + is_import = True + end_time = None + total_row = 0 + job_info = JobInfo() + job_info.auto_increment_col_timezone = is_tz_col + job_info.target = proc_cfg.name + + # get current job id + t_job_management: JobManagement = JobManagement.get_last_job_of_process(proc_id, JobType.FACTORY_IMPORT.name) + job_id = str(t_job_management.id) if t_job_management else '' + data_source_name = proc_cfg.data_source.name + table_name = proc_cfg.table_name + + while inserted_row_count < MAX_RECORD and is_import: + # get sql range + if end_time: + if total_row: + sql_day = calc_range_days_func(sql_day, total_row) + + start_time, end_time, filter_time = get_sql_range_time(end_time, range_day=sql_day, is_tz_col=is_tz_col) + else: + start_time, end_time, filter_time = get_sql_range_time(filter_time, is_tz_col=is_tz_col) + + # no data in range, stop + if start_time > fac_max_date: + break + + # validate import date range + if end_time >= fac_max_date: + end_time = fac_max_date + is_import = False + + # get data from factory + data = get_factory_data(proc_cfg, column_names, auto_increment_col, start_time, end_time) + if not data: + break + + cols = next(data) + remain_rows = tuple() + for rows in data: + is_import, rows, remain_rows = gen_import_data(rows, remain_rows, auto_increment_idx) + if not is_import: + continue + + # dataframe + df = pd.DataFrame(rows, columns=cols) + + # no records + if not len(df): + continue + + # Convert UTC time + for col, dtype in dic_use_cols.items(): + if DataType[dtype] is not DataType.DATETIME: + continue + + null_is_error = False + if col == get_date_col: + null_is_error = True + + validate_datetime(df, col, is_strip=False, null_is_error=null_is_error) + df[col] = convert_df_col_to_utc(df, col, *dic_tz_info[col]) + df[col] = convert_df_datetime_to_str(df, col) + + # convert types + df = df.convert_dtypes() + + # original df + orig_df = df.copy() + + # data pre-processing + df_error = data_pre_processing(df, orig_df, dic_use_cols, exclude_cols=[get_date_col]) + df_error_cnt = len(df_error) + if df_error_cnt: + df_error_trace = gen_error_output_df(proc_id, proc_name, dic_sensor, get_df_first_n_last(df_error)) + write_error_trace(df_error_trace, proc_cfg.name) + write_error_import(df_error, proc_cfg.name) + + # no records + if not len(df): + continue + + # remove duplicate records which exists DB + df_duplicate = remove_duplicates(df, orig_df, proc_id, get_date_col) + df_duplicate_cnt = len(df_duplicate) + if df_duplicate_cnt: + write_duplicate_records_to_file_factory(df_duplicate, data_source_name, table_name, dic_use_cols, + proc_cfg.name, job_id) + + # import data + save_res = import_data(df, proc_id, get_date_col, cycle_cls, dic_sensor, dic_sensor_cls, + dic_substring_sensors) + + # update job info + imported_end_time = rows[-1][auto_increment_idx] + gen_import_job_info(job_info, save_res, start_time, imported_end_time, err_cnt=df_error_cnt) + + # total row of one job + total_row = job_info.row_count + inserted_row_count += total_row + + job_info.calc_percent(inserted_row_count, MAX_RECORD) + + yield job_info + + # raise exception if FATAL error happened + if job_info.status is JobStatus.FATAL: + raise job_info.exception + + # calc range of days to gen sql + logger.info( + f'FACTORY DATA IMPORT SQL(days = {sql_day}, records = {total_row}, range = {start_time} - {end_time})') + + + +@log_execution_time() +def calc_sql_range_days(): + """ + calculate range of days for 1 sql sentence + """ + limit_record = 100_000 + limit_max_day = 256 + limit_min_day = 1 + + prev_day_cnt = 1 + prev_record_cnt = 0 + + def _calc_sql_range_days(cur_day_cnt, cur_record_cnt): + nonlocal prev_day_cnt, prev_record_cnt + + # compare current to previous, get max + if cur_record_cnt >= prev_record_cnt: + rec_cnt = cur_record_cnt + day_cnt = cur_day_cnt + else: + rec_cnt = prev_record_cnt + day_cnt = prev_day_cnt + + # set previous sql + prev_day_cnt = day_cnt + prev_record_cnt = rec_cnt + + # adjust number of days to get data + if rec_cnt > limit_record * 2: + day_cnt //= 2 + elif rec_cnt < limit_record: + day_cnt *= 2 + + # make sure range is 1 ~ 256 days + day_cnt = min(day_cnt, limit_max_day) + day_cnt = max(day_cnt, limit_min_day) + + return day_cnt + + return _calc_sql_range_days + + +@log_execution_time() +def get_sql_range_time(filter_time=None, range_day=SQL_DAY, start_days_ago=SQL_DAYS_AGO, is_tz_col=False): + # if there is no data , this poling is the first time, so get data of n days ago. + limit_date = add_days(days=-start_days_ago) + limit_date = limit_date.replace(hour=0, minute=0, second=0, microsecond=0) + + if filter_time: + filter_time = max(convert_time(filter_time), convert_time(limit_date)) + else: + filter_time = convert_time(limit_date) + + # start time + start_time = convert_time(filter_time, return_string=False) + + # 8 days after + end_time = add_days(start_time, days=range_day) + + # convert to string + start_time = convert_time(start_time, format_str=DATE_FORMAT_STR_FACTORY_DB, only_milisecond=True) + end_time = convert_time(end_time, format_str=DATE_FORMAT_STR_FACTORY_DB, only_milisecond=True) + filter_time = convert_time(filter_time, format_str=DATE_FORMAT_STR_FACTORY_DB, only_milisecond=True) + + if is_tz_col: + start_time += 'Z' + end_time += 'Z' + filter_time += 'Z' + + return start_time, end_time, filter_time + + +@log_execution_time('[FACTORY DATA IMPORT SELECT SQL]') +def get_data_by_range_time(db_instance, get_date_col, column_names, table_name, start_time, end_time, sql_limit): + if isinstance(db_instance, mysql.MySQL): + sel_cols = ','.join(column_names) + else: + table_name = add_double_quotes(table_name) + sel_cols = ','.join([add_double_quotes(col) for col in column_names]) + get_date_col = add_double_quotes(get_date_col) + + # sql + sql = f"{sel_cols} FROM {table_name} WHERE {get_date_col} > '{start_time}' AND {get_date_col} <= '{end_time}'" + sql = f'{sql} ORDER BY {get_date_col}' + + if isinstance(db_instance, mssqlserver.MSSQLServer): + sql = f'SELECT TOP {sql_limit} {sql}' + elif isinstance(db_instance, oracle.Oracle): + sql = f'SELECT * FROM (SELECT {sql}) WHERE ROWNUM <= {sql_limit}' + else: + sql = f'SELECT {sql} LIMIT {sql_limit}' + + logger.info(f'sql: {sql}') + data = db_instance.fetch_many(sql, FETCH_MANY_SIZE) + if not data: + return None + + yield from data + + +@log_execution_time() +def get_factory_data(proc_cfg, column_names, auto_increment_col, start_time, end_time): + """generate select statement and get data from factory db + + Arguments: + proc_id {[type]} -- [description] + db_config_yaml {DBConfigYaml} -- [description] + proc_config_yaml {ProcConfigYaml} -- [description] + """ + # exe sql + with DbProxy(proc_cfg.data_source) as db_instance: + data = get_data_by_range_time(db_instance, auto_increment_col, column_names, proc_cfg.table_name, start_time, + end_time, + SQL_FACTORY_LIMIT) + + if not data: + return None + + for rows in data: + yield tuple(rows) + + +@log_execution_time() +def get_factory_max_date(proc_cfg): + """ + get factory max date + """ + + with DbProxy(proc_cfg.data_source) as db_instance: + # gen sql + get_date_col = add_double_quotes(proc_cfg.get_auto_increment_col_else_get_date()) + orig_tblname = proc_cfg.table_name.strip('\"') + if not isinstance(db_instance, mysql.MySQL): + table_name = add_double_quotes(orig_tblname) + else: + table_name = orig_tblname + + sql = f'select max({get_date_col}) from {table_name}' + _, rows = db_instance.run_sql(sql, row_is_dict=False) + + if not rows: + return None + + out = rows[0][0] + + is_tz_col = db_instance.is_timezone_hold_column(orig_tblname, get_date_col) + out = format_factory_date_to_meta_data(out, is_tz_col) + + return out, is_tz_col + + +SQL_PAST_DAYS_AGO = 1 + + +@log_execution_time() +def get_tzoffset_of_random_record(data_source, table_name, get_date_col): + # exec sql + with DbProxy(data_source) as db_instance: + # get timezone offset + db_timezone = get_db_timezone(db_instance) + + sql = gen_sql(db_instance, table_name, get_date_col) + _, rows = db_instance.run_sql(sql, row_is_dict=False) + + date_val = None + tzoffset_str = None + if rows: + date_val, tzoffset_str = rows[0] + + return date_val, tzoffset_str, db_timezone + + +@scheduler_app_context +def factory_past_data_transform_job(_job_id, _job_name, _db_id, _proc_id, _proc_name, *args, **kwargs): + """ scheduler job import factory data + + Keyword Arguments: + _job_id {[type]} -- [description] (default: {None}) + _job_name {[type]} -- [description] (default: {None}) + """ + gen = factory_past_data_transform(*args, **kwargs) + send_processing_info(gen, JobType.FACTORY_PAST_IMPORT, db_code=_db_id, + process_id=_proc_id, process_name=_proc_name, + after_success_func=add_gen_proc_link_job) + + +@log_execution_time() +def factory_past_data_transform(proc_id): + """transform data and then import from factory db to universal db + + Arguments: + proc_id {[type]} -- [description] + + Yields: + [type] -- [description] + """ + # start job + yield 0 + + # get process id in edge db + proc_cfg: CfgProcess = CfgProcess.query.get(proc_id) + data_src: CfgDataSourceDB = CfgDataSourceDB.query.get(proc_cfg.data_source_id) + + # check db connection + check_db_connection(data_src) + + proc = Process.get_or_create_proc(proc_id=proc_id) + + # columns info + proc_name = proc_cfg.name + column_names = [col.column_name for col in proc_cfg.columns] + auto_increment_col = proc_cfg.get_auto_increment_col_else_get_date() + auto_increment_idx = column_names.index(auto_increment_col) + dic_use_cols = {col.column_name: col.data_type for col in proc_cfg.columns} + + # cycle class + cycle_cls = find_cycle_class(proc_id) + + # check new adding column, save. + missing_sensors = get_new_adding_columns(proc, dic_use_cols) + save_sensors(missing_sensors) + + # sensor classes + dic_sensor, dic_sensor_cls = gen_dic_sensor_n_cls(proc_id, dic_use_cols) + + # substring sensors info + dic_substring_sensors = gen_substring_column_info(proc_id, dic_sensor) + + # get date time column + get_date_col = proc_cfg.get_date_col() + + # last import date + last_import = FactoryImport.get_last_import(proc.id, JobType.FACTORY_PAST_IMPORT.name, is_first_id=True) + + if not last_import: + # check if first time factory import was DONE ! + last_import = FactoryImport.get_first_import(proc.id, JobType.FACTORY_IMPORT.name) + + # the first time import data + if not last_import: + yield 100 + return + + filter_time = last_import.import_from + + if filter_time < convert_time(add_years(years=-1)): + yield 100 + return + + # return if already inserted 2 millions + if cycle_cls.get_count_by_proc_id(proc.id) > 2_000_000: + yield 100 + return + + # is timezone column + is_tz_col = False + if filter_time[-1] == 'Z': + is_tz_col = True + + # calc end time + end_time = convert_time(filter_time, return_string=False) + + # calc start time + start_time = add_days(end_time, days=-SQL_PAST_DAYS_AGO) + + # convert to char format + start_time = format_factory_date_to_meta_data(start_time, is_tz_col) + end_time = format_factory_date_to_meta_data(end_time, is_tz_col) + + # convert utc function + dic_tz_info = {col: handle_time_zone(proc_cfg, col) + for col, dtype in dic_use_cols.items() if DataType[dtype] is DataType.DATETIME} + + # job info + job_info = JobInfo() + job_info.auto_increment_col_timezone = is_tz_col + job_info.target = proc_cfg.name + + # get data from factory + data = get_factory_data(proc_cfg, column_names, auto_increment_col, start_time, end_time) + + # there is no data , return + if not data: + gen_import_job_info(job_info, 0, start_time, end_time) + job_info.auto_increment_col_timezone = is_tz_col + job_info.percent = 100 + yield job_info + return + + # get current job id + t_job_management: JobManagement = JobManagement.get_last_job_of_process(proc_id, JobType.FACTORY_PAST_IMPORT.name) + job_id = str(t_job_management.id) if t_job_management else '' + data_source_name = proc_cfg.data_source.name + table_name = proc_cfg.table_name + + # start import data + cols = next(data) + remain_rows = tuple() + inserted_row_count = 0 + for rows in data: + is_import, rows, remain_rows = gen_import_data(rows, remain_rows, auto_increment_idx) + if not is_import: + continue + + # dataframe + df = pd.DataFrame(rows, columns=cols) + + # no records + if not len(df): + continue + + # Convert UTC time + for col, dtype in dic_use_cols.items(): + if DataType[dtype] is not DataType.DATETIME: + continue + + null_is_error = False + if col == get_date_col: + null_is_error = True + + validate_datetime(df, col, is_strip=False, null_is_error=null_is_error) + df[col] = convert_df_col_to_utc(df, col, *dic_tz_info[col]) + df[col] = convert_df_datetime_to_str(df, col) + + # convert types + df = df.convert_dtypes() + + # original df + orig_df = df.copy() + + # data pre-processing + df_error = data_pre_processing(df, orig_df, dic_use_cols, exclude_cols=[get_date_col]) + df_error_cnt = len(df_error) + if df_error_cnt: + df_error_trace = gen_error_output_df(proc_id, proc_name, dic_sensor, get_df_first_n_last(df_error)) + write_error_trace(df_error_trace, proc_cfg.name) + write_error_import(df_error, proc_cfg.name) + + # remove duplicate records which exists DB + df_duplicate = remove_duplicates(df, orig_df, proc_id, get_date_col) + df_duplicate_cnt = len(df_duplicate) + if df_duplicate_cnt: + write_duplicate_records_to_file_factory(df_duplicate, data_source_name, table_name, dic_use_cols, + proc_cfg.name, job_id) + + # import data + save_res = import_data(df, proc_id, get_date_col, cycle_cls, dic_sensor, dic_sensor_cls, dic_substring_sensors) + + # update job info + imported_end_time = rows[-1][auto_increment_idx] + gen_import_job_info(job_info, save_res, start_time, imported_end_time, err_cnt=df_error_cnt) + + # total row of one job + total_row = job_info.row_count + inserted_row_count += total_row + + job_info.calc_percent(inserted_row_count, MAX_RECORD) + yield job_info + + # raise exception if FATAL error happened + if job_info.status is JobStatus.FATAL: + raise job_info.exception + + # output log + log_str = 'FACTORY PAST DATA IMPORT SQL(days={}, records={}, range={}-{})' + logger.info(log_str.format(SQL_PAST_DAYS_AGO, total_row, start_time, end_time)) + + yield 100 + + +@log_execution_time() +def handle_time_zone(proc_cfg, get_date_col): + # convert utc time func + get_date, tzoffset_str, db_timezone = get_tzoffset_of_random_record(proc_cfg.data_source, proc_cfg.table_name, + get_date_col) + + if tzoffset_str: + # use os time zone + db_timezone = None + else: + detected_timezone = detect_timezone(get_date) + # if there is time offset in datetime value, do not force time. + if detected_timezone is None: + # check and update if use os time zone flag changed + # if tz offset in val date, do not need to force + check_update_time_by_changed_tz(proc_cfg) + + if proc_cfg.data_source.db_detail.use_os_timezone: + # use os time zone + db_timezone = None + + is_tz_inside, _, time_offset = get_time_info(get_date, db_timezone) + + return is_tz_inside, time_offset + + +@log_execution_time() +def check_db_connection(data_src): + # check db connection + with DbProxy(data_src) as db_instance: + if not db_instance.is_connected: + raise Exception(MSG_DB_CON_FAILED) + + +@log_execution_time() +def gen_import_data(rows, remain_rows, auto_increment_idx): + is_allow_import = True + # last fetch + if len(rows) < FETCH_MANY_SIZE: + return is_allow_import, remain_rows + rows, [] + + rows += remain_rows + last_row_idx = len(rows) - 1 + first_row_idx = max(last_row_idx - 1000, 0) + + for i in range(last_row_idx, first_row_idx, -1): + # difference time + if rows[i][auto_increment_idx] != rows[i - 1][auto_increment_idx]: + return is_allow_import, rows[:i], rows[i:] + + # no difference + is_allow_import = False + return is_allow_import, [], rows + + +def write_duplicate_records_to_file_factory(df_duplicate: DataFrame, data_source_name, table_name, dic_use_cols, + proc_name, job_id=None): + error_msg = 'Duplicate Record' + time_str = convert_time(datetime.now(), format_str=DATE_FORMAT_STR_ONLY_DIGIT)[4:-3] + ip_address = get_ip_address() + + df_output = gen_duplicate_output_df(dic_use_cols, get_df_first_n_last(df_duplicate), + table_name=table_name, error_msgs=error_msg) + + write_duplicate_import(df_output, [proc_name, data_source_name, 'Duplicate', job_id, time_str, ip_address]) diff --git a/histview2/api/setting_module/services/filter_settings.py b/histview2/api/setting_module/services/filter_settings.py new file mode 100644 index 0000000..38a544f --- /dev/null +++ b/histview2/api/setting_module/services/filter_settings.py @@ -0,0 +1,77 @@ +from histview2.common.constants import CfgFilterType, RelationShip +from histview2.setting_module.models import CfgFilter, CfgFilterDetail, make_session, get_or_create, CfgProcess, \ + insert_or_update_config, crud_config + + +def get_filter_request_data(params): + process_id = params.get('processId') + filter_id = ''.join(params.get('filterId') or []) + filter_type = params.get('filterType') + filter_name = params.get('filterName') + column_id = params.get('columnName') or None + filter_parent_detail_ids = params.get('filterDetailParentIds') or [] + filter_detail_ids = params.get('fitlerDetailIds') or [] + filter_detail_conds = params.get('filterConditions') or [] + filter_detail_names = params.get('filterDetailNames') or [] + filter_detail_functions = params.get('filterFunctions') or [] + filter_detail_start_froms = params.get('filterStartFroms') or [] + + if not filter_parent_detail_ids: + filter_parent_detail_ids = [None] * len(filter_detail_ids) + if not filter_detail_functions: + filter_detail_functions = [None] * len(filter_detail_ids) + if not filter_detail_start_froms: + filter_detail_start_froms = [None] * len(filter_detail_ids) + + return [process_id, filter_id, filter_type, column_id, + filter_detail_ids, filter_detail_conds, filter_detail_names, + filter_parent_detail_ids, filter_detail_functions, filter_detail_start_froms, filter_name] + + +def save_filter_config(params): + [process_id, filter_id, filter_type, column_id, + filter_detail_ids, filter_detail_conds, filter_detail_names, filter_parent_detail_ids, + filter_detail_functions, filter_detail_start_froms, filter_name] = get_filter_request_data(params) + + with make_session() as meta_session: + cfg_filter = CfgFilter(**{ + 'id': int(filter_id) if filter_id else None, + 'process_id': process_id, + 'name': filter_name, + 'column_id': column_id, + 'filter_type': filter_type, + }) + cfg_filter = insert_or_update_config(meta_session, cfg_filter) + meta_session.commit() + + filter_id = cfg_filter.id # to return to frontend (must) + + # crud filter details + num_details = len(filter_detail_conds) + filter_details = [] + for idx in range(num_details): + filter_detail = CfgFilterDetail(**{ + 'id': int(filter_detail_ids[idx]) if filter_detail_ids[idx] else None, + 'filter_id': cfg_filter.id, + 'name': filter_detail_names[idx], + 'parent_detail_id': filter_parent_detail_ids[idx] or None, + 'filter_condition': filter_detail_conds[idx], + 'filter_function': filter_detail_functions[idx] or None, + 'filter_from_pos': filter_detail_start_froms[idx] or None, + }) + filter_details.append(filter_detail) + + crud_config(meta_session=meta_session, + data=filter_details, + model=CfgFilterDetail, + key_names=CfgFilterDetail.id.key, + parent_key_names=CfgFilterDetail.filter_id.key, + parent_obj=cfg_filter, + parent_relation_key=CfgFilter.filter_details.key, + parent_relation_type=RelationShip.MANY) + return filter_id + + +def delete_cfg_filter_from_db(filter_id): + with make_session() as mss: + CfgFilter.delete_by_id(mss, filter_id) diff --git a/histview2/api/setting_module/services/polling_frequency.py b/histview2/api/setting_module/services/polling_frequency.py new file mode 100644 index 0000000..9903433 --- /dev/null +++ b/histview2/api/setting_module/services/polling_frequency.py @@ -0,0 +1,136 @@ +import time +from datetime import datetime +from typing import List + +from apscheduler.triggers.date import DateTrigger +from apscheduler.triggers.interval import IntervalTrigger +from loguru import logger +from pytz import utc + +from histview2 import scheduler, dic_request_info +from histview2.api.setting_module.services.csv_import import import_csv_job +from histview2.api.setting_module.services.factory_import import import_factory_job, \ + factory_past_data_transform_job +from histview2.api.setting_module.services.process_delete import add_del_proc_job +from histview2.common.common_utils import add_seconds +from histview2.common.constants import CfgConstantType, DBType +from histview2.common.logger import log_execution_time +from histview2.common.scheduler import JobType, remove_jobs, scheduler_app_context, add_job_to_scheduler +from histview2.setting_module.models import CfgConstant, CfgProcess, JobManagement + + +@log_execution_time() +def change_polling_all_interval_jobs(interval_sec, run_now=False): + """ add job for csv and factory import + + Arguments: + interval_sec {[type]} -- [description] + + Keyword Arguments: + target_job_names {[type]} -- [description] (default: {None}) + """ + # target jobs (do not remove factory past data import) + target_jobs = [JobType.CSV_IMPORT, JobType.FACTORY_IMPORT] + + # remove jobs + remove_jobs(target_jobs) + + # check if not run now and interval is zero , quit + if interval_sec == 0 and not run_now: + return + + # add new jobs with new interval + procs: List[CfgProcess] = CfgProcess.query.all() + + for proc_cfg in procs: + add_import_job(proc_cfg, interval_sec=interval_sec, run_now=run_now) + + +def add_import_job(proc_cfg: CfgProcess, interval_sec=None, run_now=None): + if interval_sec is None: + interval_sec = CfgConstant.get_value_by_type_first(CfgConstantType.POLLING_FREQUENCY.name, int) + + if interval_sec: + trigger = IntervalTrigger(seconds=interval_sec, timezone=utc) + else: + trigger = DateTrigger(datetime.now().astimezone(utc), timezone=utc) + + if proc_cfg.data_source.type.lower() == DBType.CSV.value.lower(): + job_name = JobType.CSV_IMPORT.name + import_func = import_csv_job + else: + job_name = JobType.FACTORY_IMPORT.name + import_func = import_factory_job + + # check for last job entry in t_job_management + prev_job = JobManagement.get_last_job_of_process(proc_cfg.id, job_name) + + job_id = f'{job_name}_{proc_cfg.id}' + dic_import_param = dict(_job_id=job_id, _job_name=job_name, + _db_id=proc_cfg.data_source_id, _proc_id=proc_cfg.id, _proc_name=proc_cfg.name, + proc_id=proc_cfg.id) + + add_job_to_scheduler(job_id, job_name, trigger, import_func, run_now, dic_import_param) + + add_idle_mornitoring_job() + + # double check + attempt = 0 + while attempt < 3: + attempt += 1 + scheduler_job = scheduler.get_job(job_id) + last_job = JobManagement.get_last_job_of_process(proc_cfg.id, job_name) + if is_job_added(scheduler_job, prev_job, last_job): + break + else: + add_job_to_scheduler(job_id, job_name, trigger, import_func, run_now, dic_import_param) + logger.info("ADD MISSING JOB: job_id=", job_id) + time.sleep(1) + + +def is_job_added(scheduler_job, prev_job, last_job): + if not scheduler_job: + if (prev_job is None and last_job is None) or (prev_job is not None and last_job.id == prev_job.id): + return False + return True + + +@log_execution_time() +def add_idle_mornitoring_job(): + scheduler.add_job(JobType.IDLE_MORNITORING.name, idle_monitoring, + name=JobType.IDLE_MORNITORING.name, + replace_existing=True, + trigger=IntervalTrigger(seconds=60, timezone=utc), + kwargs=dict(_job_id=JobType.IDLE_MORNITORING.name, _job_name=JobType.IDLE_MORNITORING.name)) + + return True + + +@scheduler_app_context +def idle_monitoring(_job_id=None, _job_name=None): + """ + check if system if idle + + """ + # check last request > now() - 5 minutes + last_request_time = dic_request_info.get('last_request_time', datetime.utcnow()) + if last_request_time > add_seconds(seconds=-5 * 60): + return + + # delete unused processes + add_del_proc_job() + + processes = CfgProcess.get_all() + for proc_cfg in processes: + if proc_cfg.data_source.type.lower() == DBType.CSV.name.lower(): + continue + + job_id = f'{JobType.FACTORY_PAST_IMPORT.name}_{proc_cfg.id}' + logger.info('IDLE_MONITORING', job_id) + dic_import_param = dict(_job_id=job_id, _job_name=JobType.FACTORY_PAST_IMPORT.name, + _db_id=proc_cfg.data_source_id, _proc_id=proc_cfg.id, _proc_name=proc_cfg.name, + proc_id=proc_cfg.id) + scheduler.add_job(job_id, factory_past_data_transform_job, + trigger=DateTrigger(datetime.now().astimezone(utc), timezone=utc), + name=JobType.FACTORY_PAST_IMPORT.name, replace_existing=True, + kwargs=dic_import_param) diff --git a/histview2/api/setting_module/services/process_delete.py b/histview2/api/setting_module/services/process_delete.py new file mode 100644 index 0000000..bca982b --- /dev/null +++ b/histview2/api/setting_module/services/process_delete.py @@ -0,0 +1,94 @@ +from datetime import datetime + +from apscheduler.triggers.date import DateTrigger +from pytz import utc + +from histview2 import db, scheduler +from histview2.common.logger import log_execution_time, log_execution +from histview2.common.scheduler import scheduler_app_context, JobType, remove_jobs +from histview2.setting_module.models import CfgDataSource, make_session +from histview2.setting_module.models import CfgProcess +from histview2.setting_module.services.background_process import send_processing_info +from histview2.trace_data.models import Process + + +@scheduler_app_context +def delete_process_job(_job_id=None, _job_name=None, *args, **kwargs): + """ scheduler job to delete process from db + + Keyword Arguments: + _job_id {[type]} -- [description] (default: {None}) + _job_name {[type]} -- [description] (default: {None}) + """ + gen = delete_process(*args, **kwargs) + send_processing_info(gen, JobType.DEL_PROCESS, db_code=kwargs.get('db_id'), process_id=kwargs.get('proc_id'), is_check_disk=False) + + +@log_execution_time() +def delete_process(): + """ + delete processes + :return: + """ + yield 0 + + missing_procs = get_unused_procs() + + if missing_procs: + proc_id = missing_procs[0] + proc = Process.query.get(proc_id) + proc.delete_proc_detail() + db.session.delete(proc) + db.session.commit() + + yield 100 + + +@log_execution() +def add_del_proc_job(): + missing_procs = get_unused_procs() + + if not missing_procs: + return + + scheduler.add_job( + JobType.DEL_PROCESS.name, delete_process_job, + trigger=DateTrigger(run_date=datetime.now().astimezone(utc), timezone=utc), + replace_existing=True, + kwargs=dict(_job_id=JobType.DEL_PROCESS.name, _job_name=JobType.DEL_PROCESS.name) + ) + + +@log_execution_time() +def delete_proc_cfg_and_relate_jobs(proc_id): + # delete cfg process + deleted = CfgProcess.delete(proc_id=proc_id) + + # remove job relate to that process + if deleted: + # target jobs + target_jobs = [JobType.CSV_IMPORT, JobType.FACTORY_IMPORT, JobType.FACTORY_PAST_IMPORT] + # remove importing job from job queue + remove_jobs(target_jobs, proc_id) + + +@log_execution_time() +def get_unused_procs(): + return list(set([proc.id for proc in Process.get_all_ids()]) - set([proc.id for proc in CfgProcess.get_all_ids()])) + + +def del_data_source(ds_id): + """ + delete data source + :param ds_id: + :return: + """ + with make_session() as meta_session: + ds = meta_session.query(CfgDataSource).get(ds_id) + if not ds: + return + + # delete data + for proc in ds.processes or []: + delete_proc_cfg_and_relate_jobs(proc.id) + meta_session.delete(ds) diff --git a/histview2/api/setting_module/services/save_load_user_setting.py b/histview2/api/setting_module/services/save_load_user_setting.py new file mode 100644 index 0000000..523fd45 --- /dev/null +++ b/histview2/api/setting_module/services/save_load_user_setting.py @@ -0,0 +1,201 @@ +import re +from collections import defaultdict +from itertools import zip_longest + + +class UserSettingDetail: + def __init__(self, dic_vals): + self.id = dic_vals.get('id') + self.name = dic_vals.get('name') + self.value = dic_vals.get('value') + self.type = dic_vals.get('type') + self.level = dic_vals.get('level') + self.genBtnId = dic_vals.get('genBtnId') + self.checked = dic_vals.get('checked') + self.isActiveTab = dic_vals.get('isActiveTab') + self.original_obj = dic_vals + + def convert_to_obj(self): + dic_vals = {key: val for key, val in self.__dict__.items() if key != 'original_obj' and val is not None} + return dic_vals + + def is_checkbox_or_radio(self): + return True if self.type in ('checkbox', 'radio') else False + + +def transform_settings(mapping_groups): + dic_output = {} + for form_name, src_vals, des_vals in mapping_groups: + vals = transform_setting(src_vals, des_vals) + vals = [val.convert_to_obj() for val in vals] + dic_output[form_name] = vals + + return dic_output + + +def transform_setting(src_vals, des_vals): + dic_src_checkboxes, dic_src_others = group_by_name(src_vals) + dic_des_checkboxes, dic_des_others = group_by_name(des_vals) + checkbox_vals = mapping_checkbox_radio(dic_src_checkboxes, dic_des_checkboxes) + other_vals = mapping_others(dic_src_others, dic_des_others) + + return other_vals + checkbox_vals + + +def mapping_checkbox_radio(dic_src, dic_des): + output_vals = [] + all_keys = list(set(list(dic_src) + list(dic_des))) + for name in all_keys: + src_vals = dic_src.get(name, []) + if not src_vals: + src_vals = dic_des.get(name, []) + + output_vals.extend(src_vals) + + return output_vals + + +def get_pair_names(target_name, dic_vals): + if target_name in dic_vals: + return dic_vals[target_name] + + target_name = remove_non_str(target_name) + target_name = target_name.lower() + for name, vals in dic_vals.items(): + name = remove_non_str(name) + name = name.lower() + if vals and 'select' in vals[0].type: + if name == target_name: + return vals + else: + if name in target_name or target_name in name: + return vals + + return [] + + +def mapping_others(dic_src, dic_des): + all_vals = [] + all_keys = list(set(list(dic_src) + list(dic_des))) + for name in all_keys: + group_vals = [] + src_vals = get_pair_names(name, dic_src) + des_vals = get_pair_names(name, dic_des) + + if not src_vals: + continue + + if not des_vals: + des_vals = src_vals + + first_des_val = des_vals[0] + des_id_str, _ = split_str_and_last_number(first_des_val.id) + des_name_str, _ = split_str_and_last_number(first_des_val.name) + for src_val, des_val in zip_longest(src_vals, des_vals): + if src_val is None: + continue + + src_val: UserSettingDetail + des_val: UserSettingDetail + + _, src_name_num = split_str_and_last_number(src_val.name) + _, src_id_num = split_str_and_last_number(src_val.id) + new_obj = UserSettingDetail(first_des_val.__dict__) + new_obj.id = des_id_str + src_id_num + new_obj.name = des_name_str + src_name_num + new_obj.value = src_val.value + group_vals.append(new_obj) + + all_vals += group_vals + + return all_vals + + +def group_by_name(vals): + dic_checkboxes = defaultdict(list) + dic_others = defaultdict(list) + for dic_vals in vals: + setting = UserSettingDetail(dic_vals) + if not setting.name: + continue + + if setting.is_checkbox_or_radio(): + if setting.name == 'cat_filter': + continue + dic_checkboxes[setting.name.lower()].append(setting) + else: + short_name, _ = split_str_and_last_number(setting.name) + short_name = short_name.lower() + dic_others[short_name].append(setting) + + dic_checkboxes = {key: sorted(vals, key=lambda x: x.name) for key, vals in dic_checkboxes.items()} + dic_others = {key: sorted(vals, key=lambda x: x.name) for key, vals in dic_others.items()} + + return dic_checkboxes, dic_others + + +def map_form(dic_src_vals, dic_des_vals): + mapping_groups = [] + if len(dic_src_vals) == len(dic_des_vals): + names = zip(list(dic_src_vals), list(dic_des_vals)) + else: + src_active_form = get_active_tab(dic_src_vals) + names = zip([src_active_form], list(dic_des_vals)) + + for src_name, des_name in names: + mapping_groups.append((des_name, dic_src_vals[src_name], dic_des_vals[des_name])) + + return mapping_groups + + +def map_form_bk(dic_src_vals, dic_des_vals): + mapping_groups = [] + if len(dic_src_vals) == len(dic_des_vals): + names = zip(list(dic_src_vals), list(dic_des_vals)) + for src_name, des_name in names: + mapping_groups.append((des_name, dic_src_vals[src_name], dic_des_vals[des_name])) + return mapping_groups + + for form_name, vals in dic_des_vals.items(): + if form_name in dic_src_vals: + mapping_groups.append((form_name, dic_src_vals[form_name], vals)) + else: + src_vals = [(len(set(form_name) & set(_form_name)), _vals) for _form_name, _vals in dic_src_vals.items()] + src_vals = sorted(src_vals, key=lambda x: x[0])[-1] + + mapping_groups.append((form_name, src_vals[1], vals)) + return mapping_groups + + +def get_active_tab(dic_setting): + tabs = [] + for form_name, vals in dic_setting.items(): + for dic_item in vals: + is_active_tab = dic_item.get('isActiveTab', None) + if is_active_tab is not None: + tabs.append(is_active_tab) + + if tabs: + break + + zip_forms = zip(list(dic_setting), tabs) + for form_name, is_active in zip_forms: + if is_active: + return form_name + + return list(dic_setting.keys())[0] + + +def remove_non_str(val): + return re.sub(r"[-_\d\s]", '', val) + + +def split_str_and_last_number(input_str): + if not input_str: + return [input_str, ''] + + res = re.match(r'^(.*[^0-9])(\d+)$', input_str) + if res is None: + return [input_str, ''] + + return res[1], res[2] diff --git a/histview2/api/setting_module/services/show_latest_record.py b/histview2/api/setting_module/services/show_latest_record.py new file mode 100644 index 0000000..9d919c5 --- /dev/null +++ b/histview2/api/setting_module/services/show_latest_record.py @@ -0,0 +1,366 @@ +import os +from functools import lru_cache +from itertools import islice + +from histview2.api.efa.services.etl import preview_data, detect_file_delimiter +from histview2.api.setting_module.services.csv_import import convert_csv_timezone +from histview2.api.setting_module.services.data_import import strip_special_symbol, validate_datetime +from histview2.api.setting_module.services.factory_import import get_tzoffset_of_random_record +from histview2.common.common_utils import guess_data_types, get_csv_delimiter, get_sorted_files +from histview2.common.constants import DBType, DataType, RelationShip, WR_VALUES, WR_HEADER_NAMES, WR_TYPES +from histview2.common.logger import log_execution_time +from histview2.common.memoize import memoize +from histview2.common.pydn.dblib import mssqlserver, oracle +from histview2.common.pydn.dblib.db_proxy import DbProxy +from histview2.common.services import csv_header_wrapr as chw +from histview2.common.services.csv_content import read_data, gen_data_types, is_normal_csv +from histview2.common.services.jp_to_romaji_utils import to_romaji, change_duplicated_columns +from histview2.common.services.normalization import normalize_list, normalize_big_rows +from histview2.common.timezone_utils import get_time_info +from histview2.setting_module.models import CfgDataSource, CfgProcessColumn, CfgVisualization, \ + make_session, CfgProcess, crud_config +from histview2.setting_module.schemas import VisualizationSchema +from histview2.trace_data.models import Sensor, find_sensor_class + + +def get_latest_records(data_source_id, table_name, limit): + blank_output = dict(cols=[], rows=[]) + if not data_source_id: + return blank_output + + data_source = CfgDataSource.query.get(data_source_id) + if not data_source: + return blank_output + + previewed_files = None + cols_with_types = [] + if data_source.type.lower() == DBType.CSV.name.lower(): + csv_detail = data_source.csv_detail + dic_preview = preview_csv_data(csv_detail.directory, csv_detail.etl_func, csv_detail.delimiter, limit, + return_df=True) + headers = dic_preview.get('header') + data_types = dic_preview.get('dataType') + if headers and data_types: + cols_with_types = gen_cols_with_types(headers, data_types) + + # sort columns + sorted_columns = sorted(csv_detail.csv_columns, key=lambda c: c.order or c.id) + cols = [col.column_name for col in sorted_columns if col.column_name in headers] + + # get rows + df_rows = dic_preview.get('content', None) + previewed_files = dic_preview.get('previewed_files') + else: + cols, df_rows = get_info_from_db(data_source, table_name) + data_types = [gen_data_types(df_rows[col]) for col in cols] + if cols and data_types: + cols_with_types = gen_cols_with_types(cols, data_types) + # format data + df_rows = convert_utc_df(df_rows, cols, data_types, data_source, table_name) + + # change name if romaji cols is duplicated + cols_with_types, cols_duplicated = change_duplicated_columns(cols_with_types) + rows = transform_df_to_rows(cols, df_rows, limit) + return cols_with_types, rows, cols_duplicated, previewed_files + + +def gen_data_types_from_factory_type(cols, cols_with_types): + dic_col_type = {col.get('name'): guess_data_types(col.get('type')) for col in cols_with_types} + return [dic_col_type.get(col) for col in cols] + + +@lru_cache(maxsize=20) +def get_info_from_db(data_source, table_name): + with DbProxy(data_source) as db_instance: + if not db_instance or not table_name: + return [], [] + + sql_limit = 2000 + if isinstance(db_instance, mssqlserver.MSSQLServer): + cols, rows = db_instance.run_sql("select TOP {} * from \"{}\"".format(sql_limit, table_name), False) + elif isinstance(db_instance, oracle.Oracle): + cols, rows = db_instance.run_sql( + "select * from \"{}\" where rownum <= {}".format(table_name, sql_limit), False) + else: + cols, rows = db_instance.run_sql("select * from \"{}\" limit {}".format(table_name, sql_limit), False) + + cols = normalize_list(cols) + df_rows = normalize_big_rows(rows, cols, strip_quote=False) + return cols, df_rows + + +def get_filter_col_data(proc_config: dict): + filter_cfgs = proc_config.get('filters') or [] + cfg_col_ids = [filter_cfg.get('column_id') for filter_cfg in filter_cfgs] + if not cfg_col_ids: + return {} + sensor_data = {} + for col_id in cfg_col_ids: + sensor_data[col_id] = get_distinct_sensor_values(col_id) + return sensor_data + + +@memoize() +def get_distinct_sensor_values(cfg_col_id): + cfg_col: CfgProcessColumn = CfgProcessColumn.query.get(cfg_col_id) + if not cfg_col: + return [] + sensor = Sensor.get_sensor_by_col_name(cfg_col.process_id, cfg_col.column_name) + sensor_vals = [] + if sensor: + sensor_val_cls = find_sensor_class(sensor.id, DataType(sensor.type)) + sensor_vals = sensor_val_cls.get_distinct_values(cfg_col.column_name, limit=1000) + sensor_vals = [sensor_val.value for sensor_val in sensor_vals] + return sensor_vals + + +@memoize() +def get_last_distinct_sensor_values(cfg_col_id): + cfg_col: CfgProcessColumn = CfgProcessColumn.query.get(cfg_col_id) + if not cfg_col: + return [] + sensor = Sensor.get_sensor_by_col_name(cfg_col.process_id, cfg_col.column_name) + unique_sensor_vals = set() + if sensor: + sensor_val_cls = find_sensor_class(sensor.id, DataType(sensor.type)) + sensor_vals = sensor_val_cls.get_last_distinct_values(cfg_col.column_name, limit=10000) + unique_sensor_vals = set([sensor_val.value for sensor_val in sensor_vals][:1000]) + unique_sensor_vals = sorted(unique_sensor_vals) + return list(unique_sensor_vals) + + +def save_master_vis_config(proc_id, cfg_jsons): + vis_schema = VisualizationSchema() + + with make_session() as meta_session: + proc: CfgProcess = meta_session.query(CfgProcess).get(proc_id or -1) + if proc: + cfg_vis_data = [] + for cfg_json in cfg_jsons: + cfg_vis_data.append(vis_schema.load(cfg_json)) + crud_config(meta_session=meta_session, + data=cfg_vis_data, + model=CfgVisualization, + key_names=CfgVisualization.id.key, + parent_key_names=CfgVisualization.process_id.key, + parent_obj=proc, + parent_relation_key=CfgProcess.visualizations.key, + parent_relation_type=RelationShip.MANY) + + +@log_execution_time() +def preview_csv_data(folder_url, etl_func, csv_delimiter, limit, return_df=False): + df_data_details = None + csv_delimiter = get_csv_delimiter(csv_delimiter) + sorted_files = get_sorted_files(folder_url) + sorted_files = sorted_files[0:5] + + csv_file = '' + skip_head = 0 + skip_tail = 0 + header_names = [] + data_types = [] + data_details = [] + if not sorted_files: + return { + 'file_name': csv_file, + 'header': header_names, + 'content': [] if return_df else data_details, + 'dataType': data_types, + 'skip_head': skip_head, + 'skip_tail': skip_tail, + 'previewed_files': sorted_files, + } + + csv_file = sorted_files[0] + + # call efa etl + has_data_file = None + if etl_func: + # try to get file which has data to detect data types + get col names + for file_path in sorted_files: + preview_file_path = preview_data(file_path) + if preview_file_path and not isinstance(preview_file_path, Exception): + has_data_file = True + csv_file = preview_file_path + csv_delimiter = detect_file_delimiter(csv_file, csv_delimiter) + break + + if (etl_func and has_data_file) or is_normal_csv(csv_file, csv_delimiter): + for i in range(2): + data = None + try: + data = read_data(csv_file, delimiter=csv_delimiter, do_normalize=False) + header_names = next(data) + + # strip special symbols + if i == 0: + data = strip_special_symbol(data) + + # get 5 rows + data_details = list(islice(data, 1000)) + finally: + if data: + data.close() + + if data_details: + break + + # normalization + header_names = normalize_list(header_names) + df_data_details = normalize_big_rows(data_details, header_names) + data_types = [gen_data_types(df_data_details[col]) for col in header_names] + else: + # try to get file which has data to detect data types + get col names + dic_file_info, csv_file = get_etl_good_file(sorted_files) + if dic_file_info and csv_file: + skip_head = chw.get_skip_head(dic_file_info) + skip_tail = chw.get_skip_tail(dic_file_info) + header_names = chw.get_columns_name(dic_file_info) + etl_headers = chw.get_etl_headers(dic_file_info) + data_types = chw.get_data_type(dic_file_info) + for i in range(2): + data = None + try: + data = read_data(csv_file, headers=header_names, skip_head=skip_head, delimiter=csv_delimiter, + do_normalize=False) + # non-use header + next(data) + + # strip special symbols + if i == 0: + data = strip_special_symbol(data) + + # get 5 rows + data_details = list(islice(data, limit + skip_tail)) + data_details = data_details[:len(data_details) - skip_tail] + finally: + if data: + data.close() + + if data_details: + break + + # Merge heads with Machine, Line, Process + if etl_headers[WR_VALUES]: + header_names += etl_headers[WR_HEADER_NAMES] + data_types += etl_headers[WR_TYPES] + data_details = chw.merge_etl_heads(etl_headers[WR_VALUES], data_details) + + header_names = normalize_list(header_names) + df_data_details = normalize_big_rows(data_details, header_names) + + if df_data_details is not None: + # convert utc + for col, dtype in zip(header_names, data_types): + if DataType(dtype) is not DataType.DATETIME: + continue + # Convert UTC time + validate_datetime(df_data_details, col, False, False) + convert_csv_timezone(df_data_details, col) + df_data_details.dropna(subset=[col], inplace=True) + + df_data_details = df_data_details[0:5] + if not return_df: + df_data_details = df_data_details.to_records(index=False).tolist() + else: + if not return_df: + df_data_details = [] + + if csv_file: + csv_file = csv_file.replace('/', os.sep) + + return { + 'file_name': csv_file, + 'header': header_names, + 'content': df_data_details, + 'dataType': data_types, + 'skip_head': skip_head, + 'skip_tail': skip_tail, + 'previewed_files': sorted_files, + } + + +@log_execution_time() +def get_etl_good_file(sorted_files): + csv_file = None + dic_file_info = None + for file_path in sorted_files: + check_result = chw.get_file_info_py(file_path) + if isinstance(check_result, Exception): + continue + + dic_file_info, is_empty_file = check_result + + if dic_file_info is None: + continue + + if is_empty_file: + continue + + csv_file = file_path + break + + return dic_file_info, csv_file + + +@log_execution_time() +def gen_cols_with_types(cols, data_types): + cols_with_types = [] + for col_name, data_type in zip(cols, data_types): + is_date = DataType(data_type) is DataType.DATETIME + + # add to output + if col_name: + cols_with_types.append({ + "name": col_name, + "type": DataType(data_type).name, + 'romaji': to_romaji(col_name), + 'is_date': is_date, + }) + + return cols_with_types + + +@log_execution_time() +def convert_utc_df(df_rows, cols, data_types, data_source, table_name): + for col_name, data_type in zip(cols, data_types): + is_date = DataType(data_type) is DataType.DATETIME + if not is_date: + continue + + # convert utc + date_val, tzoffset_str, db_timezone = get_tzoffset_of_random_record(data_source, table_name, col_name) + + # use os timezone + if data_source.db_detail.use_os_timezone: + db_timezone = None + + is_tz_inside, _, time_offset = get_time_info(date_val, db_timezone) + + # Convert UTC time + validate_datetime(df_rows, col_name, False, False) + convert_csv_timezone(df_rows, col_name) + df_rows.dropna(subset=[col_name], inplace=True) + + return df_rows + + +def transform_df_to_rows(cols, df_rows, limit): + return [dict(zip(cols, vals)) for vals in df_rows[0:limit][cols].to_records(index=False).tolist()] + + +@log_execution_time() +def gen_preview_data_check_dict(rows, previewed_files): + dic_preview_limit = {} + file_path = previewed_files[0] if previewed_files else '' + file_name = '' + folder_path = '' + if file_path: + file_name = os.path.basename(file_path) + folder_path = os.path.dirname(file_path) + + dic_preview_limit['reach_fail_limit'] = True if not rows and previewed_files else False + dic_preview_limit['file_name'] = file_name + dic_preview_limit['folder'] = folder_path + return dic_preview_limit diff --git a/histview2/api/table_viewer/controllers.py b/histview2/api/table_viewer/controllers.py new file mode 100644 index 0000000..b165485 --- /dev/null +++ b/histview2/api/table_viewer/controllers.py @@ -0,0 +1,112 @@ +import json + +from flask import Blueprint, request + +from histview2.common.pydn.dblib import mssqlserver, oracle +from histview2.common.pydn.dblib.db_proxy import DbProxy +from histview2.common.services import http_content +from histview2.common.services.jp_to_romaji_utils import to_romaji +from histview2.common.services.sse import notify_progress +from histview2.setting_module.models import CfgDataSource + +api_table_viewer_blueprint = Blueprint( + 'api_table_viewer', + __name__, + url_prefix='/histview2/api/table_viewer' +) + + +@api_table_viewer_blueprint.route('/column_names', methods=['GET']) +def get_column_names(): + """[summary] + show_column_names + Returns: + [type] -- [description] + """ + database = request.args.get('database') + table = request.args.get('table') + + blank_output = json.dumps({ + 'cols': [], + 'rows': [] + }, ensure_ascii=False, default=http_content.json_serial) + + data_source = CfgDataSource.query.get(database) + if not data_source: + return blank_output + + with DbProxy(data_source) as db_instance: + if not db_instance or not table: + return blank_output + + cols = db_instance.list_table_columns(table) + for col in cols: + col['romaji'] = to_romaji(col['name']) + + content = { + 'cols': cols, + } + + return json.dumps(content, ensure_ascii=False, default=http_content.json_serial) + + +@api_table_viewer_blueprint.route('/table_records', methods=['POST']) +def get_table_records(): + """[summary] + Show limited records + Returns: + [type] -- [description] + """ + + request_data = json.loads(request.data) + db_code = request_data.get("database_code") + table_name = request_data.get("table_name") + sort_column = request_data.get("sort_column") + sort_order = request_data.get("sort_order") or "DESC" + limit = request_data.get("limit") or 5 + + blank_output = json.dumps({ + 'cols': [], + 'rows': [] + }, ensure_ascii=False, default=http_content.json_serial) + + if not db_code or not table_name or sort_order not in ("ASC", "DESC"): + return blank_output + + data_source = CfgDataSource.query.get(db_code) + if not data_source: + return blank_output + + with DbProxy(data_source) as db_instance: + if not db_instance or not table_name: + return blank_output + + cols_with_types = db_instance.list_table_columns(table_name) + for col in cols_with_types: + col['romaji'] = to_romaji(col['name']) + + cols, rows = query_data(db_instance, table_name, sort_column, sort_order, limit) + + result = { + 'cols': cols_with_types, + 'rows': rows + } + return json.dumps(result, ensure_ascii=False, default=http_content.json_serial) + + +@notify_progress(50) +def query_data(db_instance, table_name, sort_column, sort_order, limit): + sort_statement = '' + if sort_column and sort_order: + sort_statement = "order by \"{}\" {} ".format(sort_column, sort_order) + + if isinstance(db_instance, mssqlserver.MSSQLServer): + sql = "select TOP {} * from \"{}\" {} ".format(limit, table_name, sort_statement) + elif isinstance(db_instance, oracle.Oracle): + sql = "select * from \"{}\" where rownum <= {} {} ".format(table_name, limit, sort_statement) + else: + sql = "select * from \"{}\" {} limit {}".format(table_name, sort_statement, limit) + + cols, rows = db_instance.run_sql(sql=sql) + + return cols, rows diff --git a/histview2/api/trace_data/controllers.py b/histview2/api/trace_data/controllers.py new file mode 100644 index 0000000..1648371 --- /dev/null +++ b/histview2/api/trace_data/controllers.py @@ -0,0 +1,154 @@ +import json +import timeit + +import simplejson +from flask import Blueprint, request, jsonify, Response + +from histview2.api.trace_data.services.csv_export import gen_csv_data +from histview2.api.trace_data.services.time_series_chart import (update_outlier_flg, save_proc_sensor_order_to_db, + gen_graph_fpp) +from histview2.common.services import http_content, csv_content +from histview2.common.services.form_env import parse_request_params, parse_multi_filter_into_one +from histview2.common.services.import_export_config_n_data import export_debug_info, set_export_dataset_id_to_dic_param, \ + get_dic_form_from_debug_info, import_user_setting_db, \ + import_config_db, get_zip_full_path +from histview2.common.trace_data_log import save_input_data_to_file, EventType + +api_trace_data_blueprint = Blueprint( + 'api_trace_data', + __name__, + url_prefix='/histview2/api/fpp' +) + +FPP_MAX_GRAPH = 20 + + +@api_trace_data_blueprint.route('/index', methods=['POST']) +def trace_data(): + """ + Trace Data API + return dictionary + """ + + start = timeit.default_timer() + dic_form = request.form.to_dict(flat=False) + + # save dic_form to pickle (for future debug) + save_input_data_to_file(dic_form, EventType.FPP) + dic_param = parse_multi_filter_into_one(dic_form) + + # check if we run debug mode (import mode) + dic_param = get_dic_form_from_debug_info(dic_param) + + dic_param = gen_graph_fpp(dic_param, FPP_MAX_GRAPH) + stop = timeit.default_timer() + dic_param['backend_time'] = stop - start + + # export mode ( output for export mode ) + set_export_dataset_id_to_dic_param(dic_param) + + # trace_data.htmlをもとにHTML生成 + out_dict = simplejson.dumps(dic_param, ensure_ascii=False, default=http_content.json_serial, ignore_nan=True) + + return out_dict, 200 + + +@api_trace_data_blueprint.route('/csv_export', methods=['GET']) +def csv_export(): + """csv export + + Returns: + [type] -- [description] + """ + dic_form = parse_request_params(request) + dic_param = parse_multi_filter_into_one(dic_form) + csv_str = gen_csv_data(dic_param) + csv_filename = csv_content.gen_csv_fname() + + response = Response(csv_str.encode("utf-8-sig"), mimetype="text/csv", + headers={ + "Content-Disposition": "attachment;filename={}".format(csv_filename), + }) + response.charset = "utf-8-sig" + + return response + + +@api_trace_data_blueprint.route('/tsv_export', methods=['GET']) +def tsv_export(): + """tsv export + + Returns: + [type] -- [description] + """ + dic_form = parse_request_params(request) + dic_param = parse_multi_filter_into_one(dic_form) + csv_str = gen_csv_data(dic_param, delimiter='\t') + csv_filename = csv_content.gen_csv_fname("tsv") + + response = Response(csv_str.encode("utf-8-sig"), mimetype="text/tsv", + headers={ + "Content-Disposition": "attachment;filename={}".format(csv_filename), + }) + response.charset = "utf-8-sig" + + return response + + +@api_trace_data_blueprint.route('/zip_export', methods=['GET']) +def zip_export(): + """zip export + + Returns: + [type] -- [description] + """ + dic_form = parse_request_params(request) + dataset_id = int(dic_form['dataset_id']) + user_setting_id = int(dic_form['user_setting_id']) + response = export_debug_info(dataset_id, user_setting_id) + + return response + + +@api_trace_data_blueprint.route('/zip_import', methods=['GET']) +def zip_import(): + """zip import + + Returns: + [type] -- [description] + """ + dic_form = parse_request_params(request) + filename = dic_form['filename'] + zip_file = get_zip_full_path(filename) + import_config_db(zip_file) + user_setting = import_user_setting_db(zip_file) + dic_user_setting = {'id': user_setting['id'], 'page': user_setting['page']} + + return jsonify(dic_user_setting), 200 + + +@api_trace_data_blueprint.route('/update_outlier', methods=['POST']) +def update_outlier(): + """ + Update outlier flags to DB. + :return: + """ + request_data = json.loads(request.data) + proc_id = request_data.get("process_id") + cycle_ids = request_data.get("cycle_ids") + is_outlier = request_data.get("is_outlier") + update_outlier_flg(proc_id, cycle_ids, is_outlier) + return jsonify({}), 200 + + +@api_trace_data_blueprint.route('/save_order', methods=['POST']) +def save_proc_sensor_order(): + """ + Save order of processes and sensors from GUI drag & drop + :return: + """ + request_data = json.loads(request.data) + orders = request_data.get("orders") or {} + save_proc_sensor_order_to_db(orders) + + return jsonify({}), 200 diff --git a/histview2/api/trace_data/services/__init__.py b/histview2/api/trace_data/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/api/trace_data/services/csv_export.py b/histview2/api/trace_data/services/csv_export.py new file mode 100644 index 0000000..3059995 --- /dev/null +++ b/histview2/api/trace_data/services/csv_export.py @@ -0,0 +1,127 @@ +from typing import Dict + +import numpy as np +import pandas as pd +import pytz +from dateutil import tz +from pandas import DataFrame + +from histview2.api.trace_data.services.time_series_chart import get_data_from_db, get_procs_in_dic_param +from histview2.common.common_utils import gen_sql_label, DATE_FORMAT_STR, DATE_FORMAT_STR_CSV +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.services.form_env import bind_dic_param_to_class +from histview2.setting_module.models import CfgProcess +from histview2.trace_data.schemas import DicParam + + +@log_execution_time() +def gen_csv_data(dic_param, delimiter=None): # get the most cover flows + """tracing data to show csv + 1 start point x n end point + filter by condition points that between start point and end_point + """ + # bind dic_param + graph_param = bind_dic_param_to_class(dic_param) + + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + # add start proc + graph_param.add_start_proc_to_array_formval() + + # add category + graph_param.add_cate_procs_to_array_formval() + + # get serials + date + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + + get_date = proc_cfg.get_date_col(column_name_only=False).id + proc.add_cols(get_date, append_first=True) + + serial_ids = [serial.id for serial in proc_cfg.get_serials(column_name_only=False)] + proc.add_cols(serial_ids, append_first=True) + + # get data from database + df, *_ = get_data_from_db( graph_param) + client_timezone = dic_param[COMMON].get(CLIENT_TIMEZONE) + client_timezone = pytz.timezone(client_timezone) if client_timezone else tz.tzlocal() + # client_timezone = tz.gettz(client_timezone or None) or tz.tzlocal() + + if delimiter: + csv_data = to_csv(df, dic_proc_cfgs, graph_param, delimiter=delimiter, client_timezone=client_timezone) + else: + csv_data = to_csv(df, dic_proc_cfgs, graph_param, client_timezone=client_timezone) + + return csv_data + + +def gen_export_col_name(proc_name, col_name): + return f'{proc_name}|{col_name}' + + +@log_execution_time() +def to_csv(df: DataFrame, dic_proc_cfgs: Dict[int, CfgProcess], graph_param: DicParam, delimiter=',', + client_timezone=None, output_path=None, output_col_ids=None, len_of_col_name=None): + # rename + new_headers = [] + suffix = '...' + dic_rename = {} + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + for col_id, col_name, name in zip(proc.col_ids, proc.col_names, proc.col_show_names): + old_name = gen_sql_label(col_id, col_name) + if old_name not in df.columns: + continue + + if output_col_ids and col_id not in output_col_ids: + continue + + new_name = gen_export_col_name(proc_cfg.name, name) + if len_of_col_name and len(new_name) > len_of_col_name: + new_name = new_name[:len_of_col_name - len(suffix)] + suffix + idx = 1 + while new_name in new_headers: + new_name = f'{new_name[:-3]}({idx})' + idx += 1 + new_headers.append(new_name) + + dic_rename[old_name] = new_name + + # get only output columns + df_csv = df[dic_rename] + df_csv.rename(columns=dic_rename, inplace=True) + df_csv.replace({np.nan: None}, inplace=True) + + # timezone + if client_timezone: + # get date list + get_dates = [] + for proc_cfg in dic_proc_cfgs.values(): + get_date_col = proc_cfg.get_date_col(column_name_only=False) + get_date_name_in_df = gen_export_col_name(proc_cfg.name, get_date_col.name) + get_dates.append(get_date_name_in_df) + + for col in df_csv.columns: + if col not in get_dates: + continue + # df_csv[col] = df_csv[col].apply(lambda v: convert_dt_str_to_timezone(client_timezone, v)) + df_csv[col] = pd.to_datetime(df_csv[col], format=DATE_FORMAT_STR, utc=True) \ + .dt.tz_convert(client_timezone).dt.strftime(DATE_FORMAT_STR_CSV) + + return df_csv.to_csv(output_path, sep=delimiter, index=False) + + +def sql_label_short(headers, length=10): + new_headers = [] + suffix = '...' + for header in headers: + new_header = header[:length - len(suffix)] + suffix if len(header) > length else header + + idx = 1 + while new_header in new_headers: + new_header = f'{new_header[:-3]}({idx})' + idx += 1 + + new_headers.append(new_header) + return new_headers diff --git a/histview2/api/trace_data/services/graph_search.py b/histview2/api/trace_data/services/graph_search.py new file mode 100644 index 0000000..5d73668 --- /dev/null +++ b/histview2/api/trace_data/services/graph_search.py @@ -0,0 +1,57 @@ + +class GraphUtil: + + def __init__(self, V): + self.V = V + self.adj = [[] for i in range(len(V))] + + def dfs_util(self, temp, v, visited): + visited[v] = True + temp.append(v) + idx = self.V.index(v) + for i in self.adj[idx]: + pr = self.V[i] + if not visited[pr]: + temp = self.dfs_util(temp, pr, visited) + return temp + + def add_edge(self, v, w): + v_index = self.V.index(v) + w_index = self.V.index(w) + self.adj[v_index].append(w_index) + self.adj[w_index].append(v_index) + + def connected_components(self): + visited = {} + cc = [] + for v in self.V: + visited[v] = False + for v in self.V: + if not visited[v]: + temp = [] + cc.append(self.dfs_util(temp, v, visited)) + return cc + + def find_linked_processes(self, target): + if target not in self.V: + return [] + + visited = {} + for v in self.V: + visited[v] = False + for v in self.V: + if not visited[v]: + temp = [] + component = self.dfs_util(temp, v, visited) + if target in component: + return component + return [] + + +if __name__ == "__main__": + g = GraphUtil([10, 11, 12, 13, 14, 20, 2222]) # pass list of proc_ids + g.add_edge(11, 10) + g.add_edge(12, 13) + g.add_edge(13, 14) + components = g.find_linked_processes(10) + print(components) diff --git a/histview2/api/trace_data/services/plot_view.py b/histview2/api/trace_data/services/plot_view.py new file mode 100644 index 0000000..3c8eac5 --- /dev/null +++ b/histview2/api/trace_data/services/plot_view.py @@ -0,0 +1,461 @@ +import itertools +from datetime import datetime, timedelta +from typing import List + +import numpy as np +import pandas as pd +import pytz +from dateutil import parser, tz + +from histview2.api.trace_data.services.graph_search import GraphUtil +from histview2.api.trace_data.services.proc_link import order_before_mapping_data +from histview2.api.trace_data.services.time_series_chart import get_data_from_db, gen_dic_data_from_df, \ + get_chart_infos, gen_plotdata, create_rsuffix, get_procs_in_dic_param, create_graph_config, \ + gen_blank_df_end_cols +from histview2.common.common_utils import gen_sql_label, DATE_FORMAT_STR_CSV, DATE_FORMAT, TIME_FORMAT +from histview2.common.constants import ARRAY_PLOTDATA, PRC_MAX, PRC_MIN, THRESH_HIGH, THRESH_LOW, ARRAY_FORMVAL, \ + END_PROC, GET02_VALS_SELECT, ACT_FROM, ACT_TO, SUMMARIES +from histview2.common.logger import log_execution_time +from histview2.common.services.form_env import bind_dic_param_to_class, parse_multi_filter_into_one +from histview2.common.services.statistics import calc_summaries +from histview2.common.yaml_utils import YamlConfig +from histview2.setting_module.models import CfgProcessColumn, CfgTrace, CfgProcess +from histview2.trace_data.models import Cycle +from histview2.trace_data.schemas import EndProc + + +@log_execution_time() +def gen_graph_plot_view(dic_form): + """tracing data to show graph + 1 start point x n end point + filter by condition point + https://files.slack.com/files-pri/TJHPR9BN3-F01GG67J84C/image.pngnts that between start point and end_point + """ + dic_param = parse_multi_filter_into_one(dic_form) + cycle_id = int(dic_form.get('cycle_id')) + point_time = dic_form.get('time') + target_id = int(dic_form.get('sensor_id')) + sensor = CfgProcessColumn.query.get(target_id) + target_proc_id = sensor.process_id + + # bind graph_param + graph_param, dic_proc_cfgs = build_graph_param(dic_param) + + # get data from database + df, _, _ = get_data_from_db( graph_param) + + # create output data + orig_graph_param = bind_dic_param_to_class(dic_param) + orig_graph_param.add_cate_procs_to_array_formval() + dic_data = gen_dic_data_from_df(df, orig_graph_param) + orig_graph_param = bind_dic_param_to_class(dic_param) + times = df[Cycle.time.key].tolist() or [] + + # get chart infos + chart_infos, original_graph_configs = get_chart_infos(orig_graph_param, dic_data, times) + + dic_param[ARRAY_FORMVAL], dic_param[ARRAY_PLOTDATA] \ + = gen_plotdata(orig_graph_param, dic_data, chart_infos, original_graph_configs) + + # calculate_summaries + calc_summaries(dic_param) + + # extract_cycle + df = extract_cycle(df, cycle_id) + if df.empty: + df = gen_blank_df_end_cols(graph_param.array_formval) + df[df.columns] = df[df.columns].to_numpy() + + # timezone + client_timezone = graph_param.common.client_timezone or tz.tzlocal() + client_timezone = pytz.timezone(client_timezone) + + # List table + list_tbl_header, list_tbl_rows = gen_list_table(dic_proc_cfgs, graph_param, df, client_timezone) + + # Stats table + stats_tbl_header, stats_tbl_data = gen_stats_table( + dic_proc_cfgs, + graph_param, + df, + dic_param, + original_graph_configs, + client_timezone, + point_time, + target_id, + target_proc_id, + ) + + # Full link table + dic_param = build_dic_param_plot_view(dic_form) + graph_param, dic_proc_cfgs = build_graph_param(dic_param, full_link=True) + df_full, _, _ = get_data_from_db( graph_param) + df_full: pd.DataFrame = extract_cycle(df_full, cycle_id) + if df_full.empty: + df_full = gen_blank_df_end_cols(graph_param.array_formval) + df_full[df.columns] = df[df.columns].to_numpy() + + full_link_tbl_header, full_link_tbl_rows = gen_list_table(dic_proc_cfgs, graph_param, df_full, client_timezone) + + return dic_param, { + 'stats_tbl_header': stats_tbl_header, + 'stats_tbl_data': stats_tbl_data, + 'list_tbl_header': list_tbl_header, + 'list_tbl_rows': list_tbl_rows, + 'full_link_tbl_header': full_link_tbl_header, + 'full_link_tbl_rows': full_link_tbl_rows, + } + + +def extract_cycle(df: pd.DataFrame, cycle_id): + if 'id' in df.columns: + df = df[df.id == cycle_id].reset_index() + else: + df = df[df.index == cycle_id].reset_index() + + return df.replace({np.nan: ''}) + + +def gen_stats_table(dic_proc_cfgs, graph_param, df, dic_param, chart_infos, client_timezone, start_time_val, target_id, + target_proc_id): + proc_ids = dic_proc_cfgs.keys() + proc_ids = order_proc_as_trace_config(proc_ids) + + stats_tbl_data = [] + max_num_serial = 1 + for proc_id, proc_cfg in dic_proc_cfgs.items(): + serial_col_cfgs = proc_cfg.get_serials(column_name_only=False) + if len(serial_col_cfgs) >= max_num_serial: + max_num_serial = len(serial_col_cfgs) + + for proc_order, proc_id in enumerate(proc_ids): + proc_cfg = dic_proc_cfgs.get(proc_id) + end_proc: EndProc = graph_param.search_end_proc(proc_id)[1] + col_ids = end_proc.col_ids + col_names = end_proc.col_names + col_show_names = end_proc.col_show_names + serial_col_cfgs = proc_cfg.get_serials(column_name_only=False) + serial_ids = [] + serial_vals = [] + for serial in serial_col_cfgs: + serial_label = gen_sql_label(serial.id, serial.column_name) + serial_ids.append(serial.id) + serial_vals.append(df.loc[0][serial_label]) + + if len(serial_col_cfgs) < max_num_serial: + diff = max_num_serial - len(serial_col_cfgs) + for i in range(diff): + serial_ids.append('') + serial_vals.append('') + + # Datetime + time_col_name = str(Cycle.time.key) + create_rsuffix(proc_id) + time_val = df.loc[0][time_col_name] + if not pd.isna(time_val) and time_val: + dt_obj = parser.parse(time_val) + dt_obj = dt_obj.astimezone(client_timezone) + time_val = datetime.strftime(dt_obj, DATE_FORMAT_STR_CSV) + else: + time_val = '' + + for col_idx, col_id in enumerate(col_ids): + if col_id in serial_ids: + continue + + row = [] + if col_id == target_id: + priority = 1 + elif proc_id == target_proc_id: + priority = 2 + else: + priority = proc_order + 10 + row.append(priority) + + # Serial No + row.extend(serial_vals) + + # Item + col_name = col_names[col_idx] + row.append(col_name) + + # Name + col_show_name = col_show_names[col_idx] + row.append(col_show_name) + + # Value + col_label = gen_sql_label(col_id, col_name) + col_val = df.loc[0][col_label] + row.append(col_val) + + # Datetime + row.append(time_val) + + # Process + row.append(proc_cfg.name) + + # Threshold + latest_idx = None + col_idx = get_sensor_idx(dic_param, proc_id, col_id) + col_thresholds = YamlConfig.get_node(chart_infos, [proc_id, col_id]) or [] + threshold = {} + if col_thresholds: + point_time = time_val or start_time_val + threshold, latest_idx = get_latest_threshold(col_thresholds, point_time, client_timezone) + th_type = threshold.get('type') or '' + th_name = threshold.get('name') or '' + if col_idx is not None: + th_type = th_type or 'Default' + th_name = th_name or 'Default' + lcl = threshold.get(THRESH_LOW) or '' + ucl = threshold.get(THRESH_HIGH) or '' + lpcl = threshold.get(PRC_MIN) or '' + upcl = threshold.get(PRC_MAX) or '' + row.extend([th_type, th_name, lcl, ucl, lpcl, upcl]) + + # Summaries + if col_idx is not None and latest_idx is not None: + plotdata = dic_param[ARRAY_PLOTDATA][col_idx] + summaries = plotdata[SUMMARIES] or [] + summary = summaries[latest_idx] + row.extend(build_summary_cells(summary)) + else: + row.extend(build_empty_summary_cells()) + + stats_tbl_data.append(row) + + stats_tbl_data = sorted(stats_tbl_data, key=lambda x: int(x[0])) + stats_tbl_data = list(map(lambda x: x[1:], stats_tbl_data)) + + stats_tbl_header = build_stats_header(max_num_serial) + + return stats_tbl_header, stats_tbl_data + + +def get_latest_threshold(col_thresholds, point_time, client_timezone): + if not col_thresholds: + return create_graph_config()[0], None + + if point_time: + point_time = parser.parse(point_time) + if point_time.tzinfo is None: + point_time = client_timezone.localize(point_time) + latest_act_to = parser.parse('1970-01-01T00:00:00.000Z') + latest_threshold = col_thresholds[0] + latest_idx = 0 + for idx, th in enumerate(col_thresholds): + act_from = parser.parse(th.get(ACT_FROM) or '1970-01-01T00:00:00.000Z') + act_to = parser.parse(th.get(ACT_TO) or '9999-01-01T00:00:00.000Z') + if act_from <= point_time <= act_to: + if latest_act_to < act_to: + latest_threshold = th + latest_idx = idx + latest_act_to = act_to + return latest_threshold, latest_idx + else: + latest_idx = 0 + return col_thresholds[latest_idx], latest_idx + + +def build_stats_header(max_num_serial): + stats_tbl_header = ['Serial No {}'.format(idx + 1) for idx in range(max_num_serial)] + stats_tbl_header.extend(['Item', 'Name', 'Value', 'Datetime', 'Process name', + 'Type', 'Name', 'Lower threshold', 'Upper threshold', + 'Lower process threshold', 'Upper process threshold', + 'N', 'Average', '3σ', 'Cp', 'Cpk', 'σ', 'Max', 'Min', + 'Median', 'P95', 'P75 Q3', 'P25 Q1', 'P5', 'IQR']) + return stats_tbl_header + + +def build_empty_summary_cells(): + return [''] * 14 + + +def build_summary_cells(summary): + bstats = summary['basic_statistics'] or {} + non_pt = summary['non_parametric'] or {} + return list(map( + lambda x: '' if x is None else x, + [bstats.get('n_stats'), bstats.get('average'), bstats.get('sigma_3'), bstats.get('Cp'), + bstats.get('Cpk'), bstats.get('sigma'), bstats.get('Max'), bstats.get('Min'), + non_pt.get('median'), non_pt.get('p95'), non_pt.get('p75'), non_pt.get('p25'), non_pt.get('p5'), + non_pt.get('iqr')] + )) + + +def convert_and_format(time_val, client_timezone, out_format=DATE_FORMAT_STR_CSV): + dt_obj = parser.parse(time_val) + dt_obj = dt_obj.astimezone(client_timezone) + return datetime.strftime(dt_obj, out_format) + + +def gen_list_table(dic_proc_cfgs, graph_param, df, client_timezone): + proc_ids = dic_proc_cfgs.keys() + proc_ids = order_proc_as_trace_config(proc_ids) + list_tbl_data = [] + list_tbl_header = [] + for proc_id in proc_ids: + proc_cfg = dic_proc_cfgs.get(proc_id) + list_tbl_header.extend(['Item', "Name", "Value"]) + end_proc: EndProc = graph_param.search_end_proc(proc_id)[1] + col_ids = end_proc.col_ids + get_date_col: CfgProcessColumn = proc_cfg.get_date_col(column_name_only=False) + dic_id_col = {col.id: col for col in proc_cfg.columns} + serial_col_cfgs = proc_cfg.get_serials(column_name_only=False) + serial_ids = [] + serial_vals = [] + proc_rows = [] + for serial in serial_col_cfgs: + serial_label = gen_sql_label(serial.id, serial.column_name) + serial_ids.append(serial.id) + serial_val = df.loc[0][serial_label] + serial_vals.append(serial_val) + # Serial No + proc_rows.append([serial.column_name, serial.name, serial_val]) + + # Datetime + time_col_name = str(Cycle.time.key) + create_rsuffix(proc_id) + time_val = df.loc[0][time_col_name] + if not pd.isna(time_val) and time_val: + time_val = convert_and_format(time_val, client_timezone, DATE_FORMAT_STR_CSV) + else: + time_val = '' + proc_rows.append(['Datetime', '', time_val]) + + # Line No + proc_rows.append(['Line No', '', '']) + + # Process + proc_rows.append(['Process', '', proc_cfg.name]) + + # Machine No + proc_rows.append(['Machine No', '', '']) + + # Part No + proc_rows.append(['Part No', '', '']) + + # Other columns + for col_id in col_ids: + cfg_col: CfgProcessColumn = dic_id_col.get(col_id) + if not cfg_col: + continue + + if col_id in serial_ids: + continue + + if col_id == get_date_col.id: + continue + + row = [] + # Item + row.append(cfg_col.column_name) + + # Name + row.append(cfg_col.name) + + # Value + col_label = gen_sql_label(cfg_col.id, cfg_col.column_name) + col_val = df.loc[0][col_label] + if cfg_col.is_get_date and not pd.isna(time_val) and time_val: + time_val = convert_and_format(time_val, client_timezone, DATE_FORMAT_STR_CSV) + else: + row.append(col_val) + + proc_rows.append(row) + + # append to first table + list_tbl_data.append(proc_rows) + + list_tbl_rows = [] + for row in itertools.zip_longest(*list_tbl_data): + list_tbl_rows.append(list(itertools.chain.from_iterable([r if r else ['', '', ''] for r in row]))) + + return list_tbl_header, list_tbl_rows + + +def get_sensor_idx(dic_param, proc_id, col_id): + for idx, form_val in enumerate(dic_param[ARRAY_FORMVAL]): + if form_val[END_PROC] == proc_id and form_val[GET02_VALS_SELECT] == col_id: + return idx + return None + + +def build_dic_param_plot_view(dic_form): + clicked_time = dic_form.get('time') + + clicked_time = parser.parse(clicked_time) + clicked_time = clicked_time - timedelta(minutes=5) + start_date = clicked_time.strftime(DATE_FORMAT) + start_time = clicked_time.strftime(TIME_FORMAT) + clicked_time = clicked_time + timedelta(minutes=10) + end_date = clicked_time.strftime(DATE_FORMAT) + end_time = clicked_time.strftime(TIME_FORMAT) + dic_form['START_DATE'] = start_date + dic_form['END_DATE'] = end_date + dic_form['START_TIME'] = start_time + dic_form['END_TIME'] = end_time + + dic_param = parse_multi_filter_into_one(dic_form) + + return dic_param + + +def order_proc_as_trace_config(proc_ids): + edges = CfgTrace.get_all() + ordered_edges: List[CfgTrace] = order_before_mapping_data(edges) + ordered_proc_ids = [(edge.self_process_id, edge.target_process_id) for edge in ordered_edges] + ordered_proc_ids = list(itertools.chain.from_iterable(ordered_proc_ids)) + reversed_proc_ids = list(reversed(ordered_proc_ids)) + ordered_proc_ids = [] + for proc_id in reversed_proc_ids: + if proc_id in proc_ids and proc_id not in ordered_proc_ids: + ordered_proc_ids.append(proc_id) + return list(reversed(ordered_proc_ids)) or proc_ids + + +def get_linked_procs(proc_id): + processes = CfgProcess.get_all() + nodes = [proc.id for proc in processes] + + edges: List[CfgTrace] = CfgTrace.get_all() + + graph_util = GraphUtil(nodes) + for edge in edges: + graph_util.add_edge(edge.self_process_id, edge.target_process_id) + graph_util.add_edge(edge.target_process_id, edge.self_process_id) + + linked_procs = graph_util.find_linked_processes(proc_id) + + return linked_procs + + +def build_graph_param(dic_param, full_link=False): + # bind dic_param + graph_param = bind_dic_param_to_class(dic_param) + + # add relevant procs + if full_link: + relevant_procs = get_linked_procs(graph_param.get_start_proc()) + for proc_id in relevant_procs: + graph_param.add_proc_to_array_formval(proc_id, []) + + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + # add start proc + graph_param.add_start_proc_to_array_formval() + + # add condition procs + graph_param.add_cond_procs_to_array_formval() + + # add category + graph_param.add_cate_procs_to_array_formval() + + # get serials + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + columns = proc_cfg.get_serials(column_name_only=False) + if full_link: + columns = proc_cfg.columns + col_ids = [col.id for col in columns] + proc.add_cols(col_ids) + + return graph_param, dic_proc_cfgs diff --git a/histview2/api/trace_data/services/proc_link.py b/histview2/api/trace_data/services/proc_link.py new file mode 100644 index 0000000..a4ffd81 --- /dev/null +++ b/histview2/api/trace_data/services/proc_link.py @@ -0,0 +1,457 @@ +from collections import namedtuple, deque +from datetime import datetime, timedelta +from typing import List, Dict + +from apscheduler.triggers import date +from pytz import utc +from sqlalchemy import insert +from sqlalchemy.sql.expression import literal + +from histview2 import scheduler +# from histview2 import web_socketio +from histview2.api.setting_module.services.data_import import get_from_to_substring_col +from histview2.common.common_utils import chunks_dic +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.memoize import set_all_cache_expired +from histview2.common.pydn.dblib.db_proxy import DbProxy, gen_data_source_of_universal_db +from histview2.common.scheduler import scheduler_app_context, JobType, IMPORT_DATA_JOBS, RESCHEDULE_SECONDS +from histview2.common.services.sse import background_announcer, AnnounceEvent +from histview2.setting_module.models import JobManagement, CfgTrace, ProcLink, CfgProcess +from histview2.setting_module.services.background_process import send_processing_info +from histview2.trace_data.models import * + +# socketio = web_socketio[SOCKETIO] + +# 2 proc join key string +JOIN_KEY = 'key' +SET_RELATION_INSERT = 'set_relation_insert' +DIC_CYCLE_UPDATE = 'dic_cycle_update' +SET_EXIST_RELATION = 'set_exist_relation' +RECORD_PER_COMMIT = 1_000_000 + + +@scheduler_app_context +def gen_global_id_job(_job_id=None, _job_name=None, is_new_data_check=True, is_publish=True): + """run job generate global id + + Keyword Arguments: + _job_id {[type]} -- [description] (default: {None}) + _job_name {[type]} -- [description] (default: {None}) + """ + + # check if generate global is needed + if is_new_data_check: + prev_gen_job = JobManagement.get_last_job_id_by_jobtype(JobType.GEN_GLOBAL.name) + prev_gen_job_id = 0 + if prev_gen_job: + prev_gen_job_id = prev_gen_job.id + + jobs = JobManagement.check_new_jobs(prev_gen_job_id, IMPORT_DATA_JOBS) + if not jobs: + print("QUIT GENERATE GLOBAL ID , BECAUSE THERE IS NO NEW DATA FROM THE LAST GENERATE") + return + + # generate global ids + gen = gen_global_id() + else: + # generate global ids + gen = gen_global_id(reset_existed_global_id=True) + + send_processing_info(gen, JobType.GEN_GLOBAL) + + # publish to clients that proc link job was done ! + if is_publish: + background_announcer.announce(True, AnnounceEvent.PROC_LINK.name) + print('PROC_LINK_DONE_PUBLISH: DONE') + # clear cache + set_all_cache_expired() + + +@log_execution_time('[GENERATE GLOBAL ID]') +def gen_global_id(reset_existed_global_id=False): + """ + generate global id for universal db (root function) + """ + yield 0 + + if reset_existed_global_id: + clear_data_before_gen_proc_link() + + yield 10 + + # get all first,end procs ( forward trace ) + edges = CfgTrace.get_all() + + start_procs = get_start_procs(edges) + + # check universal zero + if not start_procs: + return + + # create sub string sensors + gen_substring_sensors(edges) + + # set global id for start procs + for proc_id in start_procs: + cycle_cls = find_cycle_class(proc_id) + cycle_cls.gen_auto_global_id(proc_id) + + db.session.commit() + percent = 20 + + # trace each edge , return global & relation list + dic_output = {SET_RELATION_INSERT: set(), DIC_CYCLE_UPDATE: {}, SET_EXIST_RELATION: set(GlobalRelation.get_all())} + + # matched count on proc + # dic_cycle_ids = defaultdict(set) + dic_edge_cnt = {} + edges = order_before_mapping_data(edges) + for edge in edges: + # matching data + start_cycle_ids, end_cycle_ids = mapping_data(edge, dic_output) + + # count matching data for per process + dic_edge_cnt[(edge.self_process_id, edge.target_process_id)] = len(start_cycle_ids) + # dic_cycle_ids[edge.self_process_id].update(start_cycle_ids) + # dic_cycle_ids[edge.target_process_id].update(end_cycle_ids) + + # save db + cycle_cls = find_cycle_class(edge.target_process_id) + for chunk in chunks_dic(dic_output[DIC_CYCLE_UPDATE], RECORD_PER_COMMIT): + cycles = [dict(id=cycle_id, global_id=global_id) for cycle_id, global_id in chunk.items()] + db.session.bulk_update_mappings(cycle_cls, cycles) + db.session.commit() + + # reset dict after saved + dic_output[DIC_CYCLE_UPDATE] = {} + percent += 1 + yield percent + + insert_targets = list(dic_output[SET_RELATION_INSERT]) + if len(insert_targets): + global_relate_cols = (GlobalRelation.global_id.key, GlobalRelation.relate_id.key, GlobalRelation.created_at.key) + with DbProxy(gen_data_source_of_universal_db(), True) as db_instance: + for chunk in chunks(insert_targets, RECORD_PER_COMMIT): + created_at = get_current_timestamp() + insert_relations = [] + + for rel in chunk: + insert_relations.append((rel[0], rel[1], created_at)) + insert_relations.append((rel[1], rel[0], created_at)) + + # insert to db + db_instance.bulk_insert(GlobalRelation.__table__.name, global_relate_cols, insert_relations) + + # commit changes to db + db_instance.connection.commit() + + # percent + percent += 1 + yield percent + + yield 99, {}, dic_edge_cnt + yield 100 + + +@log_execution_time() +def order_before_mapping_data(edges: List[CfgTrace]): + """ trace all node in dic_node , and gen sql + """ + ordered_edges = [] + + max_loop = len(edges) * 10 + edges = deque(edges) + cnt = 0 + while edges: + if cnt > max_loop: + raise Exception('Edges made a ring circle, You must re-setting tracing edge to break the ring circle!!!') + + # get first element + edge = edges.popleft() + + # check if current start proc is in others edge's end procs + # if YES , we must wait for these end proc run first( move the current edge to the end) + if any((edge.self_process_id == other_edge.target_process_id for other_edge in edges)): + # move to the end of queue + edges.append(edge) + cnt += 1 + else: + ordered_edges.append(edge) + cnt = 0 + + return ordered_edges + + +@log_execution_time() +def mapping_data(edge: CfgTrace, dic_output: Dict): + dic_start_data = build_proc_data(edge, True) + dic_end_data = build_proc_data(edge) + + # update global id and relation + start_cycle_ids = [] + end_cycle_ids = [] + for keys, start_row in dic_start_data.items(): + end_row = dic_end_data.get(keys) + if end_row is None: + continue + + # if end proc global id is NULL + if end_row.global_id: + # cross relate + # if not same number( already added), and not exist in database + if end_row.global_id == start_row.global_id: + continue + + key = (start_row.global_id, end_row.global_id) + reverse_key = (end_row.global_id, start_row.global_id) + + if key in dic_output[SET_EXIST_RELATION]: + continue + + if key in dic_output[SET_RELATION_INSERT]: + continue + + if reverse_key in dic_output[SET_RELATION_INSERT]: + continue + + # add to insert list + dic_output[SET_RELATION_INSERT].add((start_row.global_id, end_row.global_id)) + else: + # add to update list + dic_output[DIC_CYCLE_UPDATE][end_row.id] = start_row.global_id + + # count + start_cycle_ids.append(start_row.id) + end_cycle_ids.append(end_row.id) + + return start_cycle_ids, end_cycle_ids + + +def gen_trace_key_info(edge: CfgTrace, is_start_proc): + # trace key info + TraceKeyInfo = namedtuple('TraceKeyInfo', + 'proc_id, column_id, column_name, col_name_with_substr, from_char, to_char') + + if is_start_proc: + proc_id = edge.self_process_id + keys = [(key.self_column_id, key.self_column_substr_from, key.self_column_substr_to) for key in edge.trace_keys] + else: + proc_id = edge.target_process_id + keys = [(key.target_column_id, key.target_column_substr_from, key.target_column_substr_to) for key in + edge.trace_keys] + + trace_key_infos = [] + for key in keys: + col_id, from_char, to_char = key + column = CfgProcessColumn.query.get(col_id) + if from_char or to_char: + substr_col_name = SUB_STRING_COL_NAME.format(column.column_name, from_char, to_char) + else: + substr_col_name = column.column_name + + trace_key_info = TraceKeyInfo(proc_id, column.id, column.column_name, substr_col_name, from_char, to_char) + trace_key_infos.append(trace_key_info) + + return trace_key_infos + + +def build_proc_data(edge: CfgTrace, is_start_proc=False): + """ + build sql to query matching global + get cycles by proc_id + """ + + # get proc_id , keys + trace_key_infos = gen_trace_key_info(edge, is_start_proc) + proc_id = trace_key_infos[0].proc_id + + cycle_cls = find_cycle_class(proc_id) + data = db.session.query(cycle_cls.id, cycle_cls.global_id).filter(cycle_cls.process_id == proc_id) + data = data.order_by(cycle_cls.time, cycle_cls.id) + + # only get global_id that not null + if is_start_proc: + data = data.filter(cycle_cls.global_id > 0) + + # get sensor information of keys from database (run separate to reuse cache, keys are only 1 or 2 columns) + sensors = Sensor.query.filter(Sensor.process_id == proc_id).filter( + Sensor.column_name.in_([trace_key.col_name_with_substr for trace_key in trace_key_infos])).all() + for sensor in sensors: + sensor_val_cls = find_sensor_class(sensor.id, DataType(sensor.type), auto_alias=True) + data = data.join(sensor_val_cls, sensor_val_cls.cycle_id == cycle_cls.id) + data = data.filter(sensor_val_cls.sensor_id == sensor.id) + data = data.add_columns(sensor_val_cls.value.label(gen_sql_label(sensor.column_name))) + + data = data.all() + + # make dictionary (remove duplicate and faster for tracing) + data = {tuple([getattr(row, gen_sql_label(key.col_name_with_substr)) for key in trace_key_infos]): row + for row in data} + return data + + +@log_execution_time() +def gen_substring_sensor(proc_id, orig_col_name, from_char, to_char): + # new column name for sub string + substr_col_name = SUB_STRING_COL_NAME.format(orig_col_name, from_char, to_char) + + # check duplicate sub string sensors + if Sensor.get_sensor_by_col_name(proc_id, substr_col_name): + return None + + orig_sensor = Sensor.get_sensor_by_col_name(proc_id, orig_col_name) + if not orig_sensor: + return None + + sensor = Sensor(process_id=proc_id, column_name=substr_col_name, type=orig_sensor.type) + db.session.add(sensor) + db.session.commit() + + sensor_id = sensor.id + sensor_type = sensor.type + orig_sensor_val_cls = find_sensor_class(orig_sensor.id, DataType(orig_sensor.type)) + + # get all value of original sensor + data = db.session.query(orig_sensor_val_cls.cycle_id, literal(sensor_id), + func.substr(orig_sensor_val_cls.value, from_char, to_char - from_char + 1)) + + data = data.filter(orig_sensor_val_cls.sensor_id == orig_sensor.id) + + # insert into sensor val + sensor_val_cls = find_sensor_class(sensor_id, DataType(sensor_type)) + sensor_insert = insert(sensor_val_cls).from_select( + (sensor_val_cls.cycle_id, sensor_val_cls.sensor_id, sensor_val_cls.value), data) + + # execute + db.session.execute(sensor_insert) + db.session.commit() + + return substr_col_name + + +def add_gen_proc_link_job(publish=False): + """call gen proc link id job + + Args: + :param publish: + """ + job_id = JobType.GEN_GLOBAL.name + run_time = datetime.now().astimezone(utc) + timedelta(seconds=RESCHEDULE_SECONDS) + date_trigger = date.DateTrigger(run_date=run_time, timezone=utc) + scheduler.add_job(job_id, gen_global_id_job, trigger=date_trigger, replace_existing=True, + kwargs=dict(_job_id=job_id, _job_name=job_id, is_new_data_check=True, is_publish=publish)) + + +####################################################### + +def get_start_procs(edges): + self_trace_ids = set() + target_trace_ids = set() + + for edge in edges: + self_trace_ids.add(edge.self_process_id) + target_trace_ids.add(edge.target_process_id) + + # start procs, end procs + return self_trace_ids - target_trace_ids + + +def get_end_procs(edges): + self_trace_ids = set() + target_trace_ids = set() + + for edge in edges: + self_trace_ids.add(edge.self_process_id) + target_trace_ids.add(edge.target_process_id) + + # start procs, end procs + return target_trace_ids - self_trace_ids + + +def gen_substring_sensors(edges: List[CfgTrace]): + for edge in edges: + for trace_key in edge.trace_keys: + if trace_key.self_column_substr_from: + orig_col = CfgProcessColumn.query.get(trace_key.self_column_id) + gen_substring_sensor(edge.self_process_id, orig_col.column_name, + trace_key.self_column_substr_from, + trace_key.self_column_substr_to) + if trace_key.target_column_substr_from: + orig_col = CfgProcessColumn.query.get(trace_key.target_column_id) + gen_substring_sensor(edge.target_process_id, orig_col.column_name, + trace_key.target_column_substr_from, + trace_key.target_column_substr_to) + + +# @log_execution_time() +# def show_proc_link_info(): +# """ +# show matched global id count +# :return: +# """ +# # get infos +# data = ProcLink.calc_proc_link() +# +# # matched count on edge +# dic_edge_cnt = {} +# dic_proc_cnt = {} +# +# # count matched per edge +# for row in data: +# if row.target_process_id: +# dic_edge_cnt[f'{row.process_id}-{row.target_process_id}'] = row.matched_count +# else: +# dic_proc_cnt[row.process_id] = row.matched_count +# +# return dic_proc_cnt, dic_edge_cnt + +@log_execution_time() +def show_proc_link_info(): + """ + show matched global id count + :return: + """ + dic_proc_cnt = {} + dic_edge_cnt = {} + + # all procs + all_procs = CfgProcess.get_all() + + for proc in all_procs: + cycle_cls = find_cycle_class(proc.id) + dic_proc_cnt[proc.id] = (cycle_cls.count_not_none_global_ids(proc.id), cycle_cls.count_all(proc.id)) + + # get infos + data = ProcLink.calc_proc_link() + + # count matched per edge + for row in data: + if row.target_process_id: + dic_edge_cnt[f'{row.process_id}-{row.target_process_id}'] = row.matched_count + + return dic_proc_cnt, dic_edge_cnt + + +@log_execution_time() +def clear_data_before_gen_proc_link(): + # clear relation global id + GlobalRelation.delete_all() + for cycle_class in CYCLE_CLASSES: + cycle_class.clear_global_id() + + # clear log in global_detail + ProcLink.delete_all() + db.session.commit() + + # clear substring data + sensors = Sensor.query.all() + for sensor in sensors: + substr_check_res = get_from_to_substring_col(sensor) + if not substr_check_res: + continue + + substr_cls, from_char, to_char = substr_check_res + substr_cls.delete_by_sensor_id(sensor.id) + db.session.delete(sensor) + db.session.commit() diff --git a/histview2/api/trace_data/services/proc_link_simulation.py b/histview2/api/trace_data/services/proc_link_simulation.py new file mode 100644 index 0000000..acbf58b --- /dev/null +++ b/histview2/api/trace_data/services/proc_link_simulation.py @@ -0,0 +1,225 @@ +from collections import deque +from typing import List, Union, Dict + +from sqlalchemy import and_ + +from histview2 import db +from histview2.api.trace_data.services.proc_link import gen_trace_key_info +from histview2.common.common_utils import gen_sql_label +from histview2.common.constants import DataType +from histview2.common.logger import log_execution_time +from histview2.setting_module.models import CfgTrace, CfgProcess +from histview2.trace_data.models import find_cycle_class, Sensor, find_sensor_class + +PREDICT_SAMPLE = 10_000 + + +@log_execution_time('[SIMULATE GLOBAL ID]') +def sim_gen_global_id(edges: List[CfgTrace]): + """ + generate global id for universal db (root function) + """ + edges = sim_order_before_mapping_data(edges) + + # matched count on proc + dic_cycle_ids = {} + + # matched count on edge + dic_edge_cnt = {} + + # proc : rows in database + dic_proc_data = {} + + # filtered flags + filtered_procs = [] + + # backward start leaf procs + for edge in edges: + # matching keys + start_keys = gen_trace_key_info(edge, False) + end_keys = gen_trace_key_info(edge, True) + + start_proc_id = edge.target_process_id + start_proc_data = gen_start_proc_data(start_proc_id, dic_proc_data, filtered_procs, dic_cycle_ids) + if not start_proc_data: + continue + + dic_start_data = gen_dic_proc_data(start_proc_data, start_keys) + + end_proc_id = edge.self_process_id + end_proc_data = gen_end_proc_data(start_proc_id, start_keys, end_proc_id, end_keys, dic_proc_data) + dic_end_data = gen_dic_proc_data(end_proc_data, end_keys) + + # init count data + dic_cycle_ids.setdefault(start_proc_id, set()) + dic_cycle_ids.setdefault(end_proc_id, set()) + + # mapping + cnt = 0 + for keys, end_row in dic_end_data.items(): + start_row = dic_start_data.get(keys) + if not start_row: + continue + + dic_cycle_ids[start_proc_id].add(start_row.id) + dic_cycle_ids[end_proc_id].add(end_row.id) + cnt += 1 + + # count matched per edge + dic_edge_cnt[f'{end_proc_id}-{start_proc_id}'] = cnt + + dic_proc_cnt = {proc_id: [len(cycles), len(dic_proc_data[proc_id])] for proc_id, cycles in dic_cycle_ids.items()} + return dic_proc_cnt, dic_edge_cnt + + +def get_sample_data(proc_id, cols_filters: Union[Dict, List], from_time=None, limit=PREDICT_SAMPLE): + """ + build sql to query matching global + get cycles by proc_id + """ + dic_filter = {} + if isinstance(cols_filters, dict): + dic_filter = cols_filters + + column_names = list(cols_filters) + + cycle_cls = find_cycle_class(proc_id) + data = db.session.query(cycle_cls.id, cycle_cls.global_id, cycle_cls.time).filter(cycle_cls.process_id == proc_id) + data = data.order_by(cycle_cls.time, cycle_cls.id) + + offset = PREDICT_SAMPLE + if from_time: + data = data.filter(cycle_cls.time >= from_time) + offset = 0 + + # get sensor information of keys from database (run separate to reuse cache, keys are only 1 or 2 columns) + sensors = Sensor.query.filter(Sensor.process_id == proc_id).filter(Sensor.column_name.in_(column_names)).all() + + for sensor in sensors: + sensor_val_cls = find_sensor_class(sensor.id, DataType(sensor.type), auto_alias=True) + + filter_val = dic_filter.get(sensor.column_name) + if filter_val is None: + data = data.join(sensor_val_cls, sensor_val_cls.cycle_id == cycle_cls.id) + else: + data = data.join(sensor_val_cls, + and_(sensor_val_cls.cycle_id == cycle_cls.id, sensor_val_cls.value == filter_val)) + + data = data.filter(sensor_val_cls.sensor_id == sensor.id) + data = data.add_columns(sensor_val_cls.value.label(gen_sql_label(sensor.column_name))) + + data = data.limit(limit + offset) + + data = data.all() + + # do not use offset , because maybe records count < 50 + if not from_time: + data = data[min(offset * 2, len(data)) // 2:] + + return data + + +@log_execution_time() +def sim_order_before_mapping_data(edges: List[CfgTrace]): + """ trace all node in dic_node , and gen sql + """ + ordered_edges = [] + + max_loop = len(edges) * 10 + edges = deque(edges) + cnt = 0 + while edges: + if cnt > max_loop: + raise Exception('Edges made a ring circle, You must re-setting tracing edge to break the ring circle!!!') + + # get first element + edge = edges.popleft() + + # check if current start proc is in others edge's end procs + # if YES , we must wait for these end proc run first( move the current edge to the end) + # traceback. So target => start , self => end + if any((edge.target_process_id == other_edge.self_process_id for other_edge in edges)): + # move to the end of queue + edges.append(edge) + cnt += 1 + else: + ordered_edges.append(edge) + cnt = 0 + + return ordered_edges + + +def gen_start_proc_data(proc_id, dic_proc_data, filtered_procs, dic_cycle_ids): + # get sample data + proc_data = dic_proc_data.get(proc_id) + if proc_data: + # already a node of previous edge + if proc_id not in filtered_procs: + filter_cycle_ids = dic_cycle_ids.get(proc_id) + + # only use data that matched before edge ( as a end proc of previous edge ) + if filter_cycle_ids: + proc_data = [row for row in proc_data if row.id in filter_cycle_ids] + elif filter_cycle_ids is not None: + proc_data = [] + + # save after filtered + filtered_procs.append(proc_id) + dic_proc_data[proc_id] = proc_data + else: + # end leaf proc case + cfg_proc = CfgProcess.query.get(proc_id) + serials = cfg_proc.get_serials() + proc_data = get_sample_data(proc_id, serials) + dic_proc_data[proc_id] = proc_data + + return proc_data + + +def gen_end_proc_data(start_proc_id, start_keys, end_proc_id, end_keys, dic_proc_data): + # get sample data + end_proc_data = dic_proc_data.get(end_proc_id) + + # reuse already exist data + if end_proc_data: + return end_proc_data + + # get end proc time by start proc condition + start_proc_data = dic_proc_data[start_proc_id] + from_time = find_from_time(start_proc_data, start_keys, end_proc_id, end_keys) + + # get data from db + cfg_proc = CfgProcess.query.get(end_proc_id) + serials = cfg_proc.get_serials() + end_proc_data = get_sample_data(end_proc_id, serials, from_time=from_time) + dic_proc_data[end_proc_id] = end_proc_data + + return end_proc_data + + +def find_from_time(start_proc_data, start_keys, end_proc_id, end_keys): + row = start_proc_data[0] + dic_keys = {end_key.column_name: getattr(row, gen_sql_label(start_key.column_name)) + for start_key, end_key in zip(start_keys, end_keys)} + + end_proc_data = get_sample_data(end_proc_id, dic_keys, limit=1) + if end_proc_data: + return end_proc_data[0].time + + return row.time + + +def gen_dic_proc_data(data, trace_key_infos): + dic_filter = {} + for row in data: + keys = [] + for key in trace_key_infos: + val = str(getattr(row, gen_sql_label(key.column_name))) + if key.from_char: + val = val[key.from_char - 1:key.to_char] + + keys.append(val) + + dic_filter[tuple(keys)] = row + + return dic_filter diff --git a/histview2/api/trace_data/services/regex_infinity.py b/histview2/api/trace_data/services/regex_infinity.py new file mode 100644 index 0000000..b0e476c --- /dev/null +++ b/histview2/api/trace_data/services/regex_infinity.py @@ -0,0 +1,202 @@ +import re +from collections import defaultdict + +import numpy as np +import pandas as pd +from pandas import DataFrame + +from histview2.common.logger import log_execution_time +from histview2.trace_data.models import Cycle + +PATTERN_POS_1 = re.compile(r'^(9{4,}(\.0+)?|9{1,3}\.9{3,}0*)$') +# PATTERN_NEG_1 = re.compile(r'^-(9{4,}(\.0+)?|9{1,3}\.9{3,}0*)$') + +# support to identify -9999.9 as -inf +PATTERN_NEG_1 = re.compile(r'^-(9{4,}(\.)?|9{3,}\.9+|9{1,3}\.?9{3,})0*$') + +PATTERN_POS_2 = re.compile(r'^((\d)\2{3,}(\.0+)?|(\d)\4{0,2}\.\4{3,}0*)$') +PATTERN_NEG_2 = re.compile(r'^-((\d)\2{3,}(\.0+)?|(\d)\4{0,2}\.\4{3,}0*)$') + +PATTERN_3 = re.compile(r'^(-+|0+(\d)\2{3,}(\.0+)?|(.)\3{4,}0*)$') + +# regex filter exclude columns +EXCLUDE_COLS = [Cycle.id.key, Cycle.global_id.key, Cycle.time.key, Cycle.is_outlier.key] + + +@log_execution_time() +def filter_method(df: DataFrame, col_name, idxs, cond_gen_func, return_vals): + if len(idxs) == 0: + return df + + target_data = df.loc[idxs, col_name].astype(str) + if len(target_data) == 0: + return df + + conditions = cond_gen_func(target_data) + df.loc[target_data.index, col_name] = np.select(conditions, return_vals, df.loc[target_data.index, col_name]) + return df + + +@log_execution_time() +def validate_numeric_minus(df: DataFrame, col_name, return_vals): + num = 0 + if df[col_name].count() == 0: + return df + + min_val = df[col_name].min() + if min_val >= num: + return df + + # return_vals = [pd.NA, pd.NA] + # idxs = df.eval(f'{col_name} < {num}') + idxs = pd.eval(f'df["{col_name}"] < {num}') + df = filter_method(df, col_name, idxs, gen_neg_conditions, return_vals) + + return df + + +@log_execution_time() +def validate_numeric_plus(df: DataFrame, col_name, return_vals): + num = 0 + if df[col_name].count() == 0: + return df + + max_val = df[col_name].max() + if max_val < num: + return df + + # return_vals = [pd.NA, pd.NA, pd.NA] + idxs = pd.eval(f'df["{col_name}"] >= {num}') + df = filter_method(df, col_name, idxs, gen_pos_conditions, return_vals) + + return df + + +@log_execution_time() +def validate_string(df: DataFrame, col_name): + if df[col_name].count() == 0: + return df + + target_data = df[col_name].astype(str) + if len(target_data) == 0: + return df + + conditions = gen_all_conditions(target_data) + return_vals = ['inf', '-inf', 'inf', '-inf', pd.NA] + df.loc[target_data.index, col_name] = np.select(conditions, return_vals, df.loc[target_data.index, col_name]) + + return df + + +def gen_pos_conditions(df_str: DataFrame): + return [df_str.str.contains(PATTERN_POS_1), df_str.str.contains(PATTERN_POS_2), df_str.str.contains(PATTERN_3)] + + +def gen_neg_conditions(df_str: DataFrame): + return [df_str.str.contains(PATTERN_NEG_1), df_str.str.contains(PATTERN_NEG_2)] + + +def gen_all_conditions(df_str: DataFrame): + return [df_str.str.contains(PATTERN_POS_1), + df_str.str.contains(PATTERN_NEG_1), + df_str.str.contains(PATTERN_POS_2), + df_str.str.contains(PATTERN_NEG_2), + df_str.str.contains(PATTERN_3)] + + +@log_execution_time() +def get_changed_value_after_validate(df_before: DataFrame, df_after: DataFrame): + checked_cols = [] + dic_abnormal = defaultdict(list) + + for col in df_before.columns: + if not check_validate_target_column(col): + continue + + checked_cols.append(col) + original_val = f'__{col}__' + s_before = df_before[col] + s_after = df_after[col].drop_duplicates() + idxs = s_before[~s_before.isin(s_after)].index + + if not len(idxs): + continue + + df = pd.DataFrame() + df[col] = df_after[col][idxs] + df[original_val] = df_before[col][idxs] + df.drop_duplicates(inplace=True) + series = df.groupby(col)[original_val].apply(list) + + for idx, vals in series.items(): + dic_abnormal[idx].extend(vals) + + dic_abnormal = {key: list(set(vals)) for key, vals in dic_abnormal.items()} + + return checked_cols, dic_abnormal + + +@log_execution_time() +def validate_data_with_regex(df): + # convert data types + df = df.convert_dtypes() + + # integer cols + int_cols = df.select_dtypes(include='integer').columns.tolist() + return_vals = [pd.NA, pd.NA] + for col in int_cols: + if not check_validate_target_column(col): + continue + + df = validate_numeric_minus(df, col, return_vals) + df = validate_numeric_plus(df, col, return_vals + [pd.NA]) + + # float + float_cols = df.select_dtypes(include='float').columns.tolist() + return_neg_vals = [float('-inf'), float('-inf')] + return_pos_vals = [float('inf'), float('inf'), np.NAN] + for col in float_cols: + if not check_validate_target_column(col): + continue + + df = validate_numeric_minus(df, col, return_neg_vals) + df = validate_numeric_plus(df, col, return_pos_vals) + + # non-numeric cols + for col in df.columns: + if not check_validate_target_column(col): + continue + + if col in int_cols or col in float_cols: + continue + df = validate_string(df, col) + + return df + + +@log_execution_time() +def validate_data_with_simple_searching(df, checked_cols, dic_abnormal): + # convert data types + df = df.convert_dtypes() + + for col in checked_cols: + conditions = [] + results = [] + for result, vals in dic_abnormal.items(): + conditions.append(df[col].isin(vals)) + results.append(result) + + if conditions: + df[col] = np.select(conditions, results, df[col]) + + return df + + +def check_validate_target_column(col: str): + if col in EXCLUDE_COLS: + return False + + if col.startswith(Cycle.time.key): + return False + + return True diff --git a/histview2/api/trace_data/services/time_series_chart.py b/histview2/api/trace_data/services/time_series_chart.py new file mode 100644 index 0000000..9a20841 --- /dev/null +++ b/histview2/api/trace_data/services/time_series_chart.py @@ -0,0 +1,2885 @@ +import json +import re +import traceback +from collections import defaultdict, Counter +from copy import deepcopy +from itertools import groupby +from math import ceil +from typing import List, Dict + +import numpy as np +import pandas as pd +from loguru import logger +from numpy import quantile +from pandas import DataFrame, Series +from sqlalchemy import and_, or_ + +from histview2 import db +from histview2.api.trace_data.services.regex_infinity import validate_data_with_regex, get_changed_value_after_validate, \ + validate_data_with_simple_searching, check_validate_target_column +from histview2.common.common_utils import as_list, get_debug_data +from histview2.common.common_utils import start_of_minute, end_of_minute, convert_time, add_days, gen_sql_label, \ + gen_sql_like_value, gen_python_regex, chunks, gen_abbr_name +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.memoize import memoize +from histview2.common.services.ana_inf_data import calculate_kde_trace_data +from histview2.common.services.form_env import bind_dic_param_to_class +from histview2.common.services.request_time_out_handler import request_timeout_handling +from histview2.common.services.sse import notify_progress +from histview2.common.services.statistics import calc_summaries, get_mode +from histview2.common.sigificant_digit import signify_digit +from histview2.common.trace_data_log import trace_log, TraceErrKey, EventAction, Target, EventType, save_df_to_file +from histview2.setting_module.models import CfgConstant, CfgProcess, CfgProcessColumn, CfgFilter, CfgFilterDetail, \ + CfgVisualization +from histview2.trace_data.models import find_cycle_class, GlobalRelation, Sensor, Cycle, find_sensor_class +from histview2.trace_data.schemas import DicParam, EndProc, ConditionProc, CategoryProc + + +@log_execution_time('[TRACE DATA]') +@request_timeout_handling() +@trace_log((TraceErrKey.TYPE, TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventType.FPP, EventAction.PLOT, Target.GRAPH), send_ga=True) +@memoize(is_save_file=True) +def gen_graph_fpp(dic_param, max_graph=None): + dic_param, cat_exp, cat_procs, dic_cat_filters, use_expired_cache, temp_serial_column, temp_serial_order, \ + temp_serial_process, temp_x_option, *_ = customize_dic_param_for_reuse_cache(dic_param) + + dic_param, df, orig_graph_param, graph_param, graph_param_with_cate = gen_df(dic_param, + _use_expired_cache=use_expired_cache) + + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + # use for enable and disable index columns + all_procs = [] + all_cols = [] + for proc in graph_param.array_formval: + all_procs.append(proc.proc_id) + all_cols.extend(proc.col_ids) + + dic_param[COMMON][DF_ALL_PROCS] = all_procs + dic_param[COMMON][DF_ALL_COLUMNS] = all_cols + + if cat_exp: + for i, val in enumerate(cat_exp): + dic_param[COMMON][f'{CAT_EXP_BOX}{i + 1}'] = val + if cat_procs: + dic_param[COMMON][CATE_PROCS] = cat_procs + + orig_graph_param = bind_dic_param_to_class(dic_param) + + # order index with other param + if temp_x_option: + df = check_and_order_data(df, dic_proc_cfgs, temp_x_option, temp_serial_process, temp_serial_column, + temp_serial_order) + dic_param[COMMON][X_OPTION] = temp_x_option + dic_param[COMMON][SERIAL_PROCESS] = temp_serial_process + dic_param[COMMON][SERIAL_COLUMNS] = temp_serial_column + dic_param[COMMON][SERIAL_ORDER] = temp_serial_order + + # distinct category for filter setting form + cate_col_ids = [] + for proc in graph_param.common.cate_procs or []: + cate_col_ids += proc.col_ids + + dic_unique_cate = gen_unique_data(df, dic_proc_cfgs, cate_col_ids) + cat_exp_list = gen_unique_data(df, dic_proc_cfgs, graph_param.common.cat_exp) + cat_exp_list = list(cat_exp_list.values()) + + # filter list + df = filter_df(df, dic_cat_filters) + + # reset index (keep sorted position) + df.reset_index(inplace=True, drop=True) + + str_cols = dic_param.get(STRING_COL_IDS) + dic_str_cols = get_str_cols_in_end_procs(dic_proc_cfgs, orig_graph_param) + dic_ranks = gen_before_rank_dict(df, dic_str_cols) + dic_data, is_graph_limited = gen_dic_data(df, orig_graph_param, graph_param_with_cate, max_graph) + dic_param[IS_GRAPH_LIMITED] = is_graph_limited + + is_thin_data = False + # 4000 chunks x 3 values(min,median,max) + dic_thin_param = None + if len(df) > THIN_DATA_COUNT: + is_thin_data = True + dic_thin_param = deepcopy(dic_param) + + dic_param = gen_dic_param(df, dic_param, dic_data, dic_proc_cfgs) + gen_dic_serial_data_from_df(df, dic_proc_cfgs, dic_param) + + # calculate_summaries + calc_summaries(dic_param) + + # calc common scale y min max + min_max_list, all_graph_min, all_graph_max = calc_raw_common_scale_y(dic_param[ARRAY_PLOTDATA], str_cols) + + # get min max order columns + output_orders = [] + x_option = graph_param.common.x_option + if x_option == 'INDEX' and graph_param.common.serial_columns: + group_col = '__group_col__' + dic_cfg_cols = {cfg_col.id: cfg_col for cfg_col in + CfgProcessColumn.get_by_ids(graph_param.common.serial_columns)} + dic_order_cols = {} + for order_col_id in graph_param.common.serial_columns: + cfg_col = dic_cfg_cols.get(order_col_id) + if not cfg_col: + continue + + sql_label = gen_sql_label(RANK_COL, cfg_col.id, cfg_col.column_name) + if sql_label not in df.columns: + sql_label = gen_sql_label(cfg_col.id, cfg_col.column_name) + if sql_label not in df.columns: + continue + + dic_order_cols[sql_label] = cfg_col + + df_order = df[dic_order_cols] + if is_thin_data: + count_per_group = ceil(len(df_order) / THIN_DATA_CHUNK) + df_order[group_col] = df_order.index // count_per_group + df_order = df_order.dropna().groupby(group_col).agg(['min', 'max']) + for sql_label, col in dic_order_cols.items(): + output_orders.append(dict(name=col.name, min=df_order[(sql_label, 'min')].tolist(), + max=df_order[(sql_label, 'max')].tolist())) + else: + for sql_label, col in dic_order_cols.items(): + output_orders.append(dict(name=col.name, value=df_order[sql_label].tolist())) + + full_arrays = None + if is_thin_data: + full_arrays = make_str_full_array_y(dic_param) + list_summaries = get_summary_infos(dic_param) + dic_cat_exp_labels = None + if graph_param.common.cat_exp: + df, dic_cat_exp_labels = gen_thin_df_cat_exp(dic_param) + else: + add_serials_to_thin_df(dic_param, df) + + copy_dic_param_to_thin_dic_param(dic_param, dic_thin_param) + dic_param = gen_thin_dic_param(df, dic_thin_param, dic_proc_cfgs, dic_cat_exp_labels, dic_ranks) + dic_param['is_thin_data'] = is_thin_data + + for i, plot in enumerate(dic_param[ARRAY_PLOTDATA]): + plot[SUMMARIES] = list_summaries[i] + else: + dic_param = gen_category_info(dic_param, dic_ranks) + set_str_rank_to_dic_param(dic_param, dic_ranks, full_arrays) + set_str_category_data(dic_param, dic_ranks) + + calc_scale_info(dic_param[ARRAY_PLOTDATA], min_max_list, all_graph_min, all_graph_max, str_cols) + + # kde + gen_kde_data_trace_data(dic_param, full_arrays) + + # add unique category values + for dic_cate in dic_param.get(CATEGORY_DATA) or []: + col_id = dic_cate['column_id'] + dic_cate[UNIQUE_CATEGORIES] = dic_unique_cate[col_id][UNIQUE_CATEGORIES] if dic_unique_cate.get(col_id) else [] + if len(set(dic_cate.get('data', []))) > 200: + dic_cate[IS_OVER_UNIQUE_LIMIT] = True + else: + dic_cate[IS_OVER_UNIQUE_LIMIT] = False + + dic_param[CAT_EXP_BOX] = cat_exp_list + dic_param[INDEX_ORDER_COLS] = output_orders + dic_param['proc_name'] = {k: proc.name for (k, proc) in dic_proc_cfgs.items()} + + # remove unnecessary data + # if graph_param.common.x_option == 'INDEX': + # del dic_param[TIMES] + + return dic_param + + +def customize_dic_param_for_reuse_cache(dic_param): + use_expired_cache = False + for name in (DIC_CAT_FILTERS, TEMP_CAT_EXP, TEMP_CAT_PROCS, TEMP_X_OPTION, TEMP_SERIAL_PROCESS, TEMP_SERIAL_COLUMN, + TEMP_SERIAL_ORDER, MATRIX_COL, COLOR_ORDER): + if name in dic_param[COMMON]: + use_expired_cache = True + break + dic_cat_filters = json.loads(dic_param[COMMON].get(DIC_CAT_FILTERS, {})) if isinstance( + dic_param[COMMON].get(DIC_CAT_FILTERS, {}), str) else dic_param[COMMON].get(DIC_CAT_FILTERS, {}) + cat_exp = [int(id) for id in dic_param[COMMON].get(TEMP_CAT_EXP, []) if id] + cat_procs = dic_param[COMMON].get(TEMP_CAT_PROCS, []) + for name in (DIC_CAT_FILTERS, TEMP_CAT_EXP, TEMP_CAT_PROCS): + if name in dic_param[COMMON]: + dic_param[COMMON].pop(name) + dic_param, temp_x_option, temp_serial_process, temp_serial_column, temp_serial_order = \ + prepare_temp_x_option(dic_param) + + matrix_col = dic_param[COMMON].get(MATRIX_COL) + if matrix_col and isinstance(matrix_col, (list, tuple)): + matrix_col = matrix_col[0] + + if matrix_col: + matrix_col = int(matrix_col) + + # set default for color order ( default : data value ) + color_order = dic_param[COMMON].get(COLOR_ORDER) + if color_order: + color_order = ColorOrder(int(color_order)) + else: + color_order = ColorOrder.DATA + + return dic_param, cat_exp, cat_procs, dic_cat_filters, use_expired_cache, temp_serial_column, temp_serial_order, \ + temp_serial_process, temp_x_option, matrix_col, color_order + + +@notify_progress(60) +def gen_graph(dic_param, max_graph=None): + dic_param, df, orig_graph_param, graph_param, graph_param_with_cate = gen_df(dic_param) + dic_data, is_graph_limited = gen_dic_data(df, orig_graph_param, graph_param_with_cate, max_graph) + dic_param = gen_dic_param(df, dic_param, dic_data) + dic_param[IS_GRAPH_LIMITED] = is_graph_limited + + return dic_param + + +@log_execution_time() +def gen_dic_data(df, orig_graph_param, graph_param_with_cate, max_graph=None): + # create output data + cat_exp_cols = orig_graph_param.common.cat_exp + is_graph_limited = False + if cat_exp_cols: + dic_cfg_cat_exps = {cfg_col.id: cfg_col for cfg_col in CfgProcessColumn.get_by_ids(cat_exp_cols)} + dic_data, is_graph_limited = gen_dic_data_cat_exp_from_df(df, orig_graph_param, dic_cfg_cat_exps, max_graph) + dic_cates = defaultdict(dict) + for proc in orig_graph_param.common.cate_procs: + for col_id, col_name in zip(proc.col_ids, proc.col_names): + sql_label = gen_sql_label(col_id, col_name) + dic_cates[proc.proc_id][col_id] = df[sql_label].tolist() if sql_label in df.columns else [] + + dic_data[CATEGORY_DATA] = dic_cates + else: + dic_data = gen_dic_data_from_df(df, graph_param_with_cate, cat_exp_mode=True) + + return dic_data, is_graph_limited + + +def prepare_temp_x_option(dic_param): + params = [TEMP_X_OPTION, TEMP_SERIAL_PROCESS, TEMP_SERIAL_COLUMN, TEMP_SERIAL_ORDER] + temp_x_option = dic_param[COMMON].get(TEMP_X_OPTION, '') + temp_serial_process = as_list(dic_param[COMMON].get(TEMP_SERIAL_PROCESS)) + temp_serial_column = as_list(dic_param[COMMON].get(TEMP_SERIAL_COLUMN)) + temp_serial_order = as_list(dic_param[COMMON].get(TEMP_SERIAL_ORDER)) + + for param in params: + if param in dic_param[COMMON]: + dic_param[COMMON].pop(param) + + return dic_param, temp_x_option, temp_serial_process, temp_serial_column, temp_serial_order + + +@log_execution_time() +@memoize(is_save_file=True) +def gen_df(dic_param, _use_expired_cache=False): + """tracing data to show graph + 1 start point x n end point + filter by condition point + """ + # bind dic_param + orig_graph_param = bind_dic_param_to_class(dic_param) + cat_exp_col = orig_graph_param.common.cat_exp + + graph_param_with_cate = bind_dic_param_to_class(dic_param) + graph_param_with_cate.add_cate_procs_to_array_formval() + + graph_param = bind_dic_param_to_class(dic_param) + + # add start proc + graph_param.add_start_proc_to_array_formval() + + # add condition procs + graph_param.add_cond_procs_to_array_formval() + + # add category + if cat_exp_col: + graph_param.add_cat_exp_to_array_formval() + + graph_param.add_cate_procs_to_array_formval() + + # get serials + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + serial_ids = [serial.id for serial in proc_cfg.get_serials(column_name_only=False)] + proc.add_cols(serial_ids) + + # get order columns + if graph_param.common.x_option == 'INDEX': + for proc_id, col_id in zip(graph_param.common.serial_processes, graph_param.common.serial_columns): + if proc_id and col_id: + proc_id = int(proc_id) + col_id = int(col_id) + graph_param.add_proc_to_array_formval(proc_id, col_id) + + # get data from database + df, actual_record_number, is_res_limited = get_data_from_db(graph_param) + + # string columns + df, str_cols = rank_str_cols(df, dic_proc_cfgs, orig_graph_param) + dic_param[STRING_COL_IDS] = str_cols + + # check filter match or not ( for GUI show ) + matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids = main_check_filter_detail_match_graph_data( + graph_param, df) + + # matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + dic_param[MATCHED_FILTER_IDS] = matched_filter_ids + dic_param[UNMATCHED_FILTER_IDS] = unmatched_filter_ids + dic_param[NOT_EXACT_MATCH_FILTER_IDS] = not_exact_match_filter_ids + + # apply coef for text + df = apply_coef_text(df, graph_param_with_cate, dic_proc_cfgs) + + # order data by order columns + x_option = graph_param.common.x_option or 'TIME' + serial_processes = graph_param.common.serial_processes or [] + serial_cols = graph_param.common.serial_columns or [] + serial_orders = graph_param.common.serial_orders or [] + df = check_and_order_data(df, dic_proc_cfgs, x_option, serial_processes, serial_cols, serial_orders) + + # flag to show that trace result was limited + dic_param[DATA_SIZE] = df.memory_usage(deep=True).sum() + dic_param[IS_RES_LIMITED] = is_res_limited + dic_param[ACTUAL_RECORD_NUMBER] = actual_record_number + + return dic_param, df, orig_graph_param, graph_param, graph_param_with_cate + + +@log_execution_time() +def gen_dic_param(df, dic_param, dic_data, dic_proc_cfgs=None, dic_cates=None, dic_org_cates=None, + is_get_chart_infos=True): + graph_param = bind_dic_param_to_class(dic_param) + if not dic_proc_cfgs: + dic_proc_cfgs = get_procs_in_dic_param(graph_param) + + times = df[Cycle.time.key].tolist() or [] + if times and str(times[0])[-1].upper() != 'Z': + times = [convert_time(tm) for tm in times if tm] + + # get chart infos + chart_infos = None + original_graph_configs = None + if is_get_chart_infos: + chart_infos, original_graph_configs = get_chart_infos(graph_param, dic_data, times) + + dic_param[ARRAY_FORMVAL], dic_param[ARRAY_PLOTDATA] = gen_plotdata_fpp(graph_param, dic_data, chart_infos, + original_graph_configs) + dic_param[CATEGORY_DATA] = gen_category_data(dic_proc_cfgs, graph_param, dic_cates or dic_data, dic_org_cates) + dic_param[TIMES] = times + + if Cycle.id.key in df.columns: + dic_param[CYCLE_IDS] = df.id.tolist() + + return dic_param + + +@log_execution_time() +def rank_str_cols(df: DataFrame, dic_proc_cfgs, graph_param: DicParam): + dic_str_cols = get_str_cols_in_end_procs(dic_proc_cfgs, graph_param) + str_cols = [] + for sql_label, (before_rank_label, _, col_id, _) in dic_str_cols.items(): + if sql_label not in df.columns: + continue + + df[before_rank_label] = df[sql_label] + df[sql_label] = np.where(df[sql_label].isnull(), df[sql_label], df[sql_label].astype('category').cat.codes + 1) + + df[sql_label] = df[sql_label].convert_dtypes() + str_cols.append(col_id) + + return df, str_cols + + +@log_execution_time() +def get_str_cols_in_end_procs(dic_proc_cfgs, graph_param: DicParam): + dic_output = {} + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + dic_cols = {col.id: col for col in proc_cfg.get_cols_by_data_type(DataType.TEXT, False)} + for col_id, col_name in zip(proc.col_ids, proc.col_names): + cfg_col = dic_cols.get(col_id) + if cfg_col is None: + continue + + rank_col_name = gen_sql_label(col_id, col_name) + before_rank_col_name = gen_sql_label(RANK_COL, rank_col_name) + dic_output[rank_col_name] = (before_rank_col_name, proc.proc_id, col_id, col_name) + + return dic_output + + +@log_execution_time() +def gen_before_rank_dict(df: DataFrame, dic_str_cols): + dic_output = {} + for sql_label, (before_rank_label, _, col_id, _) in dic_str_cols.items(): + if before_rank_label in df.columns: + df_rank = df[[sql_label, before_rank_label]].drop_duplicates().dropna() + dic_output[col_id] = dict(zip(df_rank[sql_label], df_rank[before_rank_label])) + + return dic_output + + +@log_execution_time() +def set_str_rank_to_dic_param(dic_param, dic_ranks, dic_full_array_y=None): + for i, plot in enumerate(dic_param[ARRAY_PLOTDATA]): + col_id = plot.get(END_COL_ID) + dic_col_ranks = dic_ranks.get(col_id) + if not dic_col_ranks: + continue + + # enc col (show graph) + # plot[ARRAY_Y] = reduce_stepped_chart_data(plot.get(ARRAY_Y)) + plot[ARRAY_Y_MIN] = None + plot[ARRAY_Y_MAX] = None + + category_distributed = {} + full_dat = dic_full_array_y[i] if dic_full_array_y else plot.get(ARRAY_Y) + none_idxs = plot.get(NONE_IDXS) + total_counts = 0 + + dic_cat_counter = Counter(full_dat) + ranks = [] + before_ranks = [] + for rank_val, cat_count in dic_cat_counter.items(): + if rank_val is None: + continue + + cat_name = dic_col_ranks.get(rank_val) + if cat_name is None: + continue + + ranks.append(rank_val) + before_ranks.append(cat_name) + + short_name = gen_abbr_name(cat_name) + category_distributed[cat_name] = { + 'counts': cat_count, + 'short_name': short_name, + 'counts_org': cat_count, + 'pctg': 0, + } + total_counts += cat_count + + plot[RANK_COL] = [ranks, before_ranks] + + for k, cat in category_distributed.items(): + cat_dist = signify_digit(cat['counts'] * 100 / total_counts) if total_counts else 0 + category_distributed[k]['pctg'] = cat_dist + category_distributed[k]['counts'] = '{} ({}%)'.format(cat['counts'], cat_dist) + + # show end col summary info + series = pd.Series(full_dat) + if none_idxs is None: + pass + else: + if none_idxs: + series = series[(series.notnull()) | (series.index.isin(none_idxs))] + else: + series.dropna(inplace=True) + + n_total = len(series) + non_na_count = len(series.dropna()) + na_count = n_total - non_na_count + + step_chart_summary = { + N_TOTAL: n_total, + N: non_na_count, + N_PCTG: signify_digit(100 * non_na_count / n_total) if n_total else 0, + N_NA: na_count, + N_NA_PCTG: signify_digit(100 * na_count / n_total) if n_total else 0 + } + plot[CAT_DISTRIBUTE] = category_distributed + plot[CAT_SUMMARY] = step_chart_summary + + +@log_execution_time() +def set_str_category_data(dic_param, dic_ranks): + for dic_cate in dic_param[CATEGORY_DATA]: + col_id = dic_cate.get('column_id') + if col_id not in dic_ranks: + continue + + dic_cate['data'] = pd.Series(dic_cate.get('data')).map(dic_ranks[col_id]).tolist() + + +@log_execution_time() +def gen_thin_dic_param(df, dic_param, dic_proc_cfgs, dic_cat_exp_labels=None, dic_ranks=None): + # bind dic_param + graph_param = bind_dic_param_to_class(dic_param) + dic_datetime_serial_cols = get_serials_and_date_col(graph_param, dic_proc_cfgs) + dic_str_cols = get_str_cols_in_end_procs(dic_proc_cfgs, graph_param) + df_thin, dic_cates, dic_org_cates, group_counts = reduce_data(df, graph_param, dic_str_cols) + + # create output data + df_cat_exp = gen_df_thin_values(df, graph_param, df_thin, dic_str_cols) + dic_data = gen_dic_data_from_df(df_cat_exp, graph_param, cat_exp_mode=True, dic_cat_exp_labels=dic_cat_exp_labels, + calculate_cycle_time=False) + dic_param = gen_dic_param(df_cat_exp, dic_param, dic_data, dic_proc_cfgs, dic_cates, dic_org_cates, + is_get_chart_infos=False) + gen_dic_serial_data_from_df_thin(df_cat_exp, dic_param, dic_datetime_serial_cols, dic_ranks) + + # get start proc time + start_tm = start_of_minute(graph_param.common.start_date, graph_param.common.start_time) + end_tm = end_of_minute(graph_param.common.end_date, graph_param.common.end_time) + threshold_filter_detail_ids = graph_param.common.threshold_boxes + + # gen min max for thin data + for plot in dic_param[ARRAY_PLOTDATA]: + sql_label = gen_sql_label(plot[END_COL_ID], plot[END_COL_NAME], plot.get(CAT_EXP_BOX)) + time_label = gen_sql_label(TIMES, sql_label) + min_label = gen_sql_label(ARRAY_Y_MIN, sql_label) + max_label = gen_sql_label(ARRAY_Y_MAX, sql_label) + cycle_label = gen_sql_label(CYCLE_IDS, sql_label) + + if time_label in df_cat_exp.columns: + plot[ARRAY_X] = df_cat_exp[time_label].replace({np.nan: None}).tolist() + # get chart infos + plot[CHART_INFOS_ORG], plot[CHART_INFOS] = get_chart_info_detail(plot[ARRAY_X], plot[END_COL_ID], + threshold_filter_detail_ids, + plot[END_PROC_ID], + graph_param.common.start_proc, + start_tm, end_tm, + dic_param[TIMES]) + + if min_label in df_cat_exp.columns: + plot[ARRAY_Y_MIN] = df_cat_exp[min_label].tolist() + + if max_label in df_cat_exp.columns: + plot[ARRAY_Y_MAX] = df_cat_exp[max_label].tolist() + + if cycle_label in df_cat_exp.columns: + plot[CYCLE_IDS] = df_cat_exp[cycle_label].tolist() + + if plot[END_COL_ID] in dic_ranks: + # category variable + p_array_y = pd.Series(plot[ARRAY_Y]).dropna().tolist() + cat_size = 0 + if len(p_array_y): + cat_size = np.unique(p_array_y).size + plot[CAT_TOTAL] = cat_size + plot[IS_CAT_LIMITED] = True if cat_size >= MAX_CATEGORY_SHOW else False + + # ignore show none value in thin mode + plot[NONE_IDXS] = [] + + # group count + dic_param[THIN_DATA_GROUP_COUNT] = group_counts + + return dic_param + + +def make_str_full_array_y(dic_param): + return [plot[ARRAY_Y] for plot in dic_param[ARRAY_PLOTDATA]] + + +def get_summary_infos(dic_param): + return [plot[SUMMARIES] for plot in dic_param[ARRAY_PLOTDATA]] + + +@log_execution_time() +def check_and_order_data(df, dic_proc_cfgs, x_option='TIME', serial_processes=[], serial_cols=[], serial_orders=[]): + if x_option.upper() == 'TIME': + df = df.sort_values(Cycle.time.key, ascending=True) + return df + + cols = [] + orders = [] + for proc_id in set(serial_processes): + if not proc_id: + continue + + proc_cfg: CfgProcess = dic_proc_cfgs.get(int(proc_id)) + if not proc_cfg: + continue + order_cols: List[CfgProcessColumn] = proc_cfg.get_order_cols(column_name_only=False) + + if not order_cols: + continue + + dic_order_cols = {col.id: gen_sql_label(col.id, col.column_name) for col in order_cols} + for col_id, order in zip(serial_cols, serial_orders): + if not col_id: + continue + col_label = dic_order_cols.get(int(col_id)) + if col_label and col_label in df.columns and col_label not in cols: + cols.append(dic_order_cols.get(int(col_id))) + orders.append(bool(int(order))) + + if cols: + df = df.sort_values(cols, ascending=orders) + + return df + + +def gen_blank_df_end_col(proc: EndProc, columns): + dic_cols = {} + for cfg_col in columns: + if cfg_col.column_name not in proc.col_names: + continue + + name = gen_sql_label(cfg_col.id, cfg_col.column_name) + dic_cols[name] = [] + + dic_cols.update({Cycle.id.key: [], Cycle.global_id.key: [], Cycle.time.key: []}) + return pd.DataFrame(dic_cols) + + +def gen_blank_df_end_cols(procs: List[EndProc]): + params = dict() + for proc in procs: + params.update({gen_sql_label(col_id, proc.col_names[idx]): [] for idx, col_id in enumerate(proc.col_ids)}) + params.update({'{}{}'.format(Cycle.time.key, create_rsuffix(proc.proc_id)): []}) + params.update({Cycle.id.key: [], Cycle.global_id.key: [], Cycle.time.key: []}) + + df = pd.DataFrame(params) + df = df.append(pd.Series(), ignore_index=True) + return df.replace({np.nan: ''}) + + +def gen_df_end(proc: EndProc, start_relate_ids=None, start_tm=None, end_tm=None): + proc_id = proc.proc_id + + # get serials + cfg_cols = CfgProcessColumn.get_all_columns(proc_id) + serials = [col for col in cfg_cols if col.is_serial_no] + serials = [gen_sql_label(serial.id, serial.column_name) for serial in serials] + + # get sensor values + df_end = get_sensor_values(proc, start_relate_ids=start_relate_ids, start_tm=start_tm, end_tm=end_tm) + if df_end.empty: + df_end = gen_blank_df_end_col(proc, cfg_cols) + + # filter duplicate + if df_end.columns.size: + df_end = df_end[df_end.eval('global_id.notnull()')] + + # drop duplicate + if df_end.columns.size and serials: + cols = [col for col in serials if col in df_end.columns] + if cols: + df_end = df_end.drop_duplicates(subset=cols, keep='last') + + # set index + if df_end.columns.size: + df_end.set_index(Cycle.global_id.key, inplace=True) + + return df_end + + +def gen_df_end_same_with_start(proc: EndProc, start_proc_id, start_tm, end_tm, drop_duplicate=True, + with_limit=None): + # proc_id = proc.proc_id + + # get serials + serials = CfgProcessColumn.get_serials(start_proc_id) + serials = [gen_sql_label(serial.id, serial.column_name) for serial in serials] + + # get sensor values + df_end = get_sensor_values(proc, start_tm=start_tm, end_tm=end_tm, use_global_id=False, with_limit=with_limit) + if df_end.empty: + return pd.DataFrame() + + df_end.set_index(Cycle.id.key, inplace=True) + + # if only 1 proc, show all data without filter duplicate + if drop_duplicate and len(serials): # TODO ask PO + cols = [col for col in serials if col in df_end.columns] + if cols: + df_end.drop_duplicates(subset=cols, keep='last', inplace=True) + + return df_end + + +def filter_proc_same_with_start(proc: ConditionProc, start_tm, end_tm, with_limit=None): + if not proc.dic_col_id_filters: + return None + + cond_records = get_cond_data(proc, start_tm=start_tm, end_tm=end_tm, use_global_id=False, with_limit=with_limit) + # important : None is no filter, [] is no data + if cond_records is None: + return None + + return [cycle.id for cycle in cond_records] + + +def filter_proc(proc: ConditionProc, start_relate_ids=None, start_tm=None, end_tm=None): + if not proc.dic_col_id_filters: + return None + + cond_records = get_cond_data(proc, start_relate_ids=start_relate_ids, start_tm=start_tm, end_tm=end_tm) + # important : None is no filter, [] is no data + if cond_records is None: + return None + + return [cycle.global_id for cycle in cond_records] + + +def create_rsuffix(proc_id): + return '_{}'.format(proc_id) + + +@log_execution_time() +@notify_progress(30) +@memoize(is_save_file=True) +def graph_one_proc(proc_id, start_tm, end_tm, cond_procs, end_procs, sql_limit, same_proc_only=False, + with_time_order=True): + """ get data from database + + Arguments: + trace {[type]} -- [description] + dic_param {[type]} -- [description] + + Returns: + [type] -- [description] + """ + + # start proc + data = get_start_proc_data(proc_id, start_tm, end_tm, with_limit=sql_limit, with_time_order=with_time_order) + # no data + if not data: + return gen_blank_df() + + df_start = pd.DataFrame(data) + df_start.set_index(Cycle.id.key, inplace=True) + + # condition + for proc in cond_procs: + if same_proc_only and proc.proc_id != proc_id: + continue + + ids = filter_proc_same_with_start(proc, start_tm, end_tm, with_limit=sql_limit) + if ids is None: + continue + + df_start = df_start[df_start.index.isin(ids)] + + # end proc + for proc in end_procs: + if same_proc_only and proc.proc_id != proc_id: + continue + df_end = gen_df_end_same_with_start(proc, proc_id, start_tm, end_tm, drop_duplicate=False) + df_start = df_start.join(df_end, rsuffix=create_rsuffix(proc.proc_id)).reset_index() + + return df_start + + +@log_execution_time() +@notify_progress(30) +@memoize(is_save_file=True) +def graph_many_proc(start_proc_id, start_tm, end_tm, cond_procs: List[ConditionProc], end_procs: List[EndProc], + sql_limit, with_time_order=True): + """ get data from database + + Arguments: + trace {[type]} -- [description] + dic_param {[type]} -- [description] + + Returns: + [type] -- [description] + """ + # without relate + data = get_start_proc_data(start_proc_id, start_tm, end_tm, with_limit=sql_limit, with_time_order=with_time_order) + # no data + if not data: + return gen_blank_df(), False + + df_start = pd.DataFrame(data) + + # with relate + data_with_relate_id = get_start_proc_data_with_relate_id(start_proc_id, start_tm, end_tm, with_limit=sql_limit) + if data_with_relate_id: + df_start_with_relate_id = pd.DataFrame(data_with_relate_id) + df_start = df_start.append(df_start_with_relate_id, ignore_index=True) + + # downcast data type + # data_types = {Cycle.global_id.key: np.int64, Cycle.is_outlier.key: 'category'} + # for col in data_types: + # df_start[col].replace({np.nan: None}, inplace=True) + # df_start = df_start.astype(data_types) + + start_relate_ids = list(df_start[df_start.eval('global_id.notnull()')][Cycle.global_id.key]) + + is_res_limited = True + if len(start_relate_ids) < 5000: + start_relate_ids = [start_relate_ids[x:x + 900] for x in range(0, len(start_relate_ids), 900)] + is_res_limited = False + else: + start_relate_ids = None + + # set index + df_start.set_index(Cycle.id.key, drop=False, inplace=True) + + # condition that same with start + cycle_ids = None + is_filter = False + for proc in cond_procs: + if not proc.proc_id == start_proc_id: + continue + + ids = filter_proc_same_with_start(proc, start_tm, end_tm, with_limit=sql_limit) + if ids is None: + continue + + if cycle_ids is None: + cycle_ids = set(ids) + else: + cycle_ids.intersection_update(ids) + + is_filter = True + + if is_filter: + df_start = df_start[df_start.index.isin(cycle_ids)] + if not df_start.columns.size: + return gen_blank_df(), False + + # end proc that same with start + for proc in end_procs: + if not proc.proc_id == start_proc_id: + continue + + # get sensor value data + df_end = gen_df_end_same_with_start(proc, proc.proc_id, start_tm, end_tm, with_limit=sql_limit) + df_start = df_start.join(df_end, how='inner', rsuffix=create_rsuffix(proc.proc_id)) + + if not df_start.columns.size: + return gen_blank_df(), False + + # get min max time {proc_id:[min,max]} + e_start_tm = convert_time(start_tm, return_string=False) + e_start_tm = add_days(e_start_tm, -14) + e_start_tm = convert_time(e_start_tm) + e_end_tm = convert_time(end_tm, return_string=False) + e_end_tm = add_days(e_end_tm, 14) + e_end_tm = convert_time(e_end_tm) + + global_ids = None + is_filter = False + for proc in cond_procs: + if proc.proc_id == start_proc_id: + continue + + ids = filter_proc(proc, start_relate_ids, e_start_tm, e_end_tm) + if ids is None: + continue + + if global_ids is None: + global_ids = set(ids) + else: + global_ids.intersection_update(ids) + + is_filter = True + + if is_filter: + if data_with_relate_id: + idxs = df_start[df_start[Cycle.global_id.key].isin(global_ids)].index + idxs = set(idxs) + df_start = df_start.loc[idxs] + else: + df_start = df_start[df_start[Cycle.global_id.key].isin(global_ids)] + + # set new Index + df_start.set_index(Cycle.global_id.key, inplace=True) + + # end proc + for proc in end_procs: + if proc.proc_id == start_proc_id: + continue + + df_end = gen_df_end(proc, start_relate_ids, e_start_tm, e_end_tm) + df_start = df_start.join(df_end, rsuffix=create_rsuffix(proc.proc_id)) + + # group by cycle id to drop duplicate ( 1:n with global relation) + df_start.set_index(Cycle.id.key, inplace=True) + if data_with_relate_id: + df_start = df_start.groupby(df_start.index).first().reset_index() + + # sort by time + if with_time_order: + df_start.sort_values(Cycle.time.key, inplace=True) + + return df_start, is_res_limited + + +@notify_progress(40) +@log_execution_time() +@trace_log((TraceErrKey.ACTION, TraceErrKey.TARGET), (EventAction.READ, Target.DATABASE)) +def get_data_from_db(graph_param: DicParam, with_time_order=True, is_save_df_to_file=True): + # DEBUG Function + if get_debug_data(DebugKey.IS_DEBUG_MODE.name): + df = get_debug_data(DebugKey.GET_DATA_FROM_DB.name) + return df, df.index.size, None + + # with limit + sql_limit = SQL_LIMIT + + # start proc + start_tm = start_of_minute(graph_param.common.start_date, graph_param.common.start_time) + end_tm = end_of_minute(graph_param.common.end_date, graph_param.common.end_time) + + is_res_limited = False + proc_ids = get_proc_ids_in_dic_param(graph_param) + if len(proc_ids) == 1: + df = graph_one_proc(graph_param.common.start_proc, start_tm, end_tm, graph_param.common.cond_procs, + graph_param.array_formval, sql_limit, + with_time_order=with_time_order) + else: + df, is_res_limited = graph_many_proc(graph_param.common.start_proc, start_tm, end_tm, + graph_param.common.cond_procs, graph_param.array_formval, sql_limit, + with_time_order=with_time_order) + + # reset index + df.reset_index(inplace=True) + + # save log + if is_save_df_to_file: + save_df_to_file(df) + + # with limit + actual_record_number = df.index.size + + # graph_param.common.is_validate_data = True + if graph_param.common.is_validate_data: + df = validate_data(df) + + return df, actual_record_number, is_res_limited + + +@log_execution_time() +def validate_data(df: DataFrame): + if len(df) > THIN_DATA_COUNT: + df_before = get_sample_df(df) + df_before = df_before.convert_dtypes() + df_after = validate_data_with_regex(df_before) + checked_cols, dic_abnormal = get_changed_value_after_validate(df_before, df_after) + df = validate_data_with_simple_searching(df, checked_cols, dic_abnormal) + else: + df = validate_data_with_regex(df) + + return df + + +@log_execution_time() +def get_sample_df(df): + sample_df = df.head(THIN_DATA_COUNT) + number_cols = df.select_dtypes(include=['integer', 'float']).columns.tolist() + for col in number_cols: + if not check_validate_target_column(col): + continue + try: + min_idx = df[col].idxmin() + max_idx = df[col].idxmax() + sample_df = sample_df.append(df.loc[min_idx], ignore_index=True) + sample_df = sample_df.append(df.loc[max_idx], ignore_index=True) + except Exception: + pass + + return sample_df + + +@log_execution_time() +def gen_df_thin_values(df: DataFrame, graph_param: DicParam, df_thin, dic_str_cols): + thin_idxs_len = len(df_thin) + thin_boxes = [None] * thin_idxs_len + df_cat_exp = pd.DataFrame() + df_cat_exp[Cycle.time.key] = thin_boxes + + # df_cat_exp[Cycle.time.key] = df_thin[Cycle.time.key] + if CAT_EXP_BOX in df_thin.columns: + df_cat_exp[CAT_EXP_BOX] = df_thin[CAT_EXP_BOX] + + series = pd.Series(thin_boxes, index=df_thin.index) + for proc in graph_param.array_formval: + orig_sql_label_serial = gen_sql_label(SERIAL_DATA, proc.proc_id) + time_col_alias = '{}_{}'.format(Cycle.time.key, proc.proc_id) + + for col_id, col_name in zip(proc.col_ids, proc.col_names): + col_id_name = gen_sql_label(col_id, col_name) + cols_in_df = [col for col in df_thin.columns if col.startswith(col_id_name)] + target_col_info = dic_str_cols.get(col_id_name) + for sql_label in cols_in_df: + sql_label_min = gen_sql_label(ARRAY_Y_MIN, sql_label) + sql_label_max = gen_sql_label(ARRAY_Y_MAX, sql_label) + sql_label_cycle = gen_sql_label(CYCLE_IDS, sql_label) + sql_label_serial = gen_sql_label(SERIAL_DATA, sql_label) + sql_label_time = gen_sql_label(TIMES, sql_label) + idxs = df_thin[sql_label].notnull() + + if not len(idxs) or not len(df_thin[idxs]): + df_cat_exp[sql_label] = thin_boxes + df_cat_exp[sql_label_min] = thin_boxes + df_cat_exp[sql_label_max] = thin_boxes + continue + + # before rank + if target_col_info: + rows = df_thin[sql_label] + df_cat_exp[sql_label] = rows + df_cat_exp[sql_label_min] = thin_boxes + df_cat_exp[sql_label_max] = thin_boxes + continue + + min_idxs, med_idxs, max_idxs = list(zip(*df_thin.loc[idxs, sql_label])) + min_idxs, med_idxs, max_idxs = list(min_idxs), list(med_idxs), list(max_idxs) + series[:] = None + series[idxs] = df.loc[med_idxs, sql_label].values + df_cat_exp[sql_label] = series + + # time start proc + if Cycle.time.key in df.columns: + series[:] = None + series[idxs] = df.loc[med_idxs, Cycle.time.key].values + df_cat_exp[Cycle.time.key] = np.where(series.isnull(), df_cat_exp[Cycle.time.key], series) + + # time end proc + if time_col_alias in df.columns: + series[:] = None + series[idxs] = df.loc[med_idxs, time_col_alias].values + df_cat_exp[sql_label_time] = series + + # cycle ids + if Cycle.id.key in df.columns: + series[:] = None + series[idxs] = df.loc[med_idxs, Cycle.id.key].values + df_cat_exp[sql_label_cycle] = series + + # serial ids + if orig_sql_label_serial in df.columns: + series[:] = None + series[idxs] = df.loc[med_idxs, orig_sql_label_serial].values + df_cat_exp[sql_label_serial] = series + + # add min value to median position + series[:] = None + series[idxs] = df.loc[min_idxs, sql_label].values + df_cat_exp[sql_label_min] = series + + # add max value to median position + series[:] = None + series[idxs] = df.loc[max_idxs, sql_label].values + df_cat_exp[sql_label_max] = series + + return df_cat_exp + + +@log_execution_time() +def gen_dic_data_from_df(df: DataFrame, graph_param: DicParam, cat_exp_mode=None, dic_cat_exp_labels=None, + calculate_cycle_time=True): + """ + :param df: + :param graph_param: + :param cat_exp_mode: + :param dic_cat_exp_labels: + :param calculate_cycle_time: + :return: + """ + dic_data = defaultdict(dict) + blank_vals = [None] * df.index.size + for proc in graph_param.array_formval: + # TODO: CfgProcessColumn call many times because outside loop + dic_datetime_cols = {cfg_col.id: cfg_col for cfg_col in + CfgProcessColumn.get_by_data_type(proc.proc_id, DataType.DATETIME)} + dic_data_cat_exp = defaultdict(list) + for col_id, col_name in zip(proc.col_ids, proc.col_names): + col_id_name = gen_sql_label(col_id, col_name) + sql_labels = [col for col in df.columns if col.startswith(col_id_name)] + series_lst = [] + for sql_label in sql_labels: + if sql_label in df.columns: + if calculate_cycle_time and col_id in dic_datetime_cols: + series = pd.to_datetime(df[sql_label]) + series.sort_values(inplace=True) + series = series.diff().dt.total_seconds() + series.sort_index(inplace=True) + df[sql_label] = series + else: + series = df[sql_label] + + series = series.replace({np.nan: None}).tolist() + else: + series = blank_vals + + series_lst.append(series) + if dic_cat_exp_labels: + sql_label_vals = dic_cat_exp_labels.get(sql_label) + if sql_label_vals: + dic_data_cat_exp[col_id].append(sql_label_vals[2]) + + if series_lst: + dic_data[proc.proc_id][col_id] = series_lst if cat_exp_mode else series_lst[0] + else: + dic_data[proc.proc_id][col_id] = [] + + if len(dic_data_cat_exp): + dic_data[proc.proc_id][CAT_EXP_BOX] = dic_data_cat_exp + + time_col_alias = '{}_{}'.format(Cycle.time.key, proc.proc_id) + if time_col_alias in df: + dic_data[proc.proc_id][Cycle.time.key] = df[time_col_alias].replace({np.nan: None}).tolist() + else: + dic_data[proc.proc_id][Cycle.time.key] = [] + + # if CAT_EXP_BOX in df.columns: + # dic_data[CAT_EXP_BOX] = df[CAT_EXP_BOX].tolist() + + return dic_data + + +@log_execution_time() +def gen_dic_data_cat_exp_from_df(df: DataFrame, graph_param: DicParam, dic_cfg_cat_exps, max_graph=None): + is_graph_limited = False + dic_data = defaultdict(dict) + if not len(df): + return dic_data + + cat_exp_cols = gen_cat_exp_names(graph_param.common.cat_exp) + for cat_exp_col, cat_exp_label in zip(graph_param.common.cat_exp, cat_exp_cols): + if cat_exp_label not in df.columns: + cfg_cat_exp = dic_cfg_cat_exps[cat_exp_col] + sql_label = gen_sql_label(cfg_cat_exp.id, cfg_cat_exp.column_name) + df[cat_exp_label] = df[sql_label] + + df_group = df.groupby(cat_exp_cols, dropna=False) + dic_df_group = {key: df_sub for key, df_sub in df_group} + + blank_vals = [None] * len(df) + series = pd.Series(blank_vals, index=df.index) + graph_count = 0 + for proc in graph_param.array_formval: + if max_graph and graph_count >= max_graph: + is_graph_limited = True + break + + dic_datetime_cols = {cfg_col.id: cfg_col for cfg_col in + CfgProcessColumn.get_by_data_type(proc.proc_id, DataType.DATETIME)} + dic_none_idxs = defaultdict(list) + dic_cat_exp_names = defaultdict(list) + time_col_alias = '{}_{}'.format(Cycle.time.key, proc.proc_id) + if time_col_alias in df: + dic_data[proc.proc_id][Cycle.time.key] = df[time_col_alias].replace({np.nan: None}).tolist() + else: + dic_data[proc.proc_id][Cycle.time.key] = [] + + for col_id, col_name in zip(proc.col_ids, proc.col_names): + if max_graph and graph_count >= max_graph: + is_graph_limited = True + break + + sql_label = gen_sql_label(col_id, col_name) + if sql_label not in df.columns: + dic_data[proc.proc_id][col_id] = blank_vals + dic_none_idxs[col_id].append(list(range(len(df)))) + dic_cat_exp_names[col_id].append(NA_STR) + continue + + plots = [] + # for cat_exp_val, idxs in df_group.groups.items(): + for cat_exp_val, df_sub in dic_df_group.items(): + if graph_count >= 20: + break + + idxs = df_sub.index + if not len(idxs): + continue + + series[:] = None + temp_series: Series = df_sub[sql_label] + if col_id in dic_datetime_cols: + temp_series = pd.to_datetime(temp_series) + temp_series.sort_values(inplace=True) + temp_series = temp_series.diff().dt.total_seconds() + temp_series.sort_index(inplace=True) + + nan_idxs = temp_series.isnull() + nan_series = temp_series[nan_idxs] + if len(temp_series) == len(nan_series): + continue + + series[idxs] = temp_series.tolist() + if len(nan_series): + series[nan_series.index] = None + + plots.append(series.tolist()) + dic_none_idxs[col_id].append(nan_series.index.tolist()) + dic_cat_exp_names[col_id].append(NA_STR if cat_exp_val is None or pd.isna(cat_exp_val) else cat_exp_val) + + graph_count += 1 + + if plots: + dic_data[proc.proc_id][col_id] = plots + dic_data[proc.proc_id][CAT_EXP_BOX] = dic_cat_exp_names + dic_data[proc.proc_id][NONE_IDXS] = dic_none_idxs + + return dic_data, is_graph_limited + + +@log_execution_time() +def gen_dic_serial_data_from_df(df: DataFrame, dic_proc_cfgs, dic_param): + dic_param[SERIAL_DATA] = dict() + dic_param[COMMON_INFO] = dict() + for proc_id, proc_cfg in dic_proc_cfgs.items(): + serial_cols = proc_cfg.get_serials(column_name_only=False) + datetime_col = proc_cfg.get_date_col(column_name_only=False) + if datetime_col: + datetime_col = datetime_col.name + sql_labels = [gen_sql_label(serial_col.id, serial_col.column_name) for serial_col in serial_cols] + before_rank_sql_labels = [gen_sql_label(RANK_COL, sql_label) for sql_label in sql_labels] + serial_cols = [serial_col.name for serial_col in serial_cols] + dic_param[COMMON_INFO][proc_id] = { + DATETIME_COL: datetime_col or '', + SERIAL_COLUMNS: serial_cols, + } + cols = [] + for sql_label, before_rank_label in zip(sql_labels, before_rank_sql_labels): + if before_rank_label in df.columns: + cols.append(before_rank_label) + else: + cols.append(sql_label) + + is_not_exist = set(cols) - set(list(df.columns)) + if not is_not_exist and cols: + dic_param[SERIAL_DATA][proc_id] = df[cols].replace({np.nan: ''}).to_records(index=False).tolist() + else: + dic_param[SERIAL_DATA][proc_id] = [] + + +@log_execution_time() +def gen_dic_serial_data_from_df_thin(df: DataFrame, dic_param, dic_datetime_serial_cols, dic_ranks): + dic_param[COMMON_INFO] = {} + + for plot in dic_param[ARRAY_PLOTDATA]: + col_id = plot[END_COL_ID] + if col_id in dic_ranks: + continue + + proc_id = plot[END_PROC_ID] + col_name = plot[END_COL_NAME] + cat_exp = plot.get(CAT_EXP_BOX) + datetime_col, serial_cols = dic_datetime_serial_cols.get(proc_id, (None, None)) + if datetime_col: + dic_param[COMMON_INFO][proc_id] = { + DATETIME_COL: datetime_col.name, + SERIAL_COLUMNS: [serial_col.name for serial_col in serial_cols], + } + + sql_label = gen_sql_label(col_id, col_name, cat_exp) + sql_label = gen_sql_label(SERIAL_DATA, sql_label) + if sql_label in df.columns: + plot[SERIAL_DATA] = df[sql_label].tolist() + else: + plot[SERIAL_DATA] = [] + + +@log_execution_time() +def get_start_proc_data_with_relate_id(proc_id, start_tm, end_tm, with_limit=None): + """ + inner join with relate table + :param proc_id: + :param start_tm: + :param end_tm: + :param with_limit: + :return: + """ + # start proc subquery + cycle_cls = find_cycle_class(proc_id) + data = db.session.query(cycle_cls.id, GlobalRelation.relate_id.label(Cycle.global_id.key), cycle_cls.time, + cycle_cls.is_outlier) + data = data.filter(cycle_cls.process_id == proc_id) + data = data.filter(cycle_cls.time >= start_tm) + data = data.filter(cycle_cls.time < end_tm) + + # join global relation + data = data.join(GlobalRelation, GlobalRelation.global_id == cycle_cls.global_id) + + if with_limit: + data = data.limit(with_limit) + + data = data.all() + + return data + + +@log_execution_time() +def get_start_proc_data(proc_id, start_tm, end_tm, with_limit=None, with_time_order=None): + """ + get start proc only (with out relation) + :param proc_id: + :param start_tm: + :param end_tm: + :param with_limit: + :param with_time_order: + :return: + """ + cycle_cls = find_cycle_class(proc_id) + cycle = db.session.query(cycle_cls.id, cycle_cls.global_id, cycle_cls.time, cycle_cls.is_outlier) + cycle = cycle.filter(cycle_cls.process_id == proc_id) + cycle = cycle.filter(cycle_cls.time >= start_tm) + cycle = cycle.filter(cycle_cls.time < end_tm) + + if with_time_order: + cycle = cycle.order_by(cycle_cls.time) + + if with_limit: + cycle = cycle.limit(with_limit) + + cycle = cycle.all() + + return cycle + + +def get_sensor_values_chunk(data_query, chunk_sensor, dic_sensors, cycle_cls, start_relate_ids=None, start_tm=None, + end_tm=None, with_limit=None): + for col_id, col_name in chunk_sensor: + if col_name not in dic_sensors: + continue + sensor = dic_sensors[col_name] + sensor_val_cls = find_sensor_class(sensor.id, DataType(sensor.type), auto_alias=True) + sensor_val = sensor_val_cls.coef(col_id) + + data_query = data_query.outerjoin( + sensor_val_cls, + and_(sensor_val_cls.cycle_id == cycle_cls.id, sensor_val_cls.sensor_id == sensor.id) + ) + + data_query = data_query.add_columns(sensor_val) + + # chunk + if start_relate_ids: + records = [] + for ids in start_relate_ids: + temp = data_query.filter(cycle_cls.global_id.in_(ids)) + records += temp.all() + id_key = Cycle.global_id.key + else: + data_query = data_query.filter(cycle_cls.time >= start_tm) + data_query = data_query.filter(cycle_cls.time < end_tm) + if with_limit: + data_query = data_query.limit(with_limit) + + records = data_query.all() + id_key = Cycle.id.key + + if records: + return pd.DataFrame(records) + else: + params = {gen_sql_label(col_id, col_name) for col_id, col_name in chunk_sensor} + params.update({ + id_key: [], + Cycle.time.key: [], + }) + df_chunk = pd.DataFrame({gen_sql_label(col_id, col_name): [] for col_id, col_name in chunk_sensor}) + return df_chunk + + +@log_execution_time() +def get_sensor_values(proc: EndProc, start_relate_ids=None, start_tm=None, end_tm=None, use_global_id=True, + with_limit=None): + """gen inner join sql for all column in 1 proc + + Arguments: + proc_id {[string]} -- [process id] + cols {[list]} -- [column name list] + """ + dic_sensors = gen_dic_sensors(proc.proc_id, proc.col_names) + + cycle_cls = find_cycle_class(proc.proc_id) + if use_global_id: + data = db.session.query(cycle_cls.global_id, cycle_cls.time) + else: + data = db.session.query(cycle_cls.id, cycle_cls.time) + + data = data.filter(cycle_cls.process_id == proc.proc_id) + dataframes = [] + all_sensors = list(zip(proc.col_ids, proc.col_names)) + for idx, chunk_sensor in enumerate(chunks(all_sensors, 50)): + df_chunk = get_sensor_values_chunk(data, chunk_sensor, dic_sensors, cycle_cls, start_relate_ids, start_tm, + end_tm, with_limit=with_limit) + if idx != 0 and Cycle.time.key in df_chunk.columns: + df_chunk = df_chunk.drop(Cycle.time.key, axis=1) + dataframes.append(df_chunk) + + df = pd.DataFrame() + if dataframes: + df = pd.concat([dfc.set_index(dfc.columns[0]) for dfc in dataframes], ignore_index=False, axis=1).reset_index() + return df + + +@log_execution_time() +def get_cond_data(proc: ConditionProc, start_relate_ids=None, start_tm=None, end_tm=None, use_global_id=True, + with_limit=None): + """generate subquery for every condition procs + """ + # get sensor info ex: sensor id , data type (int,real,text) + filter_query = Sensor.query.filter(Sensor.process_id == proc.proc_id) + + # filter + cycle_cls = find_cycle_class(proc.proc_id) + if use_global_id: + data = db.session.query(cycle_cls.global_id) + else: + data = db.session.query(cycle_cls.id) + + data = data.filter(cycle_cls.process_id == proc.proc_id) + + # for filter_sensor in filter_sensors: + for col_name, filter_details in proc.dic_col_name_filters.items(): + sensor = filter_query.filter(Sensor.column_name == col_name).first() + sensor_val = find_sensor_class(sensor.id, DataType(sensor.type), auto_alias=True) + + ands = [] + for filter_detail in filter_details: + comp_ins = [] + comp_likes = [] + comp_regexps = [] + cfg_filter_detail: CfgFilterDetail + for cfg_filter_detail in filter_detail.cfg_filter_details: + val = cfg_filter_detail.filter_condition + if cfg_filter_detail.filter_function == FilterFunc.REGEX.name: + comp_regexps.append(val) + elif not cfg_filter_detail.filter_function \ + or cfg_filter_detail.filter_function == FilterFunc.MATCHES.name: + comp_ins.append(val) + else: + comp_likes.extend(gen_sql_like_value(val, FilterFunc[cfg_filter_detail.filter_function], + position=cfg_filter_detail.filter_from_pos)) + + ands.append( + or_( + sensor_val.value.in_(comp_ins), + *[sensor_val.value.op(SQL_REGEXP_FUNC)(val) for val in comp_regexps if val is not None], + *[sensor_val.value.like(val) for val in comp_likes if val is not None], + ) + ) + + data = data.join( + sensor_val, and_( + sensor_val.cycle_id == cycle_cls.id, + sensor_val.sensor_id == sensor.id, + *ands, + ) + ) + + # chunk + if start_relate_ids: + records = [] + for ids in start_relate_ids: + temp = data.filter(cycle_cls.global_id.in_(ids)) + records += temp.all() + else: + data = data.filter(cycle_cls.time >= start_tm) + data = data.filter(cycle_cls.time < end_tm) + if with_limit: + data = data.limit(with_limit) + + records = data.all() + + return records + + +def create_graph_config(cfgs: List[CfgVisualization] = []): + if not cfgs: + return [{ + THRESH_HIGH: None, + THRESH_LOW: None, + Y_MAX: None, + Y_MIN: None, + PRC_MAX: None, + PRC_MIN: None, + ACT_FROM: None, + ACT_TO: None, + 'type': None, + 'name': None, + }] + + list_cfgs = [] + for cfg in cfgs: + list_cfgs.append({ + THRESH_HIGH: cfg.ucl, + THRESH_LOW: cfg.lcl, + Y_MAX: cfg.ymax, + Y_MIN: cfg.ymin, + PRC_MAX: cfg.upcl, + PRC_MIN: cfg.lpcl, + ACT_FROM: cfg.act_from, + ACT_TO: cfg.act_to, + 'type': cfg.filter_column.name if cfg.filter_column else None, + 'name': cfg.filter_detail.name if cfg.filter_detail else None, + 'eng_name': cfg.filter_column.english_name if cfg.filter_column else None, + }) + return list_cfgs + + +def get_default_graph_config(col_id, start_tm, end_tm): + # get sensor default cfg chart info + sensor_default_cfg: List[CfgVisualization] = CfgVisualization.get_sensor_default_chart_info(col_id, start_tm, + end_tm) or [] + return create_graph_config(sensor_default_cfg) + + +def get_col_graph_configs(col_id, filter_detail_ids, start_tm, end_tm): + if not filter_detail_ids: + return get_default_graph_config(col_id, start_tm, end_tm) + + graph_configs = CfgVisualization.get_by_control_n_filter_detail_ids(col_id, filter_detail_ids, start_tm, end_tm) + if graph_configs: + return create_graph_config(graph_configs) + + return get_default_graph_config(col_id, start_tm, end_tm) + + +def convert_chart_info_time_range(chart_config, start_proc_times, end_proc_times, query_start_tm, query_end_tm): + last_idx = len(end_proc_times) - 1 + act_from = chart_config.get(ACT_FROM) + act_to = chart_config.get(ACT_TO) + if act_from: + act_from = convert_time(act_from) + if act_to: + act_to = convert_time(act_to) + converted_act_from = None + converted_act_to = None + if act_from and act_to: + found_act_from = False + found_act_to = False + for idx, end_proc_time in enumerate(end_proc_times): + back_idx = last_idx - idx + if not found_act_from: + if act_from <= end_proc_time <= act_to: + found_act_from = True + # if idx == 0: # if it's first point -> converted act_from = -inf + # converted_act_from = None # -inf + # else: + # converted_act_from = start_proc_times[idx] + converted_act_from = start_proc_times[idx] + if idx == 0: + converted_act_from = query_start_tm + if not found_act_to: + back_time = end_proc_times[back_idx] + if act_from <= back_time <= act_to: + found_act_to = True + # if back_idx == last_idx: + # converted_act_to = None # if it's last point -> converted act_to = +inf + # else: + # converted_act_to = start_proc_times[back_idx] + converted_act_to = start_proc_times[back_idx] + if back_idx == last_idx: + converted_act_to = query_end_tm + if found_act_from and found_act_to: + break + else: + if act_from: + for idx, end_proc_time in enumerate(end_proc_times): + if act_from <= end_proc_time: + converted_act_from = start_proc_times[idx] + if idx == 0: + converted_act_from = query_start_tm + break + if act_to: + for idx in range(len(end_proc_times)): + back_idx = last_idx - idx + if end_proc_times[back_idx] <= act_to: + converted_act_to = start_proc_times[back_idx] + if back_idx == last_idx: + converted_act_to = query_end_tm + break + + return converted_act_from, converted_act_to + + +@log_execution_time() +def get_chart_infos_by_stp_var(graph_param: DicParam): + graph_configs = {} + var_col_id = graph_param.get_cate_var_col_id() + start_tm = start_of_minute(graph_param.common.start_date, graph_param.common.start_time) + end_tm = end_of_minute(graph_param.common.end_date, graph_param.common.end_time) + start_tm = convert_time(start_tm) + end_tm = convert_time(end_tm) + get_end_cols = graph_param.get_end_cols(graph_param.get_start_proc()) + + # query by var_col_id + for end_col in get_end_cols: + graph_configs[end_col] = {} + chart_infos: List[CfgVisualization] \ + = CfgVisualization.get_all_by_control_n_filter_col_id(end_col, var_col_id, start_tm, end_tm) + for chart_info in chart_infos: + filter_detail_id = chart_info.filter_detail_id + if not graph_configs[end_col].get(filter_detail_id): + graph_configs[end_col][filter_detail_id] = [] + graph_configs[end_col][filter_detail_id].append(chart_info) + + return graph_configs + + +@log_execution_time() +def build_regex_index(var_col_id): + cfg_filter: CfgFilter = CfgFilter.get_filter_by_col_id(var_col_id) + cfg_filter_details = [] + if cfg_filter: + cfg_filter_details = cfg_filter.filter_details or [] + + return { + cfg.id: gen_python_regex(cfg.filter_condition, FilterFunc[cfg.filter_function], cfg.filter_from_pos) + for cfg in cfg_filter_details + } + + +@log_execution_time() +def map_stp_val_2_cfg_details(stp_value, map_filter_detail_2_regex={}): + mapped_cfg_detail_ids = [] + for cfg_id, regex in map_filter_detail_2_regex.items(): + if regex and re.match(regex, str(stp_value)): + mapped_cfg_detail_ids.append(cfg_id) + return mapped_cfg_detail_ids + + +@log_execution_time() +def get_chart_infos_by_stp_value(stp_value, end_col, dic_filter_detail_2_regex, chart_infos_by_stp_var): + mapped_cfg_detail_ids = map_stp_val_2_cfg_details(stp_value, dic_filter_detail_2_regex) or [] + chart_infos_for_stp_value = [] + sensor_chart_infos = chart_infos_by_stp_var.get(end_col) or {} + for cfg_detail_id in mapped_cfg_detail_ids: + chart_infos_for_stp_value.extend(sensor_chart_infos.get(cfg_detail_id) or []) + + # None means default chart info of category var + # In cfg_visualization table, filter_detail_id = null means default of control column/filter column + if not chart_infos_for_stp_value: + chart_infos_for_stp_value.extend(sensor_chart_infos.get(None) or []) + return create_graph_config(chart_infos_for_stp_value) + + +@log_execution_time() +def get_chart_infos(graph_param: DicParam, dic_data=None, start_proc_times=None, no_convert=False): + graph_configs = {} + original_graph_configs = {} + start_proc = graph_param.common.start_proc + threshold_filter_detail_ids = graph_param.common.threshold_boxes + for proc in graph_param.array_formval: + graph_configs[proc.proc_id] = {} + original_graph_configs[proc.proc_id] = {} + + start_tm = start_of_minute(graph_param.common.start_date, graph_param.common.start_time) + end_tm = end_of_minute(graph_param.common.end_date, graph_param.common.end_time) + end_proc = proc.proc_id + end_proc_times = dic_data[proc.proc_id].get(Cycle.time.key) if dic_data else [] + for col_id in proc.col_ids: + orig_graph_cfg, graph_cfg = get_chart_info_detail(end_proc_times, col_id, threshold_filter_detail_ids, + end_proc, start_proc, start_tm, end_tm, start_proc_times, + no_convert=no_convert) + original_graph_configs[proc.proc_id][col_id] = orig_graph_cfg + graph_configs[proc.proc_id][col_id] = graph_cfg + + return graph_configs, original_graph_configs + + +@log_execution_time() +def get_chart_info_detail(end_proc_times, end_col, threshold_filter_detail_ids, end_proc=None, start_proc=None, + start_tm=None, end_tm=None, start_proc_times=None, no_convert=False): + start_tm = convert_time(start_tm) + end_tm = convert_time(end_tm) + query_start_tm = start_tm + query_end_tm = end_tm + if end_proc_times: + end_proc_times = pd.Series(end_proc_times, dtype='string') + end_proc_times = end_proc_times[end_proc_times.notna()] + if len(end_proc_times): + start_tm = end_proc_times.min() + end_tm = end_proc_times.max() + + end_proc_times = end_proc_times.to_list() + + # get chart thresholds for each sensor + col_graph_configs = get_col_graph_configs(end_col, threshold_filter_detail_ids, start_tm, end_tm) + orig_graph_cfgs = deepcopy(col_graph_configs) + + if end_proc_times and start_proc and end_proc and start_proc != end_proc and not no_convert and start_proc_times: + # convert thresholds + for chart_config in col_graph_configs: + act_from, act_to = convert_chart_info_time_range(chart_config, start_proc_times, end_proc_times, + query_start_tm, query_end_tm) + chart_config[ACT_FROM] = act_from + chart_config[ACT_TO] = act_to + + return col_graph_configs, orig_graph_cfgs + + +@log_execution_time() +def gen_dic_sensors(proc_id, cols=None): + """gen dictionary of sensors + {column_name: T_sensor instance} + + Arguments: + proc_id {string} -- process id + """ + + sensors = Sensor.query.filter(Sensor.process_id == proc_id) + if cols: + sensors = sensors.filter(Sensor.column_name.in_(cols)) + + return {sensor.column_name: sensor for sensor in sensors} + + +@log_execution_time() +def order_end_proc_sensor(orig_graph_param: DicParam, reorder): + dic_orders = {} + for proc in orig_graph_param.array_formval: + proc_id = proc.proc_id + orders = CfgConstant.get_value_by_type_name(type=CfgConstantType.TS_CARD_ORDER.name, name=proc_id) or '{}' + orders = json.loads(orders) + if orders: + dic_orders[proc_id] = orders + + lst_proc_end_col = [] + for proc in orig_graph_param.array_formval: + proc_id = proc.proc_id + for col_id, col_name in zip(proc.col_ids, proc.col_names): + proc_order = dic_orders.get(proc_id) or {} + order = proc_order.get(str(col_id)) or 999 + lst_proc_end_col.append((proc_id, col_id, col_name, order)) + + if not reorder: + return lst_proc_end_col + + return sorted(lst_proc_end_col, key=lambda x: x[-1]) + + +@log_execution_time() +def gen_plotdata(orig_graph_param: DicParam, dic_data, chart_infos=None, original_graph_configs=None, reorder=True): + # re-order proc-sensors to show to UI + lst_proc_end_col = order_end_proc_sensor(orig_graph_param, reorder) + + plotdatas = [] + array_formval = [] + for proc_id, col_id, col_name, _ in lst_proc_end_col: + array_y = dic_data.get(proc_id, {}).get(col_id, []) + array_x = dic_data.get(proc_id, {}).get(Cycle.time.key, []) + plotdata = {ARRAY_Y: array_y, ARRAY_X: array_x, END_PROC_ID: proc_id, END_COL_ID: col_id, + END_COL_NAME: col_name} + + plotdatas.append(plotdata) + + array_formval.append({ + END_PROC: proc_id, + GET02_VALS_SELECT: col_id + }) + + # add chart info + if chart_infos: + set_chart_infos_to_plotdata(col_id, chart_infos, original_graph_configs, plotdata) + + return array_formval, plotdatas + + +@log_execution_time() +def gen_plotdata_fpp(orig_graph_param: DicParam, dic_data, chart_infos=None, original_graph_configs=None, + dic_cycle_ids=None, reorder=True): + # re-order proc-sensors to show to UI + lst_proc_end_col = order_end_proc_sensor(orig_graph_param, reorder) + + plotdatas = [] + array_formval = [] + dic_proc_name = gen_dict_procs([proc_id for proc_id, *_ in lst_proc_end_col]) + for proc_id, col_id, col_name, _ in lst_proc_end_col: + if proc_id not in dic_data or col_id not in dic_data.get(proc_id): + continue + + y_list = dic_data.get(proc_id, {}).get(col_id) or [[]] + array_x = dic_data.get(proc_id, {}).get(Cycle.time.key, []) + ranks = dic_data[proc_id].get(RANK_COL, {}).get(col_id) + if not isinstance(y_list, (list, tuple)): + y_list = [y_list] + + cate_names = dic_data.get(proc_id, {}).get(CAT_EXP_BOX, {}).get(col_id) + none_idxs = dic_data.get(proc_id, {}).get(NONE_IDXS, {}).get(col_id) + for idx, array_y in enumerate(y_list): + if orig_graph_param.common.cat_exp and not array_y: + continue + + plotdata = {ARRAY_Y: array_y, ARRAY_X: array_x, END_PROC_ID: proc_id, + END_PROC_NAME: dic_proc_name[proc_id].name, + END_COL_ID: col_id, + END_COL_NAME: col_name} + + if cate_names: + plotdata.update({CAT_EXP_BOX: cate_names[idx]}) + + if none_idxs: + plotdata.update({NONE_IDXS: none_idxs[idx]}) + + if dic_cycle_ids: + plotdata.update({CYCLE_IDS: dic_cycle_ids.get(proc_id, {}).get(col_id, [])}) + + if ranks: + plotdata.update({RANK_COL: ranks[idx]}) + + plotdatas.append(plotdata) + + array_formval.append({ + END_PROC: proc_id, + GET02_VALS_SELECT: col_id + }) + + # add chart info + if chart_infos: + set_chart_infos_to_plotdata(col_id, chart_infos, original_graph_configs, plotdata) + + return array_formval, plotdatas + + +def set_chart_infos_to_plotdata(col_id, chart_infos, original_graph_configs, plotdata): + """ + set chart config + :param col_id: + :param chart_infos: + :param original_graph_configs: + :param plotdata: + :return: + """ + if chart_infos is None: + chart_infos = {} + + if original_graph_configs is None: + original_graph_configs = {} + + chart_info = [] + original_graph_config = [] + for proc_id, dic_col in chart_infos.items(): + if col_id in dic_col: + chart_info = dic_col[col_id] + original_graph_config = original_graph_configs[proc_id][col_id] + break + + plotdata[CHART_INFOS] = chart_info + plotdata[CHART_INFOS_ORG] = original_graph_config + + +@log_execution_time() +def gen_category_data(dic_proc_cfgs: Dict[int, CfgProcess], graph_param: DicParam, dic_data, + dic_org_cates=None): + plotdatas = [] + cate_procs: List[CategoryProc] = graph_param.common.cate_procs + if graph_param.common.cat_exp: + dic_cates = dic_data.get(CATEGORY_DATA) or dic_data + else: + dic_cates = dic_data + + for proc in cate_procs: + proc_id = proc.proc_id + dic_proc = dic_cates.get(proc_id) + if dic_proc is None: + continue + + proc_cfg = dic_proc_cfgs[proc_id] + + for col_id, column_name, col_show_name in zip(proc.col_ids, proc.col_names, proc.col_show_names): + data = dic_proc.get(col_id) + if not data: + continue + + if isinstance(data[0], (list, tuple)): + array_y = data[0] + else: + array_y = data + + cate_summary = None + if dic_org_cates: + cate_summary = dic_org_cates[proc_id].get(col_id) if dic_org_cates.get(proc_id) else None + + plotdata = dict(proc_name=proc_id, proc_master_name=proc_cfg.name, column_name=column_name, + column_master_name=col_show_name, data=array_y, summary=cate_summary, column_id=col_id) + plotdatas.append(plotdata) + + return plotdatas + + +@log_execution_time() +def clear_all_keyword(dic_param): + """ clear [All] keyword in selectbox + + Arguments: + dic_param {json} -- [params from client] + """ + dic_common = dic_param[COMMON] + cate_procs = dic_common.get(CATE_PROCS, []) + dic_formval = dic_param[ARRAY_FORMVAL] + for idx in range(len(dic_formval)): + select_vals = dic_formval[idx][GET02_VALS_SELECT] + if isinstance(select_vals, (list, tuple)): + dic_formval[idx][GET02_VALS_SELECT] = [val for val in select_vals if val not in [SELECT_ALL, NO_FILTER]] + else: + dic_formval[idx][GET02_VALS_SELECT] = [select_vals] + + for idx in range(len(cate_procs)): + select_vals = cate_procs[idx][GET02_CATE_SELECT] + if isinstance(select_vals, (list, tuple)): + cate_procs[idx][GET02_CATE_SELECT] = [val for val in select_vals if val not in [SELECT_ALL, NO_FILTER]] + else: + cate_procs[idx][GET02_CATE_SELECT] = [select_vals] + + # Need NO_FILTER keyword to decide filter or not , so we can not remove NO_FILTER keyword here. + for cond in dic_common[COND_PROCS]: + for key, value in cond.items(): + if isinstance(value, (list, tuple)): + vals = value + else: + vals = [value] + + if NO_FILTER in vals: + continue + + cond[key] = [val for val in vals if not val == SELECT_ALL] + + +@log_execution_time() +def update_outlier_flg(proc_id, cycle_ids, is_outlier): + """update outlier to t_cycle table + + Arguments: + cycle_ids {[type]} -- [description] + is_outlier {[type]} -- [description] + + Returns: + [type] -- [description] + """ + + # get global_ids linked to target cycles + cycle_cls = find_cycle_class(proc_id) + cycle_recs = cycle_cls.get_cycles_by_ids(cycle_ids) + if not cycle_recs: + return True + + global_ids = [] + for rec in cycle_recs: + if rec.global_id: + global_ids.append(rec.global_id) + else: + rec.is_outlier = is_outlier + + target_global_ids = GlobalRelation.get_all_relations_by_globals(global_ids, set_done_globals=set()) + + # update outlier for linked global ids + # TODO: fix front end + cycle_cls.update_outlier_by_global_ids(list(target_global_ids), is_outlier) + + db.session.commit() + return True + + +@log_execution_time() +def get_serials(trace, proc_name): + return [s.split()[0] for s in trace.hist2_yaml.get_serial_col(proc_name) if s] + + +@log_execution_time() +def get_date_col(trace, proc_name): + date_col = trace.hist2_yaml.get_date_col(proc_name) + date_col = date_col.split()[0] + return date_col + + +def gen_new_dic_param(dic_param, dic_non_sensor, start_proc_first=False): + pass + + +def get_non_sensor_cols(dic_proc_cfgs: Dict[int, CfgProcess], graph_param: DicParam): + """get non sensor headers + + Arguments: + trace {[type]} -- [description] + dic_param {[type]} -- [description] + + Returns: + [type] -- [description] + """ + dic_header = {} + + for proc in graph_param.array_formval: + proc_id = proc.proc_id + proc_cfg = dic_proc_cfgs[proc_id] + serials = proc_cfg.get_serials() + date_col = proc_cfg.get_date_col() + cols = serials + [date_col] + dic_header[proc_id] = cols + + # start proc + proc_id = graph_param.common.start_proc + if not dic_header.get(proc_id): + proc_cfg = dic_proc_cfgs[proc_id] + serials = proc_cfg.get_serials() + date_col = proc_cfg.get_date_col() + cols = serials + [date_col] + dic_header[proc_id] = cols + + return dic_header + + +def get_cate_var(graph_param: DicParam): + cate_procs = graph_param.common.cate_procs + if cate_procs: + return {ele[CATE_PROC]: ele[GET02_CATE_SELECT] for ele in cate_procs if + ele.get(CATE_PROC) and ele.get(GET02_CATE_SELECT)} + + return None + + +def gen_relate_ids(row): + """ + gen start proc relate ids + """ + + relate_ids = [] + if row.global_id: + relate_ids.append(row.global_id) + if row.relate_id: + relate_ids.append(row.relate_id) + + return relate_ids + + +@log_execution_time() +@request_timeout_handling() +def make_irregular_data_none(dic_param): + use_list = [YType.NORMAL.value, YType.OUTLIER.value, YType.NEG_OUTLIER.value] + none_list = [float('inf'), float('-inf')] + array_plotdata = dic_param.get(ARRAY_PLOTDATA) + for num, plotdata in enumerate(array_plotdata): + array_y = plotdata.get(ARRAY_Y) or [] + array_y_type = plotdata.get(ARRAY_Y_TYPE) or [] + + if array_y_type: # use y_type to check for irregular data + # TODO : save data as {(from,to): value} is better + # array_plotdata[num][ARRAY_Y] = [None if array_y_type[idx] not in {YType.NORMAL.value, YType.OUTLIER.value, + # YType.NEG_OUTLIER.value} else e for idx, e in enumerate(array_y)] + df = pd.DataFrame({ARRAY_Y: array_y, ARRAY_Y_TYPE: array_y_type}) + df[ARRAY_Y] = np.where(df[ARRAY_Y_TYPE].isin(use_list), df[ARRAY_Y], None) + else: # or use value to check for irregular data directly + # array_plotdata[num][ARRAY_Y] = [None if e == float('inf') or e == float('-inf') else e for e in array_y] + df = pd.DataFrame({ARRAY_Y: array_y}) + df[ARRAY_Y] = np.where(df[ARRAY_Y].isin(none_list), None, df[ARRAY_Y]) + + array_plotdata[num][ARRAY_Y] = df[ARRAY_Y].to_list() + + return dic_param + + +def get_min_max_of_all_chart_infos(chart_infos): + vals = [chart.get(Y_MIN) for chart in chart_infos if chart.get(Y_MIN) is not None] + vals += [chart.get(Y_MAX) for chart in chart_infos if chart.get(Y_MAX) is not None] + y_min = None + y_max = None + if vals: + y_min = min(vals) + y_max = max(vals) + + return y_min, y_max + + +def get_threshold_min_max_chartinfo(chart_infos): + vals = [chart.get(THRESH_LOW) for chart in chart_infos if chart.get(THRESH_LOW) is not None] + vals += [chart.get(THRESH_HIGH) for chart in chart_infos if chart.get(THRESH_HIGH) is not None] + + y_min = None + y_max = None + if vals: + y_min = min(vals) + y_max = max(vals) + + return y_min, y_max + + +def calc_upper_lower_range(array_y: Series): + arr = array_y[array_y.notnull()] + arr = arr[~arr.isin([float('inf'), float('-inf')])] + # arr = [e for e in arr if e not in {None, float('inf'), float('-inf')} and not pd.isna(e)] + if not len(arr): + return None, None + + q1, q3 = quantile(arr, [0.25, 0.75], interpolation='midpoint') + iqr = q3 - q1 + if iqr: + lower_range = q1 - 2.5 * iqr + upper_range = q3 + 2.5 * iqr + else: + lower_range = 0.9 * min(arr) + upper_range = 1.1 * max(arr) + if lower_range == upper_range: + lower_range -= 1 + upper_range += 1 + + return float(lower_range), float(upper_range) + + +def save_proc_sensor_order_to_db(orders): + try: + for proc_code, new_orders in orders.items(): + CfgConstant.create_or_merge_by_type(const_type=CfgConstantType.TS_CARD_ORDER.name, + const_name=proc_code, + const_value=new_orders) + except Exception as ex: + traceback.print_exc() + logger.error(ex) + + +def get_proc_ids_in_dic_param(graph_param: DicParam): + """ + get process + :param graph_param: + :return: + """ + procs = set() + procs.add(graph_param.common.start_proc) + for proc in graph_param.common.cond_procs: + procs.add(proc.proc_id) + + for proc in graph_param.common.cate_procs: + procs.add(proc.proc_id) + + for proc in graph_param.array_formval: + procs.add(proc.proc_id) + + return list(procs) + + +def get_procs_in_dic_param(graph_param: DicParam): + """ + get process + :param graph_param: + :return: + """ + proc_ids = get_proc_ids_in_dic_param(graph_param) + dic_procs = gen_dict_procs(proc_ids) + return dic_procs + + +def gen_dict_procs(proc_ids): + return {proc.id: proc for proc in CfgProcess.get_procs(proc_ids)} + + +def get_end_procs_in_dic_param(graph_param: DicParam): + """ + get process + :param graph_param: + :return: + """ + procs = set() + for proc in graph_param.array_formval: + procs.add(proc.proc_id) + + return {proc.id: proc for proc in CfgProcess.get_procs(procs)} + + +def gen_blank_df(): + data = {Cycle.time.key: [], Cycle.is_outlier.key: []} + return pd.DataFrame(data) + + +def fx(v): return pd.NA + + +@log_execution_time() +def apply_coef_text(df: DataFrame, graph_param: DicParam, dic_proc_cfgs: dict): + for proc_id, proc_cfg in dic_proc_cfgs.items(): + if graph_param.is_end_proc(proc_id): + end_col_ids = graph_param.get_end_cols(proc_id) or [] + end_cols: List[CfgProcessColumn] = proc_cfg.get_cols(end_col_ids) or [] + for end_col in end_cols: + if DataType[end_col.data_type] is DataType.TEXT \ + and end_col.coef is not None and end_col.operator == Operator.REGEX.value: + col_label = gen_sql_label(end_col.id, end_col.column_name) + if col_label in df.columns: + df[col_label] = df[col_label].astype('object').str \ + .replace('^(?!{})'.format(end_col.coef), fx, regex=True) + return df + + +def get_filter_detail_ids(proc_ids, column_ids): + """ + get filter detail ids to check if this filter matching dataset of graph + :param proc_ids: + :param column_ids: + :return: + """ + not_exact_matches = [] + dic_col_filter_details = defaultdict(list) + cfg_filters = CfgFilter.get_by_proc_n_col_ids(proc_ids, column_ids) + for cfg_filter in cfg_filters: + cfg_filter: CfgFilter + cfg_column: CfgProcessColumn = cfg_filter.column + df_col_name = gen_sql_label(cfg_column.id, cfg_column.column_name) + for cfg_detail in cfg_filter.filter_details: + if cfg_detail.filter_function == FilterFunc.MATCHES.name: + dic_col_filter_details[df_col_name].append((cfg_detail.id, cfg_detail.filter_condition)) + else: + not_exact_matches.append(cfg_detail.id) + + return dic_col_filter_details, not_exact_matches + + +@log_execution_time() +def gen_dic_uniq_value_from_df(df, col_names): + dic_col_values = {} + for col in col_names: + if col in df.columns: + vals = set(df[col]) + vals = [str(val) for val in vals] + dic_col_values[col] = set(vals) + + return dic_col_values + + +def check_filter_detail_match_graph_data(dic_col_filter_details, dic_col_values): + matched_filter_ids = [] + unmatched_filter_ids = [] + for col_name, filter_details in dic_col_filter_details.items(): + vals = dic_col_values.get(col_name, []) + for filter_detail_id, filter_condition in filter_details: + if filter_condition in vals: + matched_filter_ids.append(filter_detail_id) + else: + unmatched_filter_ids.append(filter_detail_id) + + return matched_filter_ids, unmatched_filter_ids + + +@log_execution_time() +def main_check_filter_detail_match_graph_data(graph_param: DicParam, df: DataFrame): + cond_proc_ids = [cond.proc_id for cond in graph_param.common.cond_procs] + cond_col_ids = graph_param.get_all_end_col_ids() + dic_col_filter_details, not_exact_match_filter_ids = get_filter_detail_ids(cond_proc_ids, cond_col_ids) + dic_col_values = gen_dic_uniq_value_from_df(df, dic_col_filter_details) + matched_filter_ids, unmatched_filter_ids = check_filter_detail_match_graph_data(dic_col_filter_details, + dic_col_values) + + return matched_filter_ids, unmatched_filter_ids, not_exact_match_filter_ids + + +def reduce_data(df_orig: DataFrame, graph_param, dic_str_cols): + """ + make data for thin mode + :param df_orig: + :param graph_param: + :param dic_str_cols: + :return: + """ + + # end cols + dic_end_col_names = {} + rank_cols = [] + for proc in graph_param.array_formval: + for col_id, col_name in zip(proc.col_ids, proc.col_names): + sql_label = gen_sql_label(col_id, col_name) + cols_in_df = [col for col in df_orig.columns if col.startswith(sql_label)] + target_col_info = dic_str_cols.get(sql_label) + if target_col_info: + rank_cols += cols_in_df + else: + for col_in_df in cols_in_df: + dic_end_col_names[col_in_df] = (proc.proc_id, col_id, col_name) + + # category + dic_cate_names = {} + cat_exp_col = graph_param.common.cat_exp + for proc in graph_param.common.cate_procs: + for col_id, col_name in zip(proc.col_ids, proc.col_names): + if cat_exp_col: + sql_label = gen_sql_label(CATEGORY_DATA, col_id, col_name) + else: + sql_label = gen_sql_label(col_id, col_name) + + if sql_label in df_orig.columns: + dic_cate_names[sql_label] = (proc.proc_id, col_id, col_name) + + all_cols = list( + set([Cycle.time.key] + list(dic_end_col_names) + list(dic_cate_names) + rank_cols)) + group_col = '__group_col__' + index_col = '__index_col__' + all_cols = [col for col in all_cols if col in df_orig.columns] + df = df_orig[all_cols] + x_option = graph_param.common.x_option or 'TIME' + if x_option.upper() == 'TIME': + df[group_col] = pd.to_datetime(df[Cycle.time.key]).values.astype(float) + min_epoc_time = df[group_col].min() + max_epoc_time = df[group_col].max() + count_per_group = calc_data_per_group(min_epoc_time, max_epoc_time) + df[group_col] = (df[group_col] - min_epoc_time) // count_per_group + df[group_col] = df[group_col].astype(int) + else: + count_per_group = ceil(len(df) / THIN_DATA_CHUNK) + df[group_col] = df.index // count_per_group + + # count element in one group + group_counts = df[group_col].value_counts().tolist() + + df[index_col] = df.index + df.set_index(group_col, inplace=True) + + # get category mode(most common) + df_blank = pd.DataFrame(index=range(THIN_DATA_CHUNK)) + dfs = [df_blank] + str_cols = list(set(list(dic_cate_names) + rank_cols)) + df_cates = None + if str_cols: + df_temp = df[str_cols] + df_temp = df_temp.groupby(group_col).agg(get_mode) + + for col in str_cols: + if col not in df_temp.columns: + df_temp[col] = None + + df_cates = pd.concat([df_blank, df_temp], axis=1) + if rank_cols: + dfs.append(df_cates[rank_cols]) + + cols = [] + for sql_label, (proc_id, *_) in dic_end_col_names.items(): + df_temp = df[[index_col, sql_label]].dropna() + df_temp = df_temp.sort_values([group_col, sql_label], ascending=[True, True]) + df_temp.drop(sql_label, axis=1, inplace=True) + df_temp = df_temp.groupby(group_col).agg(get_min_median_max_pos) + df_temp = df_temp.rename(columns={index_col: sql_label}) + if len(df_temp) == 0: + blank_vals = [None] * THIN_DATA_CHUNK + df_temp[sql_label] = blank_vals + + dfs.append(df_temp) + cols.append(sql_label) + + df_box = pd.concat(dfs, axis=1) + + # add time + # start_tm = start_of_minute(graph_param.common.start_date, graph_param.common.start_time) + # end_tm = end_of_minute(graph_param.common.end_date, graph_param.common.end_time) + # times = pd.date_range(start=start_tm, end=end_tm, periods=THIN_DATA_CHUNK) + # df_box[Cycle.time.key] = times + # df_box[Cycle.time.key] = df_box[Cycle.time.key].astype('datetime64[s]') + + # remove blanks + df_box.dropna(how="all", subset=cols + rank_cols, inplace=True) + + # remove blank category + dic_cates = defaultdict(dict) + dic_org_cates = defaultdict(dict) + for sql_label, (proc_id, col_id, _) in dic_cate_names.items(): + if df_cates is not None and sql_label in df_cates: + dic_cates[proc_id][col_id] = df_cates.loc[df_box.index, sql_label].tolist() + dic_org_cates[proc_id][col_id] = get_available_ratio(df[sql_label]) + + return df_box, dic_cates, dic_org_cates, group_counts + + +def get_min_median_max_pos(df): + # last = len(df) - 1 + last = df.size - 1 + mid = last // 2 + try: + return df.iloc[[0, mid, last]].to_list() + except Exception as e: + raise e + + +def calc_data_per_group(min_val, max_val, box=THIN_DATA_CHUNK): + dif_val = max_val - min_val + 1 + ele_per_box = dif_val / box + return ele_per_box + + +def reduce_stepped_chart_data(array_y): + rows = [None] * len(array_y) + + idx = 0 + for key, vals in groupby(array_y): + rows[idx] = key + idx += len(list(vals)) + + return rows + + +@log_execution_time() +def calc_raw_common_scale_y(plots, string_col_ids=None): + """ + calculate y min max in common scale + :param plots: + :param string_col_ids: + :return: + """ + y_commons = [] + min_max_list = [] + for plot in plots: + s = pd.Series(plot[ARRAY_Y]) + + s = s[s.notnull()] + if not len(s): + min_max_list.append((None, None)) + continue + + s = s.convert_dtypes() + s_without_inf = s[np.isfinite(s)] + # s_without_inf = remove_inf(s) + + min_val = s_without_inf.min() + max_val = s_without_inf.max() + if pd.isna(min_val): + min_val = None + if pd.isna(max_val): + max_val = None + + min_max_list.append((min_val, max_val)) + + if string_col_ids and plot[END_COL_ID] in string_col_ids: + continue + + if min_val: + y_commons.append(min_val) + + if max_val: + y_commons.append(max_val) + + all_graph_min = None + all_graph_max = None + if y_commons: + all_graph_min = min(y_commons) + all_graph_max = max(y_commons) + + return min_max_list, all_graph_min, all_graph_max + + +def calc_stp_raw_common_scale_y(dic_param): + """ + calculate y min max in common scale + :param dic_param: + :return: + """ + y_commons = [] + min_max_list = [] + for k, plots in dic_param[ARRAY_PLOTDATA].items(): + for plot in plots: + s = pd.Series(plot[ARRAY_Y]) + + s = s[s.notnull()] + if not len(s): + min_max_list.append((None, None)) + continue + + s_without_inf = s[np.isfinite(s)] + min_val = s_without_inf.min() + max_val = s_without_inf.max() + min_max_list.append((min_val, max_val)) + + # if plot[END_COL_ID] in dic_param[STRING_COL_IDS]: + # continue + + if min_val: + y_commons.append(min_val) + + if max_val: + y_commons.append(max_val) + + all_graph_min = None + all_graph_max = None + if y_commons: + all_graph_min = min(y_commons) + all_graph_max = max(y_commons) + + return min_max_list, all_graph_min, all_graph_max + + +def detect_abnormal_data(series_x, series_y, none_idxs=None): + nones = none_idxs + if none_idxs is None: + nones = series_y[series_y.isnull()].index.tolist() + + return {UNLINKED_IDXS: series_x[series_x.isnull()].index.tolist(), + NONE_IDXS: nones, + INF_IDXS: series_y[series_y == float('inf')].index.tolist(), + NEG_INF_IDXS: series_y[series_y == float('-inf')].index.tolist()} + + +def calc_auto_scale_y(plotdata, series_y): + notna_series_y = series_y[series_y.notna()] + if not len(notna_series_y): + return {Y_MIN: 0, Y_MAX: 1, LOWER_OUTLIER_IDXS: [], UPPER_OUTLIER_IDXS: []} + + summaries = plotdata.get(SUMMARIES) or [] + lower_range = None + upper_range = None + for summary in summaries: + dic_non_param = summary.get('non_parametric') + if dic_non_param: + lower_range = dic_non_param.get('lower_range_org') + upper_range = dic_non_param.get('upper_range_org') + + if lower_range is None: + p25, p75 = np.percentile(notna_series_y, [25, 75]) + iqr = p75 - p25 + lower_range = p25 - 2.5 * iqr + upper_range = p75 + 2.5 * iqr + + lower_outlier_idxs = series_y[series_y < lower_range].index.tolist() if lower_range is not None else [] + upper_outlier_idxs = series_y[series_y > upper_range].index.tolist() if upper_range is not None else [] + + if lower_range and series_y.min() >= 0: + lower_range = max(0, lower_range) + + if upper_range and series_y.max() <= 0: + upper_range = min(0, upper_range) + + lower_range, upper_range = extend_min_max(lower_range, upper_range) + + return {Y_MIN: lower_range, Y_MAX: upper_range, + LOWER_OUTLIER_IDXS: lower_outlier_idxs, + UPPER_OUTLIER_IDXS: upper_outlier_idxs} + + +def calc_setting_scale_y(plotdata, series_y): + # calculate upper/lower limit + chart_infos = plotdata.get(CHART_INFOS) + dic_scale_auto = plotdata.get(SCALE_AUTO, {}) + if not chart_infos: + if dic_scale_auto: + return dic_scale_auto + + return {Y_MIN: series_y.min(), Y_MAX: series_y.max()} + + ymin, ymax = get_min_max_of_all_chart_infos(chart_infos) + if ymin is None and ymax is None: + if dic_scale_auto: + return dic_scale_auto + + return {Y_MIN: series_y.min(), Y_MAX: series_y.max()} + + if ymin is None: + ymin = dic_scale_auto.get(Y_MIN) + lower_outlier_idxs = dic_scale_auto.get(LOWER_OUTLIER_IDXS) + else: + lower_outlier_idxs = series_y[series_y < ymin].index.tolist() + + if ymax is None: + ymax = dic_scale_auto.get(Y_MAX) + upper_outlier_idxs = dic_scale_auto.get(UPPER_OUTLIER_IDXS) + else: + upper_outlier_idxs = series_y[series_y > ymax].index.tolist() + + ymin, ymax = extend_min_max(ymin, ymax) + + return {Y_MIN: ymin, Y_MAX: ymax, LOWER_OUTLIER_IDXS: lower_outlier_idxs, UPPER_OUTLIER_IDXS: upper_outlier_idxs} + + +def calc_threshold_scale_y(plotdata, series_y): + # calculate upper/lower limit + chart_infos = plotdata.get(CHART_INFOS) + dic_scale_auto = plotdata.get(SCALE_AUTO, {}) + if not chart_infos: + return dic_scale_auto + + thresh_low, thresh_high = get_threshold_min_max_chartinfo(chart_infos) + if thresh_low is None and thresh_high is None: + return dic_scale_auto + + if thresh_low is None: + thresh_low = dic_scale_auto.get(THRESH_LOW) + lower_outlier_idxs = dic_scale_auto.get(LOWER_OUTLIER_IDXS) + else: + lower_outlier_idxs = series_y[series_y < thresh_low].index.tolist() + + if thresh_high is None: + thresh_high = dic_scale_auto.get(THRESH_HIGH) + upper_outlier_idxs = dic_scale_auto.get(UPPER_OUTLIER_IDXS) + else: + upper_outlier_idxs = series_y[series_y > thresh_high].index.tolist() + + thresh_low, thresh_high = extend_min_max(thresh_low, thresh_high) + + return {Y_MIN: thresh_low, Y_MAX: thresh_high, + LOWER_OUTLIER_IDXS: lower_outlier_idxs, + UPPER_OUTLIER_IDXS: upper_outlier_idxs} + + +@log_execution_time() +def calc_scale_info(array_plotdata, min_max_list, all_graph_min, all_graph_max, string_col_ids=None, has_val_idxs=None): + dic_datetime_cols = {} + for idx, plotdata in enumerate(array_plotdata): + # datetime column + proc_id = plotdata.get(END_PROC_ID) + col_id = plotdata.get(END_COL_ID) + if proc_id and proc_id not in dic_datetime_cols: + dic_datetime_cols[proc_id] = {cfg_col.id: cfg_col for cfg_col in + CfgProcessColumn.get_by_data_type(proc_id, DataType.DATETIME)} + + is_datetime_col = True if col_id in dic_datetime_cols.get(proc_id, {}) else False + + y_min = min_max_list[idx][0] + y_min = all_graph_min if y_min is None else y_min + y_max = min_max_list[idx][1] + y_max = all_graph_max if y_max is None else y_max + + y_min, y_max = extend_min_max(y_min, y_max) + all_graph_min, all_graph_max = extend_min_max(all_graph_min, all_graph_max) + + array_y = plotdata.get(ARRAY_Y) + array_x = plotdata.get(ARRAY_X) + if (not len(array_y)) or (not len(array_x)) or (string_col_ids and plotdata[END_COL_ID] in string_col_ids): + dic_base_scale = {Y_MIN: y_min, Y_MAX: y_max, LOWER_OUTLIER_IDXS: [], UPPER_OUTLIER_IDXS: []} + plotdata[SCALE_AUTO] = dic_base_scale + plotdata[SCALE_SETTING] = dic_base_scale + plotdata[SCALE_THRESHOLD] = dic_base_scale + plotdata[SCALE_COMMON] = dic_base_scale + plotdata[SCALE_FULL] = dic_base_scale + continue + + series_x = pd.Series(array_x) + series_y = pd.Series(array_y) + + # don't do with all blank idxs + if has_val_idxs is not None: + series_x = series_x.loc[has_val_idxs] + series_y = series_y.loc[has_val_idxs] + + none_idxs = plotdata.get(NONE_IDXS) + dic_abnormal_data = detect_abnormal_data(series_x, series_y, none_idxs) + plotdata.update(dic_abnormal_data) + for _idxs in dic_abnormal_data.values(): + if _idxs: + # array_y[_idxs] = None + for _idx in _idxs: + array_y[_idx] = None + + series_y = pd.Series(array_y) + if has_val_idxs is not None: + series_y = series_y.loc[has_val_idxs] + + plotdata[SCALE_AUTO] = calc_auto_scale_y(plotdata, series_y) + if is_datetime_col: + plotdata[SCALE_AUTO][Y_MIN] = y_min + + plotdata[SCALE_SETTING] = calc_setting_scale_y(plotdata, series_y) + plotdata[SCALE_THRESHOLD] = calc_threshold_scale_y(plotdata, series_y) + plotdata[SCALE_COMMON] = {Y_MIN: all_graph_min, Y_MAX: all_graph_max, LOWER_OUTLIER_IDXS: [], + UPPER_OUTLIER_IDXS: []} + plotdata[SCALE_FULL] = {Y_MIN: y_min, Y_MAX: y_max, LOWER_OUTLIER_IDXS: [], UPPER_OUTLIER_IDXS: []} + if is_datetime_col: + plotdata[SCALE_FULL][Y_MIN] = 0 + + return True + + +@log_execution_time() +@request_timeout_handling() +def gen_kde_data_trace_data(dic_param, full_arrays=None): + array_plotdata = dic_param.get(ARRAY_PLOTDATA) + for num, plotdata in enumerate(array_plotdata): + full_array_y = full_arrays[num] if full_arrays else None + kde_list = calculate_kde_trace_data(plotdata, full_array_y=full_array_y) + plotdata[SCALE_SETTING][KDE_DATA], plotdata[SCALE_COMMON][KDE_DATA], plotdata[SCALE_THRESHOLD][KDE_DATA], \ + plotdata[SCALE_AUTO][KDE_DATA], plotdata[SCALE_FULL][KDE_DATA] = kde_list + + return dic_param + + +def extend_min_max(y_min, y_max): + if y_max is None: + y_max = y_min * 1.2 if y_min is not None else 1 + + if y_min is None: + y_min = y_max * 0.8 + + if y_min == y_max: + y_min *= 0.8 + y_max *= 1.2 + + if y_min == 0 and y_max == 0: + y_min = -1 + y_max = 1 + + return y_min, y_max + + +def copy_dic_param_to_thin_dic_param(dic_param, dic_thin_param): + ignore_keys = [COMMON, ARRAY_FORMVAL, ARRAY_PLOTDATA, CYCLE_IDS, SERIAL_DATA] + for key, val in dic_param.items(): + if key in ignore_keys: + continue + + dic_thin_param[key] = dic_param[key] + + return True + + +def gen_thin_df_cat_exp(dic_param): + df = pd.DataFrame() + dic_end_cols = {} + + # df['index'] = list(range(len(dic_param[TIMES]))) + df[Cycle.id.key] = dic_param[CYCLE_IDS] + df[Cycle.time.key] = dic_param[TIMES] + + for plot in dic_param[ARRAY_PLOTDATA] or []: + time_sql_label = f'time_{plot[END_PROC_ID]}' + if time_sql_label not in df.columns: + df[time_sql_label] = plot[ARRAY_X] + + sql_label = gen_sql_label(plot[END_COL_ID], plot[END_COL_NAME], plot.get(CAT_EXP_BOX)) + dic_end_cols[sql_label] = (plot[END_COL_ID], plot[END_COL_NAME], plot.get(CAT_EXP_BOX)) + df[sql_label] = plot[ARRAY_Y] + + # serials + add_serials_to_thin_df(dic_param, df) + + # categories + add_categories_to_thin_df(dic_param, df) + + return df, dic_end_cols + + +def get_available_ratio(series: Series): + n_total = series.size + # na_counts = series.isnull().sum().sum() + non_na_counts = len(series.dropna()) + na_counts = n_total - non_na_counts + na_percentage = signify_digit(100 * na_counts / n_total) if n_total else 0 + non_na_percentage = signify_digit(100 - na_percentage) + return dict(nTotal=n_total, nonNACounts=non_na_counts, nonNAPercentage=non_na_percentage) + + +@log_execution_time() +def add_serials_to_thin_df(dic_param, df): + for plot in dic_param[ARRAY_PLOTDATA] or []: + proc_id = plot[END_PROC_ID] + sql_label = gen_sql_label(SERIAL_DATA, proc_id) + if sql_label in df.columns: + continue + + serials = dic_param.get(SERIAL_DATA, {}).get(proc_id) + if serials is not None and len(serials): + df[sql_label] = serials + + +@log_execution_time() +def add_categories_to_thin_df(dic_param, df): + for dic_cate in dic_param.get(CATEGORY_DATA) or []: + col_id = dic_cate.get('column_id') + col_name = dic_cate.get('column_name') + data = dic_cate.get('data') + sql_label = gen_sql_label(CATEGORY_DATA, col_id, col_name) + if sql_label in df.columns: + continue + + df[sql_label] = data + + +@log_execution_time() +def get_serials_and_date_col(graph_param: DicParam, dic_proc_cfgs): + dic_output = {} + for proc in graph_param.array_formval: + proc_cfg = dic_proc_cfgs[proc.proc_id] + serial_cols = proc_cfg.get_serials(column_name_only=False) + datetime_col = proc_cfg.get_date_col(column_name_only=False) + dic_output[proc.proc_id] = (datetime_col, serial_cols) + + return dic_output + + +@log_execution_time() +def gen_cat_exp_names(cat_exps): + if cat_exps: + return [gen_sql_label(CAT_EXP_BOX, level) for level, _ in enumerate(cat_exps, 1)] + + return None + + +@log_execution_time() +def gen_unique_data(df, dic_proc_cfgs, col_ids, ): + if not col_ids: + return {} + + dic_unique_cate = {} + dic_cols = {col.id: col for col in CfgProcessColumn.get_by_ids(col_ids)} + for col_id in col_ids: + cfg_col = dic_cols.get(col_id) + col_name = cfg_col.column_name + master_name = cfg_col.name + proc_id = cfg_col.process_id + + sql_label = gen_sql_label(RANK_COL, col_id, col_name) + if sql_label not in df.columns: + sql_label = gen_sql_label(col_id, col_name) + + unique_data = [] + if sql_label in df.columns: + # unique_data = df[sql_label].drop_duplicates().dropna().tolist() + s = df[sql_label].value_counts() + unique_data = s.index.tolist() + + cfg_proc_name = dic_proc_cfgs[proc_id].name + unique_data = {'proc_name': proc_id, 'proc_master_name': cfg_proc_name, 'column_name': col_name, + 'column_master_name': master_name, 'column_id': col_id, + UNIQUE_CATEGORIES: unique_data} + + dic_unique_cate[col_id] = unique_data + + return dic_unique_cate + + +@log_execution_time() +def filter_df(df, dic_filter): + if not dic_filter: + return df + + dic_names = {col.id: col for col in CfgProcessColumn.get_by_ids(dic_filter)} + for col_id, vals in dic_filter.items(): + if not vals: + continue + + if not isinstance(vals, (list, tuple)): + vals = [vals] + + if NO_FILTER in vals: + continue + + vals = [val for val in vals if val not in [SELECT_ALL, NO_FILTER]] + if not vals: + continue + + cfg_col = dic_names.get(col_id, None) + if cfg_col is None: + continue + + sql_label = gen_sql_label(RANK_COL, col_id, cfg_col.column_name) + if sql_label not in df.columns: + sql_label = gen_sql_label(col_id, cfg_col.column_name) + + dtype_name = cfg_col.data_type + if dtype_name == DataType.INTEGER.name: + vals = [int(val) for val in vals] + elif dtype_name == DataType.REAL.name: + vals = [float(val) for val in vals] + elif dtype_name == DataType.TEXT.name: + vals = [str(val) for val in vals] + df[sql_label] = df[sql_label].astype(str) + + df = df[df[sql_label].isin(vals)] + + return df + + +def gen_category_info(dic_param, dic_ranks): + for plot in dic_param[ARRAY_PLOTDATA]: + if plot[END_COL_ID] in dic_ranks: + # category variable + p_array_y = pd.Series(plot[ARRAY_Y]).dropna().tolist() + cat_size = 0 + if len(p_array_y): + cat_size = np.unique(p_array_y).size + plot[CAT_TOTAL] = cat_size + plot[IS_CAT_LIMITED] = True if cat_size >= MAX_CATEGORY_SHOW else False + return dic_param + + +@memoize() +def get_cfg_proc_col_info(col_ids): + dic_cols = {cfg_col.id: cfg_col for cfg_col in CfgProcessColumn.get_by_ids(col_ids)} + proc_ids = list(set(cfg_col.process_id for cfg_col in dic_cols.values())) + dic_procs = {cfg_proc.id: cfg_proc for cfg_proc in CfgProcess.get_procs(proc_ids)} + + return dic_procs, dic_cols diff --git a/histview2/categorical_plot/__init__.py b/histview2/categorical_plot/__init__.py new file mode 100644 index 0000000..8a22657 --- /dev/null +++ b/histview2/categorical_plot/__init__.py @@ -0,0 +1,4 @@ + +def create_module(app, **kwargs): + from histview2.categorical_plot.controllers import categorical_plot_blueprint + app.register_blueprint(categorical_plot_blueprint) diff --git a/histview2/categorical_plot/controllers.py b/histview2/categorical_plot/controllers.py new file mode 100644 index 0000000..f4244ac --- /dev/null +++ b/histview2/categorical_plot/controllers.py @@ -0,0 +1,27 @@ +import os + +from flask import Blueprint, render_template + +from histview2 import dic_yaml_config_file +from histview2.common.constants import * +from histview2.common.services.form_env import get_common_config_data + +categorical_plot_blueprint = Blueprint( + 'categorical_plot', + __name__, + template_folder=os.path.join('..', 'templates', 'categorical_plot'), + static_folder=os.path.join('..', 'static', 'categorical_plot'), + url_prefix='/histview2' +) + +# ローカルパラメータの設定 +local_params = { + "config_yaml_fname_proc": dic_yaml_config_file[YAML_CONFIG_PROC], + "config_yaml_fname_histview2": dic_yaml_config_file[YAML_CONFIG_HISTVIEW2], + "config_yaml_fname_db": dic_yaml_config_file[YAML_CONFIG_DB]} + + +@categorical_plot_blueprint.route('/stp') +def categorical_plot(): + output_dict = get_common_config_data() + return render_template("categorical_plot.html", **output_dict) diff --git a/histview2/categorical_plot/services/__init__.py b/histview2/categorical_plot/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/categorical_plot/services/utils.py b/histview2/categorical_plot/services/utils.py new file mode 100644 index 0000000..422901d --- /dev/null +++ b/histview2/categorical_plot/services/utils.py @@ -0,0 +1,30 @@ +from histview2.common.constants import * +from histview2.common.yaml_utils import YamlConfig, DBConfigYaml, ProcConfigYaml + + +def get_valid_procs(procs): + """ + Get valid process to show on selectbox 起点 + Arguments: + procs {dict} + + Returns: + dict -- valid process on 起点 + """ + proc_list = {} + filter_info = procs['filter_info'] + proc_master = procs['proc_master'] + + for key, value in filter_info.items(): + if len(filter_info[key]) > 0: + filter_time = False + for item in filter_info[key]: + if item.get('item_info', {}) \ + and item['item_info'].get('type') \ + and item['item_info']['type'] == 'datehour-range': + filter_time = True + if filter_time: + proc_list.update({key: proc_master[key]}) + + return proc_list + diff --git a/histview2/co_occurrence/__init__.py b/histview2/co_occurrence/__init__.py new file mode 100644 index 0000000..473b2c4 --- /dev/null +++ b/histview2/co_occurrence/__init__.py @@ -0,0 +1,4 @@ + +def create_module(app, **kwargs): + from .controllers import co_occurrence_blueprint + app.register_blueprint(co_occurrence_blueprint) diff --git a/histview2/co_occurrence/controllers.py b/histview2/co_occurrence/controllers.py new file mode 100644 index 0000000..e585994 --- /dev/null +++ b/histview2/co_occurrence/controllers.py @@ -0,0 +1,19 @@ +import os + +from flask import Blueprint, render_template + +from histview2.common.services.form_env import get_common_config_data + +co_occurrence_blueprint = Blueprint( + 'co_occurrence', + __name__, + template_folder=os.path.join('..', 'templates', 'co_occurrence'), + static_folder=os.path.join('..', 'static', 'co_occurrence'), + url_prefix='/histview2' +) + + +@co_occurrence_blueprint.route('/cog') +def index(): + output_dict = get_common_config_data(get_visualization_config=False) + return render_template("co_occurrence_csv.html", **output_dict) diff --git a/histview2/co_occurrence/services/__init__.py b/histview2/co_occurrence/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/co_occurrence/services/utils.py b/histview2/co_occurrence/services/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/common/__init__.py b/histview2/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/common/backup_db.py b/histview2/common/backup_db.py new file mode 100644 index 0000000..c620b0c --- /dev/null +++ b/histview2/common/backup_db.py @@ -0,0 +1,67 @@ +import os +from datetime import datetime + +from apscheduler.triggers.cron import CronTrigger + +from histview2 import log_execution, dic_config, SQLITE_CONFIG_DIR, APP_DB_FILE, UNIVERSAL_DB_FILE, make_dir +from histview2.common.common_utils import copy_file +from histview2.common.logger import log_execution_time +from histview2.common.scheduler import scheduler_app_context, JobType, add_job_to_scheduler +from histview2.setting_module.services.background_process import send_processing_info +from histview2.common.constants import AUTO_BACKUP +from histview2.common.yaml_utils import BasicConfigYaml + +BACKUP_TRANS_DATA_INTERVAL_DAY = 7 +CONFIG_BK_PATH = os.path.join(dic_config[SQLITE_CONFIG_DIR], 'backup') + + +@log_execution() +def backup_config_db(): + db_file_path = dic_config[APP_DB_FILE] + make_dir(CONFIG_BK_PATH) + copy_file(db_file_path, CONFIG_BK_PATH) + + +@log_execution() +def backup_universal_db(): + db_file_path = dic_config[UNIVERSAL_DB_FILE] + today = datetime.now() + created_date = datetime.fromtimestamp(os.path.getctime(db_file_path)) + if (today - created_date).days < BACKUP_TRANS_DATA_INTERVAL_DAY: + return + + make_dir(CONFIG_BK_PATH) + copy_file(dic_config[UNIVERSAL_DB_FILE], CONFIG_BK_PATH) + + +@log_execution() +def backup_dbs(): + basic_config_yaml = BasicConfigYaml() + auto_backup = BasicConfigYaml.get_node(basic_config_yaml.dic_config, ['info', AUTO_BACKUP], False) + yield 0 + backup_config_db() + if auto_backup: + yield 50 + backup_universal_db() + yield 100 + + +@scheduler_app_context +def backup_dbs_job(_job_id, _job_name, *args, **kwargs): + """ backup config database + + Keyword Arguments: + _job_id {[type]} -- [description] (default: {None}) + _job_name {[type]} -- [description] (default: {None}) + """ + gen = backup_dbs(*args, **kwargs) + send_processing_info(gen, JobType.BACKUP_DATABASE) + + +@log_execution_time() +def add_backup_dbs_job(is_run_now=None): + job_name = JobType.BACKUP_DATABASE.name + trigger = CronTrigger(hour=3, minute=0, second=0) + kwargs = dict(_job_id=job_name, _job_name=job_name) + add_job_to_scheduler(job_id=job_name, job_name=job_name, trigger=trigger, import_func=backup_dbs_job, + run_now=is_run_now, dic_import_param=kwargs) diff --git a/histview2/common/check_available_port.py b/histview2/common/check_available_port.py new file mode 100644 index 0000000..14e0bc1 --- /dev/null +++ b/histview2/common/check_available_port.py @@ -0,0 +1,29 @@ +import socket as s +import sys + +from loguru import logger + +from histview2.common.common_utils import parse_int_value +from histview2.common.logger import log_execution + + +@log_execution() +def check_available_port(port): + port = parse_int_value(port) + sock = s.socket(s.AF_INET, s.SOCK_STREAM) + sock.settimeout(1) + try: + result = sock.connect(('127.0.0.1', port)) + if not result: + logger.info("Port %d is not available right now, please check and run again." % (port)) + input("Please type any key to close application\n") + if input: + sys.exit() + except (s.timeout, s.gaierror) as ex: + logger.error("Checking port availability timeout!", ex) + # logger.exception(ex) + except Exception as ex: + logger.error("Checking port availability error!", ex) + # logger.exception(ex) + finally: + sock.close() diff --git a/histview2/common/clean_old_data.py b/histview2/common/clean_old_data.py new file mode 100644 index 0000000..a687d22 --- /dev/null +++ b/histview2/common/clean_old_data.py @@ -0,0 +1,185 @@ +import datetime as dt +import os +import shutil +import sys + +from apscheduler.triggers import interval +from pytz import utc + +from histview2 import check_exist +from histview2.common.logger import log_execution_time, log_execution +from histview2.common.scheduler import scheduler_app_context, JobType, scheduler +from histview2.script.hide_exe_root_folder import heartbeat_bundle_folder +from histview2.setting_module.services.background_process import send_processing_info + + +@scheduler_app_context +def clean_old_data_job(_job_id=None, _job_name=None, *args, **kwargs): + """ scheduler job to delete process from db + + Keyword Arguments: + _job_id {[type]} -- [description] (default: {None}) + _job_name {[type]} -- [description] (default: {None}) + """ + gen = clean_old_files(*args, **kwargs) + send_processing_info(gen, JobType.CLEAN_DATA, is_check_disk=False) + + +@log_execution_time() +def clean_old_files(folder=None, num_day_ago=30): + """ Delete old files in a folder + Arguments: + prefix {[type]} -- [file prefix] + postfix {[type]} -- [file postfix] + + Keyword Arguments: + db_id {[type]} -- [description] (default: {None}) + Yields: + [type] -- [description] + """ + percent = 0 + yield percent + + cleanup_unused_exe_folder() + + if not folder: + yield 100 + return + + files = get_files_of_last_n_days(folder, num_day_ago=num_day_ago, subdirectory=True) + percent_step = round(100 / (len(files) + 1)) + for file in files: + try: + os.remove(file) + except Exception: + pass + percent = percent + percent_step + yield percent + + yield 100 + + +@log_execution_time() +def get_files_of_last_n_days(directory, num_day_ago=30, subdirectory=False, extension=None): + """get file in folder + + Arguments: + directory {[type]} -- [description] + num_days {int} -- [description] (default: {1}) + subdirectory {int} -- [description] + extension {string} -- [use created_time or modified_time] (default: {False}) + + Keyword Arguments: + + Returns: + output_files [type] -- [files to be cleaned] + """ + now = dt.datetime.utcnow() + n_days_ago = now - dt.timedelta(days=num_day_ago) + + output_files = [] + if not directory: + return output_files + + root_folder = True + for root, dirs, files in os.walk(directory): + # limit depth of recursion + if subdirectory is False and root_folder is False: + break + + # list files + for file in files: + if extension and not file.endswith(extension): + continue + + abs_file_name = os.path.join(root, file) + st = os.stat(abs_file_name) + + time_of_file = dt.datetime.fromtimestamp(st.st_mtime) + if time_of_file <= n_days_ago: + output_files.append(abs_file_name) + + root_folder = False + + return output_files + + +@log_execution_time() +def get_folders_of_last_n_days(directory, num_day_ago=30, startswith=None): + """get file in folder + + Arguments: + directory {[type]} -- [description] + num_days {int} -- [description] (default: {1}) + startswith {string} -- [use created_time or modified_time] (default: {False}) + + Keyword Arguments: + + Returns: + output_files [type] -- [files to be cleaned] + """ + now = dt.datetime.utcnow() + n_days_ago = now - dt.timedelta(days=num_day_ago) + + output_folders = [] + if not directory: + return output_folders + + root_folder = True + for root, folders, files in os.walk(directory): + # list dirs + for folder in folders: + if startswith and not folder.startswith(startswith): + continue + + abs_folder_name = os.path.join(root, folder) + st = os.stat(abs_folder_name) + + time_of_file = dt.datetime.fromtimestamp(st.st_mtime) + if time_of_file <= n_days_ago: + output_folders.append(abs_folder_name) + + break + + return output_folders + + +@log_execution() +def cleanup_unused_exe_folder(): + if not getattr(sys, 'frozen', False): + return + + current_app_folder, current_file, file_ext = heartbeat_bundle_folder() + root_folder = os.path.dirname(current_app_folder) + folders = get_folders_of_last_n_days(root_folder, num_day_ago=2, startswith='_MEI') + files = get_files_of_last_n_days(root_folder, num_day_ago=2, extension=file_ext) + for folder_path in folders: + file_path = folder_path + file_ext + if file_path in files or not check_exist(file_path): + try: + os.remove(file_path) + shutil.rmtree(folder_path) + except Exception: + pass + + +@log_execution() +def run_clean_data_job(folder='.', num_day_ago=30, job_repeat_sec=86400, job_id=-1): + """ Trigger cleaning data job + :return: + """ + clean_job_id = f'{JobType.CLEAN_DATA.name}' + interval_trigger = interval.IntervalTrigger(seconds=job_repeat_sec, timezone=utc) + + scheduler.add_job( + clean_job_id, clean_old_data_job, + trigger=interval_trigger, + replace_existing=True, + next_run_time=dt.datetime.now().astimezone(utc), + kwargs=dict( + _job_id=clean_job_id, + _job_name=JobType.CLEAN_DATA.name, + folder=folder, + num_day_ago=num_day_ago, + ) + ) diff --git a/histview2/common/common_utils.py b/histview2/common/common_utils.py new file mode 100644 index 0000000..27a6fa7 --- /dev/null +++ b/histview2/common/common_utils.py @@ -0,0 +1,1055 @@ +import copy +import csv +import fnmatch +import os +import pickle +import re +import shutil +import socket +import sys +from collections import OrderedDict +from datetime import datetime, timedelta +from itertools import islice, chain, permutations + +import chardet +import numpy as np +import pandas as pd +import pyper +# from charset_normalizer import detect +from dateutil import parser +from dateutil.relativedelta import relativedelta +from flask import g +from pandas import DataFrame + +from histview2.common.constants import AbsPath, DataType, YAML_AUTO_INCREMENT_COL, CsvDelimiter, R_PORTABLE, \ + SQL_COL_PREFIX, FilterFunc, ENCODING_ASCII, ENCODING_UTF_8, FlaskGKey, LANGUAGES +from histview2.common.logger import logger, log_execution_time +from histview2.common.services.normalization import unicode_normalize_nfkc + +INCLUDES = ['*.csv', '*.tsv'] +DATE_FORMAT_STR = '%Y-%m-%dT%H:%M:%S.%fZ' +DATE_FORMAT_QUERY = '%Y-%m-%dT%H:%M:%S.%f' +DATE_FORMAT_STR_CSV = '%Y-%m-%d %H:%M:%S.%f' +DATE_FORMAT_STR_CSV_FOLDER = '%Y%m%d' +DATE_FORMAT_STR_FACTORY_DB = '%Y-%m-%d %H:%M:%S.%f' +DATE_FORMAT_STR_ONLY_DIGIT = '%Y%m%d%H%M%S.%f' +DATE_FORMAT = '%Y-%m-%d' +TIME_FORMAT = '%H:%M' +RL_DATETIME_FORMAT = '%Y-%m-%dT%H:%M' + + +def get_current_timestamp(format_str=DATE_FORMAT_STR): + return datetime.utcnow().strftime(format_str) + + +def parse_int_value(value): + """ + Parse integral value from text or numeric data + :param value: + :return: parsed integral value. + """ + if type(value) is str: + value = unicode_normalize_nfkc(value) + if value.isdigit(): + return int(value) + elif type(value) is int: + return value + + return None + + +def dict_deep_merge(source, destination): + """ + Deep merge two dictionary to one. + + >>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } } + >>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } } + >>> dict_deep_merge(b, a) == { 'first' : { 'all_rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } } + """ + if source: + for key, value in source.items(): + if isinstance(value, dict) and destination.get(key): + # get node or create one + node = destination.setdefault(key, {}) + dict_deep_merge(value, node) + else: + destination[key] = copy.deepcopy(value) + + return destination + + +def convert_json_to_ordered_dict(json): + """ + Deeply convert a normal dict to OrderedDict. + :param json: input json + :return: ordered json + """ + if isinstance(json, dict): + ordered_json = OrderedDict(json) + try: + for key, value in ordered_json.items(): + ordered_json[key] = convert_json_to_ordered_dict(value) + except AttributeError: + pass + return ordered_json + + return json + + +def get_columns_selected(histview_cfg, proc_cfg): + date_col = '' + result = serial_cols = alias_names = column_names = master_names = operators = coefs = [] + check_columns = proc_cfg.get('checked-columns', {}) + + # Get date-column + if histview_cfg.get('date-column'): + date_col = re.sub(r'(["*\/\'\s]+)', '', re.split(' as ', histview_cfg['date-column'])[0]) + # date_col = re.sub(r'(["*\/\'\s]+)', '', re.split(r'[-+*/]\d+', hvc['date-column'])[0]) + + # Get serial column + if histview_cfg.get('serial-column'): + serial_cols = list(map(lambda x: re.sub(r'(["*\/\'\s]+)', '', re.split(' as ', x)[0]), + histview_cfg['serial-column'])) + + # Get serial column + auto_increment_col = histview_cfg.get(YAML_AUTO_INCREMENT_COL) + + # Get params from checked-columns + if check_columns: + alias_names = check_columns.get('alias-names', []) or [] + column_names = check_columns.get('column-names', []) or [] + master_names = check_columns.get('master-names', []) or [] + operators = check_columns.get('operators', []) or [] + coefs = check_columns.get('coefs', []) or [] + data_types = check_columns.get('data-types', []) or [] + + # Merge params to result dict + for key, value in enumerate(column_names): + column_name = re.sub(r'(["*\/\'\s]+)', '', value) + alias = alias_names[key] + master_name = master_names[key] + operator = operators[key] + coef = coefs[key] + data_type = data_types[key] + + is_datetime = True if (value == date_col) else False + is_serial = True if (value in serial_cols) else False + is_auto_increment = value == auto_increment_col + + result.append({ + 'master_name': master_name, + 'column_name': column_name, + 'alias': alias, + 'operator': operator, + 'coef': coef, + 'is_datetime': is_datetime, + 'is_serial': is_serial, + 'is_auto_increment': is_auto_increment, + 'data_type': data_type + }) + return result + + +def _excludes(root, folders): + fd = folders[:] + ex = [] + try: + for folder in fd: + if datetime.strptime(folder, '%Y%m%d'): + ex.append(folder) + except Exception: + pass + ex.sort() + + if len(fd) > 0: + fd.remove(ex[-1]) + return map(lambda d: os.path.join(root, d), fd) + + +def _filter(paths, excludes): + matches = [] + for path in paths: + append = None + + for include in INCLUDES: + if os.path.isdir(path): + append = True + break + + if fnmatch.fnmatch(path, include): + append = True + break + + for exclude in excludes: + if os.path.isdir(path) and path == exclude: + append = False + break + + if fnmatch.fnmatch(path, exclude): + append = False + break + + if append: + matches.append(path) + + return matches + + +def get_latest_file(root_name): + try: + latest_files = get_files(root_name, depth_from=1, depth_to=100, extension=['csv', 'tsv']) + latest_files.sort() + return latest_files[-1].replace(os.sep, '/') + except Exception: + return '' + + +def get_sorted_files(root_name): + try: + latest_files = get_files(root_name, depth_from=1, depth_to=100, extension=['csv', 'tsv']) + latest_files = [file_path.replace(os.sep, '/') for file_path in latest_files] + latest_files.sort(reverse=True) + return latest_files + except Exception: + return [] + + +def start_of_minute(start_date, start_tm, delimeter='T'): + if start_date is None or start_tm is None: + return None + if start_tm and len(start_tm) == 5: + start_tm = start_tm + ':00' + + return '{}{}{}'.format(start_date.replace('/', '-'), delimeter, start_tm) + + +def end_of_minute(start_date, start_tm, delimeter='T'): + if start_date is None or start_tm is None: + return None + if start_tm and len(start_tm) == 5: + start_tm = start_tm + ':00' + + start_tm = start_tm[:8] + '.999999' + + return '{}{}{}'.format(start_date.replace('/', '-'), delimeter, start_tm) + + +def clear_special_char(target): + if not target: + return target + + if isinstance(target, (list, tuple)): + return [_clear_special_char(s) for s in target] + elif isinstance(target, str): + return _clear_special_char(target) + + +def _clear_special_char(target_str): + if not target_str: + return target_str + + output = target_str + for s in ('\"', '\'', '*'): + output = output.replace(s, '') + + return output + + +def universal_db_exists(): + universal_db = os.path.join(os.getcwd(), 'instance', 'universal.sqlite3') + # if getattr(sys, 'frozen', False): # Use for EXE file only + instance_folder = os.path.join(os.getcwd(), 'instance') + if not os.path.exists(instance_folder): + os.makedirs(instance_folder) + return os.path.exists(universal_db) + + +# convert time before save to database YYYY-mm-DDTHH:MM:SS.NNNNNNZ +def convert_time(time=None, format_str=DATE_FORMAT_STR, return_string=True, only_milisecond=False): + if not time: + time = datetime.utcnow() + elif isinstance(time, str): + time = parser.parse(time) + + if return_string: + time = time.strftime(format_str) + if only_milisecond: + time = time[:-3] + return time + + +def fast_convert_time(time, format_str=DATE_FORMAT_STR): + return parser.parse(time).strftime(format_str) + + +def add_miliseconds(time=None, milis=0): + """add miliseconds + + Keyword Arguments: + time {[type]} -- [description] (default: {datetime.now()}) + days {int} -- [description] (default: {0}) + + Returns: + [type] -- [description] + """ + if not time: + time = datetime.utcnow() + + return time + timedelta(milliseconds=milis) + + +def add_seconds(time=None, seconds=0): + """add seconds + + Keyword Arguments: + time {[type]} -- [description] (default: {datetime.now()}) + days {int} -- [description] (default: {0}) + + Returns: + [type] -- [description] + """ + if not time: + time = datetime.utcnow() + + return time + timedelta(seconds=seconds) + + +def add_days(time=datetime.utcnow(), days=0): + """add days + + Keyword Arguments: + time {[type]} -- [description] (default: {datetime.now()}) + days {int} -- [description] (default: {0}) + + Returns: + [type] -- [description] + """ + return time + timedelta(days) + + +def add_years(time=datetime.utcnow(), years=0): + """add days + + Keyword Arguments: + time {[type]} -- [description] (default: {datetime.now()}) + years {int} -- [description] (default: {0}) + + Returns: + [type] -- [description] + """ + return time + relativedelta(years=years) + + +def get_files(directory, depth_from=1, depth_to=2, extension=[''], file_name_only=False): + """get files in folder + + Arguments: + directory {[type]} -- [description] + + Keyword Arguments: + depth_limit {int} -- [description] (default: 2) + extension {list} -- [description] (default: ['']) + + Returns: + [type] -- [description] + """ + output_files = [] + if not directory: + return output_files + + if not check_exist(directory): + raise Exception('Folder not found!') + + root_depth = directory.count(os.path.sep) + for root, dirs, files in os.walk(directory): + # limit depth of recursion + current_depth = root.count(os.path.sep) + 1 + # assume that directory depth is 1, sub folders are 2, 3, ... + # default is to just read children sub folder, depth from 1 to 2 + if (current_depth < root_depth + depth_from) or (current_depth > root_depth + depth_to): + continue + + # list files with extension + for file in files: + # Check file is modified in [in_modified_days] days or not + if any([file.endswith(ext) for ext in extension]): + if file_name_only: + output_files.append(file) + else: + output_files.append(os.path.join(root, file)) + + return output_files + + +def add_double_quotes(instr: str): + """add double quotes to a string (column name) + + Arguments: + instr {str} -- [description] + + Returns: + [type] -- [description] + """ + if not instr: + return instr + + instr = instr.strip('\"') + + return f'"{instr}"' + + +def guess_data_types(instr: str): + """ guess data type of all kind of databases to 4 type (INTEGER,REAL,DATETIME,TEXT) + + Arguments: + instr {str} -- [description] + + Returns: + [type] -- [description] + """ + dates = ['date', 'time'] + ints = ['int', 'bit', r'num.*\([^,]+$', r'num.*\(.*,\ *0'] + reals = ['num', 'real', 'float', 'double', 'long', 'dec', 'money'] + + instr = instr.lower() + for data_type in dates: + if re.search(data_type, instr): + return DataType.DATETIME + + for data_type in ints: + if re.search(data_type, instr): + return DataType.INTEGER + + for data_type in reals: + if re.search(data_type, instr): + return DataType.REAL + return DataType.TEXT + + +def resource_path(*relative_path, level=None): + """ make absolute path + + Keyword Arguments: + level {int} -- [0: auto, 1: user can see folder, 2: user can not see folder(MEIPASS)] (default: {0}) + + Returns: + [type] -- [description] + """ + + show_path = os.getcwd() + hide_path = getattr(sys, '_MEIPASS', show_path) + + if level is AbsPath.SHOW: + basedir = show_path + elif level is AbsPath.HIDE: + basedir = hide_path + else: + if getattr(sys, 'frozen', False): + basedir = hide_path + else: + basedir = show_path + + return os.path.join(basedir, *relative_path) + + +class RUtils: + def __init__(self, package, *args, **kwargs): + # r instance + r_portable_env = os.environ.get('R-PORTABLE') + if r_portable_env: + self.r_exe = os.path.join(r_portable_env, 'bin', 'R.exe') + self.r_library = os.path.join(r_portable_env, 'library') + else: + self.r_exe = resource_path(R_PORTABLE, 'bin', 'R.exe', level=AbsPath.SHOW) + self.r_library = resource_path(R_PORTABLE, 'library', level=AbsPath.SHOW) + + # specify R-Portable execution + self.r = pyper.R(RCMD=self.r_exe, *args, **kwargs) + logger.info(self.r('Sys.getenv()')) + + # specify R-Portable library + self.r(f'.libPaths(c(""))') + self.r(f'.libPaths(c("{self.r_library}"))') + logger.info(self.r('.libPaths()')) + + # R package folder + self.source = resource_path('histview2', 'script', 'r_scripts', package) + self.r(f'source("{self.source}")') + + def __call__(self, func, *args, _number_of_recheck_r_output=1000, **kwargs) -> object: + """ call funtion with parameters + + Arguments: + func {[string]} -- R function name + + Keyword Arguments: + _number_of_recheck_r_output {int} -- [R function may take time to return output, Python must check many + time to get final output] (default: {100}) + + Returns: + [type] -- [R output] + """ + args_prefix = 'args__' + kwargs_prefix = 'kwargs__' + output_var = 'output__' + + r_args = [] + for i, val in enumerate(args): + para = f'{args_prefix}{i}' + self.r.assign(para, val) + r_args.append(para) + + r_kwargs = [] + for i, (key, val) in enumerate(kwargs.items()): + para = f'{kwargs_prefix}{i}' + self.r.assign(para, val) + r_kwargs.append(f'{key}={para}') + + final_args = ','.join(chain(r_args, r_kwargs)) + + self.r(f'{output_var} = {func}({final_args})') + + # wait for R return an output + output = None + while (not output) and _number_of_recheck_r_output: + output = self.r.get(output_var) + _number_of_recheck_r_output -= 1 + + print(_number_of_recheck_r_output, output) + return output + + +def get_file_size(f_name): + """get file size + + Arguments: + f_name {[type]} -- [description] + + Returns: + [type] -- [description] + """ + return os.path.getsize(f_name) + + +def write_csv_file(data, file_path, headers, delimiter='\t'): + """save csv, tsv file + + Arguments: + data {[type]} -- [description] + file_path {[type]} -- [description] + headers {[type]} -- [description] + + Keyword Arguments: + delimiter {str} -- [description] (default: {'\t'}) + """ + make_dir_from_file_path(file_path) + + with open(file_path, 'w', newline='') as f: + writer = csv.writer(f, delimiter=delimiter) + for row in chain([headers], data): + writer.writerow(row) + + +def create_file_path(prefix, suffix='.tsv', dt=None): + f_name = f'{prefix}_{convert_time(dt, format_str=DATE_FORMAT_STR_ONLY_DIGIT)}{suffix}' + file_path = resource_path(get_data_path(abs=False), f_name, level=AbsPath.SHOW) + + if not os.path.exists(os.path.dirname(file_path)): + os.makedirs(os.path.dirname(file_path)) + + return file_path + + +def copy_file(source, target): + """copy file + + Arguments: + source {[type]} -- [description] + target {[type]} -- [description] + """ + if not check_exist(source): + return False + + shutil.copy2(source, target) + return True + + +def path_split_all(path): + """split all part of a path + + Arguments: + path {[string]} -- [full path] + """ + allparts = [] + while True: + parts = os.path.split(path) + if parts[0] == path: # sentinel for absolute paths + allparts.insert(0, parts[0]) + break + elif parts[1] == path: # sentinel for relative paths + allparts.insert(0, parts[1]) + break + else: + path = parts[0] + allparts.insert(0, parts[1]) + + return allparts + + +def get_data_path(abs=True): + """get data folder path + + Returns: + [type] -- [description] + """ + folder_name = 'data' + return resource_path(folder_name, level=AbsPath.SHOW) if abs else folder_name + + +# TODO : delete +def get_import_error_path(abs=True): + """get import folder path + + Returns: + [type] -- [description] + """ + folder_name = 'error' + return resource_path(folder_name, level=AbsPath.SHOW) if abs else folder_name + + +def get_error_trace_path(abs=True): + """get import folder path + + Returns: + [type] -- [description] + """ + folder_name = ['error', 'trace'] + return resource_path(*folder_name, level=AbsPath.SHOW) if abs else folder_name + + +def get_error_import_path(abs=True): + """get import folder path + + Returns: + [type] -- [description] + """ + folder_name = ['error', 'import'] + return resource_path(*folder_name, level=AbsPath.SHOW) if abs else folder_name + + +def get_about_md_file(): + """ + get about markdown file path + """ + folder_name = 'about' + file_name = 'Endroll.md' + return resource_path(folder_name, file_name, level=AbsPath.SHOW) + + +def get_terms_of_use_md_file(current_locale): + """ + get about markdown file path + """ + folder_name = 'about' + if current_locale.language == 'ja': + file_name = 'terms_of_use_jp.md' + else: + file_name = 'terms_of_use_en.md' + return resource_path(folder_name, file_name, level=AbsPath.SHOW) + + +def get_wrapr_path(): + """get wrap r folder path + + Returns: + [type] -- [description] + """ + folder_names = ['histview2', 'script', 'r_scripts', 'wrapr'] + return resource_path(*folder_names, level=AbsPath.HIDE) + + +def get_temp_path(): + """get temporaty folder path + + Returns: + [type] -- [description] + """ + folder_name = 'temp' + data_folder = get_data_path() + return resource_path(data_folder, folder_name, level=AbsPath.SHOW) + + +def get_cache_path(): + """get cache folder path + + Returns: + [type] -- [description] + """ + folder_name = 'cache' + data_folder = get_data_path() + return resource_path(data_folder, folder_name, level=AbsPath.SHOW) + + +def get_export_path(): + """get cache folder path + + Returns: + [type] -- [description] + """ + folder_name = 'export' + data_folder = get_data_path() + return resource_path(data_folder, folder_name, level=AbsPath.SHOW) + + +def get_view_path(): + """get view/image folder path + + Returns: + [type] -- [description] + """ + folder_name = 'view' + data_folder = get_data_path() + return resource_path(data_folder, folder_name, level=AbsPath.SHOW) + + +def get_etl_path(*sub_paths): + """get etl output folder path + + Returns: + [type] -- [description] + """ + folder_name = 'etl' + data_folder = get_data_path() + + return resource_path(data_folder, folder_name, *sub_paths, level=AbsPath.SHOW) + + +def df_chunks(df, size): + """Yield n-sized chunks from dataframe.""" + if df.columns.size == 0: + return df + + for i in range(0, df.shape[0], size): + yield df.iloc[i:i + size] + + +def chunks(lst, size): + """Yield n-sized chunks from lst.""" + for i in range(0, len(lst), size): + yield lst[i:i + size] + + +def chunks_dic(data, size): + it = iter(data) + for i in range(0, len(data), size): + yield {k: data[k] for k in islice(it, size)} + + +def get_base_dir(path, is_file=True): + dir_name = os.path.dirname(path) if is_file else path + return os.path.basename(dir_name) + + +def make_dir(dir_path): + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + return True + + +def get_basename(path): + return os.path.basename(path) + + +def get_datetime_without_timezone(time): + """ remove timezone string from time + + Args: + time ([type]): [description] + """ + regex_str = r"(\d{4}[-\/]\d{2}[-\/]\d{2}\s\d{2}:\d{2}:\d{2}(.\d{1,6})?)" + res = re.search(regex_str, time) + if res: + return convert_time(res.group()) + + return None + + +def strip_quote_csv(instr): + return str(instr).strip("'").strip() + + +def strip_all_quote(instr): + return str(instr).strip("'").strip('"') + + +def strip_space(instr): + return str(instr).strip() + + +def get_csv_delimiter(csv_delimiter): + """ + return tab , comma depend on input data + :param csv_delimiter: + :return: + """ + if csv_delimiter is None: + return CsvDelimiter.CSV.value + + if isinstance(csv_delimiter, CsvDelimiter): + return csv_delimiter.value + + return CsvDelimiter[csv_delimiter].value + + +def sql_regexp(expr, item): + reg = re.compile(expr, re.I) + return reg.search(str(item)) is not None + + +def set_sqlite_params(conn): + cursor = conn.cursor() + cursor.execute(f'PRAGMA journal_mode=WAL') + cursor.execute('PRAGMA synchronous=NORMAL') + cursor.execute('PRAGMA cache_size=10000') + cursor.execute('pragma mmap_size = 30000000000') + cursor.execute('PRAGMA temp_store=MEMORY') + cursor.close() + + +def gen_sql_label(*args): + return SQL_COL_PREFIX + SQL_COL_PREFIX.join([str(name).strip(SQL_COL_PREFIX) for name in args if name is not None]) + + +def gen_sql_like_value(val, func: FilterFunc, position=None): + if func is FilterFunc.STARTSWITH: + return [val + '%'] + + if func is FilterFunc.ENDSWITH: + return ['%' + val] + + if func is FilterFunc.CONTAINS: + return ['%' + val + '%'] + + if func is FilterFunc.SUBSTRING: + if position is None: + position = 1 + return ['_' * max(0, (position - 1)) + val + '%'] + + if func is FilterFunc.AND_SEARCH: + conds = set(val.split()) + cond_patterns = list(permutations(conds)) # temp solution, conditions are not so many + return ['%' + '%'.join(cond_pattern) + '%' for cond_pattern in cond_patterns] + + if func is FilterFunc.OR_SEARCH: + return ['%' + cond + '%' for cond in val.split()] + + return [] + + +def gen_python_regex(val, func: FilterFunc, position=None): + if func is FilterFunc.MATCHES: + return '^' + val + '$' + + if func is FilterFunc.STARTSWITH: + return '^' + val + + if func is FilterFunc.ENDSWITH: + return val + '$' + + if func is FilterFunc.CONTAINS: + return '.*' + val + '.*' + + if func is FilterFunc.SUBSTRING: + if position is None: + position = 1 + return '^' + '.' * max(0, (position - 1)) + val + return val + + +def make_dir_from_file_path(file_path): + dirname = os.path.dirname(file_path) + # make dir + if not os.path.exists(dirname): + os.makedirs(dirname) + + return dirname + + +def delete_file(file_path): + if os.path.exists(file_path): + os.remove(file_path) + + +def rename_file(src, des): + if os.path.exists(src): + os.rename(src, des) + + +def check_exist(file_path): + return os.path.exists(file_path) + + +def any_not_none_in_dict(dict_input): + """ + check any not None in a list of dictionary + :param dict_input: [{'a': None, 'b': None}, {'a': 1, 'b': 2}] + :return: boolean + """ + return True in [any(k is not None for k in v.values()) for _, v in enumerate(dict_input)] + + +def calc_overflow_boundary(arr, remove_non_real=False): + if len(arr): + q1 = np.quantile(arr, 0.25, interpolation='midpoint') + q3 = np.quantile(arr, 0.75, interpolation='midpoint') + iqr = q3 - q1 + if iqr: + lower_boundary = q1 - 4.5 * iqr + upper_boundary = q3 + 4.5 * iqr + return lower_boundary, upper_boundary + return None, None + + +def reformat_dt_str(start_time, dt_format=DATE_FORMAT_QUERY): + if not start_time: + return start_time + dt = parser.parse(start_time) + return dt.strftime(dt_format) + + +def as_list(param): + if type(param) in [tuple, list, set]: + return list(param) + else: + return [param] + + +def is_empty(v): + if not v and v != 0: + return True + return False + + +def detect_file_encoding(file): + encoding = chardet.detect(file).get('encoding') + if encoding == ENCODING_ASCII: + encoding = ENCODING_UTF_8 + + return encoding + + +def detect_encoding(f_name, read_line=10000): + with open(f_name, 'rb') as f: + if read_line: + data = f.read(read_line) + else: + data = f.read() + + encoding = chardet.detect(data).get('encoding') + if encoding == ENCODING_ASCII: + encoding = ENCODING_UTF_8 + + return encoding + + +def is_eof(f): + cur = f.tell() # save current position + f.seek(0, os.SEEK_END) + end = f.tell() # find the size of file + f.seek(cur, os.SEEK_SET) + return cur == end + + +def replace_str_in_file(file_name, search_str, replace_to_str): + # get encoding + encoding = detect_encoding(file_name) + with open(file_name, encoding=encoding) as f: + replaced_text = f.read().replace(search_str, replace_to_str) + + with open(file_name, "w", encoding=encoding) as f_out: + f_out.write(replaced_text) + + +def get_file_modify_time(file_path): + file_time = datetime.utcfromtimestamp(os.path.getmtime(file_path)) + return convert_time(file_time) + + +def split_path_to_list(file_path): + folders = os.path.normpath(file_path).split(os.path.sep) + return folders + + +def get_ip_address(): + hostname = socket.gethostname() + ip_addr = socket.gethostbyname(hostname) + + return ip_addr + + +def gen_abbr_name(name, len_of_col_name=10): + suffix = '...' + short_name = name + if len(short_name) > len_of_col_name: + short_name = name[:len_of_col_name - len(suffix)] + suffix + + return short_name + + +# def remove_inf(series): +# return series[~series.isin([float('inf'), float('-inf')])] + +def read_pickle_file(file): + with open(file, 'rb') as f: + pickle_data = pickle.load(f) + return pickle_data + + +def write_to_pickle(data, file): + with open(file, 'wb') as f: + pickle.dump(data, f) + return file + + +def get_debug_g_dict(): + return g.setdefault(FlaskGKey.DEBUG_SHOW_GRAPH, {}) + + +def set_debug_data(func_name, data): + if not func_name: + return + + g_debug = get_debug_g_dict() + g_debug[func_name] = data + + return True + + +def get_debug_data(key): + g_debug = get_debug_g_dict() + data = g_debug.get(key, None) + return data + + +@log_execution_time() +def zero_variance(df: DataFrame): + for col in df.columns: + variance = df[col].var() + if pd.isna(variance) or variance == 0: + return True + return False + + +@log_execution_time() +def find_babel_locale(lang): + if not lang: + return lang + + lang = str(lang).lower() + lang = lang.replace('-', '_') + for _lang in LANGUAGES: + if lang == _lang.lower(): + return _lang + + return lang diff --git a/histview2/common/constants.py b/histview2/common/constants.py new file mode 100644 index 0000000..72403f9 --- /dev/null +++ b/histview2/common/constants.py @@ -0,0 +1,685 @@ +from enum import Enum, auto + +MATCHED_FILTER_IDS = 'matched_filter_ids' +UNMATCHED_FILTER_IDS = 'unmatched_filter_ids' +NOT_EXACT_MATCH_FILTER_IDS = 'not_exact_match_filter_ids' +STRING_COL_IDS = 'string_col_ids' + +SQL_COL_PREFIX = '__' +SQL_LIMIT = 5_000_000 +ACTUAL_RECORD_NUMBER = 'actual_record_number' +ACTUAL_RECORD_NUMBER_TRAIN = 'actual_record_number_train' +ACTUAL_RECORD_NUMBER_TEST = 'actual_record_number_test' +REMOVED_OUTLIER_NAN_TRAIN = 'removed_outlier_nan_train' +REMOVED_OUTLIER_NAN_TEST = 'removed_outlier_nan_test' + +YAML_CONFIG_BASIC = 'basic' +YAML_CONFIG_DB = 'db' +YAML_CONFIG_PROC = 'proc' +YAML_CONFIG_HISTVIEW2 = 'histview2' +YAML_CONFIG_VERSION = 'version' +YAML_TILE_INTERFACE_DN7 = 'ti_dn7' +YAML_TILE_INTERFACE_AP = 'ti_analysis_platform' +TILE_RESOURCE_URL = '/histview2/tile_interface/resources/' +DB_BACKUP_SUFFIX = '_old' +DB_BACKUP_FOLDER = 'backup' +IN_MODIFIED_DAYS = 30 +NORMAL_MODE_MAX_RECORD = 10000 + +DEFAULT_WARNING_DISK_USAGE = 80 +DEFAULT_ERROR_DISK_USAGE = 90 + + +class FilterFunc(Enum): + MATCHES = auto() + ENDSWITH = auto() + STARTSWITH = auto() + CONTAINS = auto() + REGEX = auto() + SUBSTRING = auto() + OR_SEARCH = auto() + AND_SEARCH = auto() + + +class CsvDelimiter(Enum): + CSV = ',' + TSV = '\t' + DOT = '.' + SMC = ';' + Auto = None + + +class DBType(Enum): + POSTGRESQL = 'postgresql' + MSSQLSERVER = 'mssqlserver' + SQLITE = 'sqlite' + ORACLE = 'oracle' + MYSQL = 'mysql' + CSV = 'csv' + + +class ErrorMsg(Enum): + W_PCA_INTEGER = auto() + E_PCA_NON_NUMERIC = auto() + + E_ALL_NA = auto() + E_ZERO_VARIANCE = auto() + + +# YAML Keywords +YAML_INFO = 'info' +YAML_R_PATH = 'r-path' +YAML_PROC = 'proc' +YAML_SQL = 'sql' +YAML_FROM = 'from' +YAML_SELECT_OTHER_VALUES = 'select-other-values' +YAML_MASTER_NAME = 'master-name' +YAML_WHERE_OTHER_VALUES = 'where-other-values' +YAML_FILTER_TIME = 'filter-time' +YAML_FILTER_LINE_MACHINE_ID = 'filter-line-machine-id' +YAML_MACHINE_ID = 'machine-id' +YAML_DATE_COL = 'date-column' +YAML_AUTO_INCREMENT_COL = 'auto_increment_column' +YAML_SERIAL_COL = 'serial-column' +YAML_SELECT_PREFIX = 'select-prefix' +YAML_CHECKED_COLS = 'checked-columns' +YAML_COL_NAMES = 'column-names' +YAML_DATA_TYPES = 'data-types' +YAML_ALIASES = 'alias-names' +YAML_MASTER_NAMES = 'master-names' +YAML_OPERATORS = 'operators' +YAML_COEFS = 'coefs' +YAML_TYPE = 'type' +YAML_COL_NAME = 'column_name' +YAML_ORIG_COL_NAME = 'column_name' +YAML_VALUE_LIST = 'value_list' +YAML_VALUE_MASTER = 'value_masters' +YAML_SQL_STATEMENTS = 'sql_statements' +YAML_TRACE = 'trace' +YAML_TRACE_BACK = 'back' +YAML_TRACE_FORWARD = 'forward' +YAML_CHART_INFO = 'chart-info' +YAML_DEFAULT = 'default' +YAML_THRESH_H = 'thresh_high' +YAML_THRESH_L = 'thresh_low' +YAML_Y_MAX = 'y_max' +YAML_Y_MIN = 'y_min' +YAML_TRACE_SELF_COLS = 'self-alias-columns' +YAML_TRACE_TARGET_COLS = 'target-orig-columns' +YAML_TRACE_MATCH_SELF = 'self-substr' +YAML_TRACE_MATCH_TARGET = 'target-substr' +YAML_DB = 'db' +YAML_UNIVERSAL_DB = 'universal_db' +YAML_ETL_FUNC = 'etl_func' +YAML_PROC_ID = 'proc_id' +YAML_PASSWORD = 'password' +YAML_HASHED = 'hashed' +YAML_DELIMITER = 'delimiter' + +# JSON Keywords +GET02_VALS_SELECT = 'GET02_VALS_SELECT' +ARRAY_FORMVAL = 'ARRAY_FORMVAL' +ARRAY_PLOTDATA = 'array_plotdata' +SERIAL_DATA = 'serial_data' +SERIAL_COLUMNS = 'serial_columns' +COMMON_INFO = 'common_info' +DATETIME_COL = 'datetime_col' +CYCLE_IDS = 'cycle_ids' +ARRAY_Y = 'array_y' +ARRAY_Z = 'array_z' +ORIG_ARRAY_Z = 'orig_array_z' +ARRAY_Y_MIN = 'array_y_min' +ARRAY_Y_MAX = 'array_y_max' +ARRAY_Y_TYPE = 'array_y_type' +IQR = 'iqr' +ARRAY_X = 'array_x' +Y_MAX = 'y-max' +Y_MIN = 'y-min' +Y_MAX_ORG = 'y_max_org' +Y_MIN_ORG = 'y_min_org' +TIME_RANGE = 'time_range' +TOTAL = 'total' + +UNLINKED_IDXS = 'unlinked_idxs' +NONE_IDXS = 'none_idxs' +INF_IDXS = 'inf_idxs' +NEG_INF_IDXS = 'neg_inf_idxs' +UPPER_OUTLIER_IDXS = 'upper_outlier_idxs' +LOWER_OUTLIER_IDXS = 'lower_outlier_idxs' + +SCALE_SETTING = 'scale_setting' +SCALE_THRESHOLD = 'scale_threshold' +SCALE_AUTO = 'scale_auto' +SCALE_COMMON = 'scale_common' +SCALE_FULL = 'scale_full' +KDE_DATA = 'kde_data' +SCALE_Y = 'scale_y' +SCALE_X = 'scale_x' +SCALE_COLOR = 'scale_color' + +CHART_INFOS = 'chart_infos' +CHART_INFOS_ORG = 'chart_infos_org' +COMMON = 'COMMON' +SELECT_ALL = 'All' +NO_FILTER = 'NO_FILTER' +START_PROC = 'start_proc' +START_DATE = 'START_DATE' +START_TM = 'START_TIME' +START_DT = 'start_dt' +COND_PROCS = 'cond_procs' +COND_PROC = 'cond_proc' +END_PROC = 'end_proc' +END_DATE = 'END_DATE' +END_TM = 'END_TIME' +END_DT = 'end_dt' +IS_REMOVE_OUTLIER = 'isRemoveOutlier' +TBLS = 'TBLS' +FILTER_PARTNO = 'filter-partno' +FILTER_MACHINE = 'machine_id' +CATE_PROC = 'end_proc_cate' +GET02_CATE_SELECT = 'GET02_CATE_SELECT' +CATEGORY_DATA = 'category_data' +CATE_PROCS = 'cate_procs' +TIMES = 'times' +TIME_NUMBERINGS = 'time_numberings' +ELAPSED_TIME = 'elapsed_time' +COLORS = 'colors' +H_LABEL = 'h_label' +V_LABEL = 'v_label' +TIME_MIN = 'time_min' +TIME_MAX = 'time_max' +X_THRESHOLD = 'x_threshold' +Y_THRESHOLD = 'y_threshold' +X_SERIAL = 'x_serial' +Y_SERIAL = 'y_serial' +SORT_KEY = 'sort_key' + +IS_RES_LIMITED = 'is_res_limited' +IS_RES_LIMITED_TRAIN = 'is_res_limited_train' +IS_RES_LIMITED_TEST = 'is_res_limited_test' +WITH_IMPORT_OPTIONS = 'with_import' +GET_PARAM = 'get_param' +PROCS = 'procs' +CLIENT_TIMEZONE = 'client_timezone' +DATA_SIZE = 'data_size' +X_OPTION = 'xOption' +SERIAL_PROCESS = 'serialProcess' +SERIAL_COLUMN = 'serialColumn' +SERIAL_ORDER = 'serialOrder' +TEMP_X_OPTION = 'TermXOption' +TEMP_SERIAL_PROCESS = 'TermSerialProcess' +TEMP_SERIAL_COLUMN = 'TermSerialColumn' +TEMP_SERIAL_ORDER = 'TermSerialOrder' +THRESHOLD_BOX = 'thresholdBox' +SCATTER_CONTOUR = 'scatter_contour' +DF_ALL_PROCS = 'dfProcs' +DF_ALL_COLUMNS = 'dfColumns' +CHART_TYPE = 'chartType' + +# CATEGORICAL PLOT +CATE_VARIABLE = 'categoryVariable' +CATE_VALUE_MULTI = 'categoryValueMulti' +PART_NO = 'PART_NO' +MACHINE_ID = 'MACHINE_ID' +COMPARE_TYPE = 'compareType' +CATEGORICAL = 'var' +TERM = 'term' +RL_CATEGORY = 'category' +RL_CYCLIC_TERM = 'cyclicTerm' +RL_DIRECT_TERM = 'directTerm' +TIME_CONDS = 'time_conds' +CATE_CONDS = 'cate_conds' +LINE_NO = 'LINE_NO' +YAML_LINE_LIST = 'line-list' +FILTER_OTHER = 'filter-other' +THRESH_HIGH = 'thresh-high' +THRESH_LOW = 'thresh-low' +PRC_MAX = 'prc-max' +PRC_MIN = 'prc-min' +ACT_FROM = 'act-from' +ACT_TO = 'act-to' +CYCLIC_DIV_NUM = 'cyclicTermDivNum' +CYCLIC_INTERVAL = 'cyclicTermInterval' +CYCLIC_WINDOW_LEN = 'cyclicTermWindowLength' +CYCLIC_TERMS = 'cyclic_terms' +END_PROC_ID = 'end_proc_id' +END_PROC_NAME = 'end_proc_name' +END_COL_ID = 'end_col_id' +END_COL_NAME = 'end_col_name' +RANK_COL = 'before_rank_values' +SUMMARIES = 'summaries' +CAT_DISTRIBUTE = 'category_distributed' +CAT_SUMMARY = 'cat_summary' +N = 'n' +N_PCTG = 'n_pctg' +N_NA = 'n_na' +N_NA_PCTG = 'n_na_pctg' +N_TOTAL = 'n_total' +UNIQUE_CATEGORIES = 'unique_categories' +UNIQUE_DIV = 'unique_div' +UNIQUE_COLOR = 'unique_color' +IS_OVER_UNIQUE_LIMIT = 'isOverUniqueLimit' +DIC_CAT_FILTERS = 'dic_cat_filters' +TEMP_CAT_EXP = 'temp_cat_exp' +TEMP_CAT_PROCS = 'temp_cat_procs' +DIV_BY_DATA_NUM = 'dataNumber' +DIV_BY_CAT = 'div' +COLOR_VAR = 'colorVar' +IS_DATA_LIMITED = 'isDataLimited' + +# Cat Expansion +CAT_EXP_BOX = 'catExpBox' + +# Order columns +INDEX_ORDER_COLS = 'indexOrderColumns' +THIN_DATA_GROUP_COUNT = 'thinDataGroupCounts' + +# validate data flag +IS_VALIDATE_DATA = 'isValidateData' +# Substring column name in universal db +SUB_STRING_COL_NAME = '{}_From_{}_To_{}' +SUB_STRING_REGEX = r'^.+_From_(\d+)_To_(\d+)$' + +# heatmap +HM_STEP = 'step' +HM_MODE = 'mode' +HM_FUNCTION_REAL = 'function_real' +HM_FUNCTION_CATE = 'function_cate' +HM_TRIM = 'remove_outlier' +CELL_SUFFIX = '_cell' +AGG_COL = 'agg_col' +TIME_COL = 'time' + + +class HMFunction(Enum): + max = auto() + min = auto() + mean = auto() + std = auto() + range = auto() + median = auto() + count = auto() + count_per_hour = auto() + count_per_min = auto() + first = auto() + time_per_count = auto() + iqr = auto() + + +class RelationShip(Enum): + ONE = auto() + MANY = auto() + + +class AbsPath(Enum): + SHOW = auto() + HIDE = auto() + + +class DataType(Enum): + NULL = 0 + INTEGER = 1 + REAL = 2 + TEXT = 3 + DATETIME = 4 + + +class DataTypeEncode(Enum): + NULL = '' + INTEGER = 'Int' + REAL = 'Real' + TEXT = 'Str' + DATETIME = 'CT' + + +class JobStatus(Enum): + def __str__(self): + return str(self.name) + + PENDING = 0 + PROCESSING = 1 + DONE = 2 + KILLED = 3 + FAILED = 4 + FATAL = 5 # error when insert to db commit, file lock v...v ( we need re-run these files on the next job) + + +class Outliers(Enum): + NOT_OUTLIER = 0 + IS_OUTLIER = 1 + + +class FlaskGKey(Enum): + TRACE_ERR = auto() + YAML_CONFIG = auto() + APP_DB_SESSION = auto() + DEBUG_SHOW_GRAPH = auto() + MEMOIZE = auto() + + +class DebugKey(Enum): + IS_DEBUG_MODE = auto() + GET_DATA_FROM_DB = auto() + + +class MemoizeKey(Enum): + STOP_USING_CACHE = auto() + + +# error message for dangling jobs +FORCED_TO_BE_FAILED = 'DANGLING JOB. FORCED_TO_BE_FAILED' +DEFAULT_POLLING_FREQ = 180 # default is import every 3 minutes + +class CfgConstantType(Enum): + def __str__(self): + return str(self.name) + + # CHECKED_COLUMN = 0 # TODO define value + # GUI_TYPE = 1 + # FILTER_REGEX = 2 + # PARTNO_LIKE = 3 + POLLING_FREQUENCY = auto() + ETL_JSON = auto() + UI_ORDER = auto() + USE_OS_TIMEZONE = auto() + TS_CARD_ORDER = auto() + EFA_HEADER_EXISTS = auto() + DISK_USAGE_CONFIG = auto() + + +# UI order types +UI_ORDER_DB = 'tblDbConfig' +UI_ORDER_PROC = 'tblProcConfig' + +# SQL +SQL_PERCENT = '%' +SQL_REGEX_PREFIX = 'RAINBOW7_REGEX:' +SQL_REGEXP_FUNC = 'REGEXP' + +# DATA TRACE LOG CONST +EXECTIME = 'ExecTime' +INPUTDATA = 'InputData' +# Measurement Protocol Server +MPS = 'www.google-analytics.com' +R_PORTABLE = 'R-Portable' +R_LIB_VERSION = 'R_LIB_VERSION' + +# Message +MSG_DB_CON_FAILED = 'Database connection failed! Please check your database connection information' + +# encoding +ENCODING_SHIFT_JIS = 'cp932' +ENCODING_UTF_8 = 'utf-8' +ENCODING_UTF_8_BOM = 'utf-8-sig' +ENCODING_ASCII = 'ascii' + +# Web socket +SOCKETIO = 'socketio' +PROC_LINK_DONE_PUBSUB = '/proc_link_done_pubsub' +PROC_LINK_DONE_SUBSCRIBE = 'proc_link_subscribe' +PROC_LINK_DONE_PUBLISH = 'proc_link_publish' +SHUTDOWN_APP_DONE_PUBSUB = '/shutdown_app_done_pubsub' +SHUTDOWN_APP_DONE_PUBLISH = 'shutdown_app_publish' +BACKGROUND_JOB_PUBSUB = '/job' +# JOB_STATUS_PUBLISH = 'job_status_publish' +# JOB_INFO_PUBLISH = 'res_background_job' + +# Dictionary Key +HAS_RECORD = 'has_record' + +# WRAPR keys +WR_CTGY = 'ctgy' +WR_HEAD = 'head' +WR_RPLC = 'rplc' +WR_VALUES = 'values' +WR_HEADER_NAMES = 'header_name' +WR_TYPES = 'types' +# RIDGELINE +RL_GROUPS = 'groups' +RL_EMD = 'emd' +RL_DATA = 'data' +RL_RIDGELINES = 'ridgelines' +RL_ARRAY_X = 'array_x' +RL_CATE_NAME = 'cate_name' +RL_PERIOD = 'TargetPeriod.from|TargetPeriod.to' +RL_SENSOR_NAME = 'sensor_name' +RL_PROC_NAME = 'proc_name' +RL_KDE = 'kde_data' +RL_DEN_VAL = 'kde' +RL_ORG_DEN = 'origin_kde' +RL_TRANS_VAL = 'transform_val' +RL_TRANS_DEN = 'trans_kde' +RL_XAXIS = 'rlp_xaxis' +RL_YAXIS = 'rlp_yaxis' +RL_HIST_LABELS = 'hist_labels' +RL_HIST_COUNTS = 'hist_counts' +RL_DATA_COUNTS = 'data_counts' +RL_CATES = 'categories' + +# SkD +SKD_TARGET_PROC_CLR = '#65c5f1' + +# tile interface +TILE_INTERFACE = 'tile_interface' +SECTIONS = 'sections' +DN7_TILE = 'dn7' +AP_TILE = 'analysis_platform' + + +# actions +class Action(Enum): + def __str__(self): + return str(self.name) + + SHUTDOWN_APP = auto() + + +class YType(Enum): + NORMAL = 0 + INF = 1 + NEG_INF = -1 + NONE = 2 + OUTLIER = 3 + NEG_OUTLIER = -3 + UNLINKED = -4 + + +class CfgFilterType(Enum): + def __str__(self): + return str(self.name) + + LINE = auto() + MACHINE_ID = auto() + PART_NO = auto() + OTHER = auto() + + +class ProcessCfgConst(Enum): + PROC_ID = 'id' + PROC_COLUMNS = 'columns' + + +class EFAColumn(Enum): + def __str__(self): + return str(self.name) + + Line = auto() + Process = auto() + Machine = auto() + + +EFA_HEADER_FLAG = '1' + + +class Operator(Enum): + def __str__(self): + return str(self.name) + + PLUS = '+' + MINUS = '-' + PRODUCT = '*' + DEVIDE = '/' + REGEX = 'regex' + + +class AggregateBy(Enum): + DAY = 'Day' + HOUR = 'Hour' + + +# App Config keys +SQLITE_CONFIG_DIR = 'SQLITE_CONFIG_DIR' +PARTITION_NUMBER = 'PARTITION_NUMBER' +UNIVERSAL_DB_FILE = 'UNIVERSAL_DB_FILE' +APP_DB_FILE = 'APP_DB_FILE' +TESTING = 'TESTING' + +DATA_TYPE_ERROR_MSG = 'Data Type Error' +DATA_TYPE_DUPLICATE_MSG = 'Duplicate Record' + +AUTO_BACKUP = 'auto-backup-universal' + + +class appENV(Enum): + PRODUCTION = 'prod' + DEVELOPMENT = 'dev' + + +THIN_DATA_CHUNK = 4000 +THIN_DATA_COUNT = THIN_DATA_CHUNK * 3 + +# variables correlation +CORRS = 'corrs' +CORR = 'corr' +PCORR = 'pcorr' +NTOTALS = 'ntotals' +DUPLICATE_COUNT_COLUMN = '__DUPLICATE_COUNT__' # A column that store duplicate count of current data row + +# Heatmap +MAX_TICKS = 8 +AGG_FUNC = 'agg_function' +CATE_VAL = 'cate_value' +END_COL = 'end_col' +X_TICKTEXT = 'x_ticktext' +X_TICKVAL = 'x_tickvals' +Y_TICKTEXT = 'y_ticktext' +Y_TICKVAL = 'y_tickvals' +ACT_CELLS = 'actual_num_cell' + +OBJ_VAR = 'objectiveVar' + +CAT_TOTAL = 'cat_total' +IS_CAT_LIMITED = 'is_cat_limited' +MAX_CATEGORY_SHOW = 30 + +# PCA +SHORT_NAMES = 'short_names' +DATAPOINT_INFO = 'data_point_info' +PLOTLY_JSON = 'plotly_jsons' +DIC_SENSOR_HEADER = 'dic_sensor_headers' + + +# chart type +class ChartType(Enum): + HEATMAP = 'heatmap' + SCATTER = 'scatter' + VIOLIN = 'violin' + + +# Scp sub request params +MATRIX_COL = 'colNumber' +COLOR_ORDER = 'scpColorOrder' + + +# COLOR ORDER +class ColorOrder(Enum): + DATA = 1 + TIME = 2 + ELAPSED_TIME = 3 + + +# import export debug info +DIC_FORM_NAME = 'dic_form' +DF_NAME = 'df' +CONFIG_DB_NAME = 'config_db' +USER_SETTING_NAME = 'user_setting' +USER_SETTING_VERSION = 0 +EN_DASH = '–' + + +# Disk usage warning level +class DiskUsageStatus(Enum): + Normal = 0 + Warning = 1 + Full = 2 + + +# debug mode +IS_EXPORT_MODE = 'isExportMode' +IS_IMPORT_MODE = 'isImportMode' + +# NA +NA_STR = 'NA' + +# Recent +VAR_TRACE_TIME = 'varTraceTime' +TERM_TRACE_TIME = 'termTraceTime' +CYCLIC_TRACE_TIME = 'cyclicTraceTime' +TRACE_TIME = 'traceTime' + +# Limited graph flag +IS_GRAPH_LIMITED = 'isGraphLimited' + +# language +LANGUAGES = [ + 'ja', + 'en', + 'it', + 'es', + 'vi', + 'pt', + 'hi', + 'th', + 'zh_CN', + 'zh_TW', + 'ar', + 'bg', + 'ca', + 'cs', + 'cy', + 'de', + 'el', + 'fa', + 'fi', + 'fr', + 'gd', + 'he', + 'hr', + 'hu', + 'id', + 'is', + 'km', + 'ko', + 'lb', + 'mi', + 'mk', + 'mn', + 'ms', + 'my', + 'ne', + 'nl', + 'no', + 'pa', + 'pl', + 'pt', + 'ro', + 'ru', + 'sd', + 'si', + 'sk', + 'sq', + 'sv', + 'te', + 'tl', + 'tr' +] diff --git a/histview2/common/cryptography_utils.py b/histview2/common/cryptography_utils.py new file mode 100644 index 0000000..7453325 --- /dev/null +++ b/histview2/common/cryptography_utils.py @@ -0,0 +1,113 @@ +from cryptography.fernet import Fernet +from histview2.common.constants import DBType, ENCODING_UTF_8 +import copy +from histview2 import dic_config + + +def generate_key(): + """ + Generate Fernet key for encrypting and decrypting + :return: + """ + return Fernet.generate_key() + + +def encode_db_secret_key(): + DB_SECRET_KEY = dic_config['DB_SECRET_KEY'] + return Fernet(str.encode(DB_SECRET_KEY)) + + +def encrypt(plain_text): + """ + Encoding a text with a key using Fernet. + :param plain_text: str or bytes + :return: cipher_text: bytes + """ + if plain_text is None: + return None + + cipher_suite = encode_db_secret_key() + if type(plain_text) == bytes: + plain_text_bytes = plain_text + else: + plain_text_bytes = str.encode(plain_text) + cipher_text = cipher_suite.encrypt(plain_text_bytes) + + return cipher_text + + +def decrypt(cipher_text): + """ + Decoding a text with a key using Fernet. + :param cipher_text: + :return: plain_text + """ + cipher_suite = encode_db_secret_key() + cipher_text_bytes = str.encode(cipher_text) + plain_text = cipher_suite.decrypt(cipher_text_bytes) + + return plain_text + + +def decrypt_pwd(cipher_text): + """ + Decoding a text with a key using Fernet. + :param cipher_text: str or bytes + :return: plain_text: str + """ + if cipher_text is None: + return None + + cipher_suite = encode_db_secret_key() + if type(cipher_text) == bytes: + cipher_text_bytes = cipher_text + else: + cipher_text_bytes = str.encode(cipher_text) + + plain_text = cipher_suite.decrypt(cipher_text_bytes) + + return plain_text.decode(ENCODING_UTF_8) + + +def encrypt_db_password(dict_db_config): + """ + + :param dict_db_config: db config dict: {db: {db-name: {key:value}}} + :return: Hashed dict_db_config + """ + dict_db_config_hashed = copy.deepcopy(dict_db_config) + + if dict_db_config_hashed.get('db'): + for key in dict_db_config_hashed["db"]: + db_config = dict_db_config_hashed["db"][key] + if db_config and not db_config.get("hashed") \ + and db_config.get("type") != DBType.SQLITE.value: + plain_password = db_config.get("password") + if plain_password: + hashed_password = encrypt(plain_password) + db_config["password"] = str(hashed_password, encoding="utf-8") + db_config["hashed"] = True + + return dict_db_config_hashed + + +def decrypt_db_password(dict_db_config): + """ + + :param dict_db_config: db config dict: {db: {db-name: {key:value}}} + :return: Unhashed dict_db_config + """ + dict_db_config_unhashed = copy.deepcopy(dict_db_config) + + if dict_db_config_unhashed and dict_db_config_unhashed.get('db'): + for key in dict_db_config_unhashed["db"]: + db_config = dict_db_config_unhashed["db"][key] + if db_config and db_config.get("hashed"): + hashed_password = db_config.get("password") + plain_password = b"" + if hashed_password: + plain_password = decrypt(hashed_password) + db_config["password"] = str(plain_password, encoding="utf-8") + db_config["hashed"] = False + + return dict_db_config_unhashed diff --git a/histview2/common/disk_usage.py b/histview2/common/disk_usage.py new file mode 100644 index 0000000..bb13be2 --- /dev/null +++ b/histview2/common/disk_usage.py @@ -0,0 +1,154 @@ +import shutil +import json + +from histview2.common.memoize import memoize +from histview2.common.common_utils import get_ip_address +from histview2.setting_module.models import CfgConstant +from histview2.common.constants import DiskUsageStatus +from histview2.common.logger import log_execution_time + + +class DiskUsageInterface: + + @classmethod + def get_disk_usage(cls, path=None): + raise NotImplementedError() + + +class MainDiskUsage(DiskUsageInterface): + """ + Checks disk usage of disk/partition that main application was installed on. + + """ + + @classmethod + def get_disk_usage(cls, path=None): + return shutil.disk_usage(path=path) + + +@memoize(duration=5*60) +def get_disk_usage_percent(path=None): + """ + Gets disk usage information. + + :param path: disk location
In case not pass this argument, it will be set './' as default location + :return: a tuple of (disk status, used percent) + """ + if not path: + path = './' # as default dir + + dict_status_measures = {CfgConstant.get_warning_disk_usage(): DiskUsageStatus.Warning, + CfgConstant.get_error_disk_usage(): DiskUsageStatus.Full} + dict_limit_capacity = {y: x for x, y in dict_status_measures.items() if x} # switch key value + + status = DiskUsageStatus.Normal + used_percent = 0 + rules = [MainDiskUsage] + + for checker in rules: + usage = checker.get_disk_usage(path) + used_percent = round(usage.used / usage.total * 100) + for measure in sorted(dict_status_measures.keys()): + if measure: + if used_percent >= measure: + status = dict_status_measures.pop(measure) + if not dict_status_measures: + break + + return status, used_percent, dict_limit_capacity + + +def get_disk_capacity(): + """ + Get information of disk capacity on Bridge Station & Postgres DB + + :return: DiskCapacityException object that include disk status, used percent and message if have. + """ + disk_status, used_percent, dict_limit_capacity = get_disk_usage_percent() + print(f'Disk usage: {used_percent}% - {disk_status.name}') + + message = '' + if disk_status == DiskUsageStatus.Full: + message = 'Data import has stopped because the hard disk capacity of `__SERVER_INFO__` has reached ' \ + f'{dict_limit_capacity.get(DiskUsageStatus.Full)}%. ' \ + 'Data import will restart when unnecessary data is deleted and the free space increases.' + elif disk_status == DiskUsageStatus.Warning: + message = 'Please delete unnecessary data because the capacity of the hard disk of `__SERVER_INFO__` has ' \ + f'reached {dict_limit_capacity.get(DiskUsageStatus.Warning)}%.' + + server_info = get_ip_address() + message = message.replace('__SERVER_INFO__', server_info) + return DiskCapacityException(disk_status, used_percent, server_info, + 'EdgeServer', + dict_limit_capacity.get(DiskUsageStatus.Warning), + dict_limit_capacity.get(DiskUsageStatus.Full), message) + + +def get_disk_capacity_once(_job_id=None): + """ + Get information of disk capacity on Bridge Station & Postgres DB and always return DiskCapacityException object + + Attention: DO NOT USE ANYWHERE ELSE, this is only used in send_processing_info method to serve checking + disk capacity for each job. + + :param _job_id: serve to check & run only once for each _job_id + :return: DiskCapacityException object + """ + return get_disk_capacity() + + +class DiskCapacityException(Exception): + """Exception raised for disk usage exceed the allowed limit. + + Attributes: + disk_status -- status of disk usage + used_percent -- amount of used storage + server_info -- String of server information + server_type -- Type of server + warning_limit_percent -- limit level + error_limit_percent -- limit level + message -- explanation of the error + """ + + def __init__(self, disk_status, used_percent, server_info, server_type, warning_limit_percent, error_limit_percent, + message): + self.disk_status: DiskUsageStatus = disk_status + self.used_percent = used_percent + self.server_info = server_info + self.server_type = server_type + self.warning_limit_percent = warning_limit_percent + self.error_limit_percent = error_limit_percent + self.message = message + super().__init__(self.message) + + def to_dict(self): + return { + 'disk_status': self.disk_status.name, + 'used_percent': self.used_percent, + 'server_info': self.server_info, + 'server_type': self.server_type, + 'warning_limit_percent': self.warning_limit_percent, + 'error_limit_percent': self.error_limit_percent, + 'message': self.message + } + + + +@log_execution_time() +def get_disk_capacity_to_load_UI(): + disk_capacity = { + 'EdgeServer': None, + } + + # check Edge Server + edge_disk_capacity = get_disk_capacity() + disk_capacity['EdgeServer'] = edge_disk_capacity.to_dict() + + return disk_capacity + + +def add_disk_capacity_into_response(response, disk_capacity): + render_data = response.get_data() + script = f'' + render_data = render_data.replace(bytes('', 'UTF-8'), bytes(f'{script}', 'UTF-8')) + response.set_data(render_data) diff --git a/histview2/common/logger.py b/histview2/common/logger.py new file mode 100644 index 0000000..d9a7f68 --- /dev/null +++ b/histview2/common/logger.py @@ -0,0 +1,220 @@ +import os +import shutil +from datetime import datetime, timedelta, time, date +from functools import wraps +from time import perf_counter +from zipfile import ZipFile + +from loguru import logger + +LOGGING_CONFIG = { + "handlers": [ + { + "sink": os.path.join("log", "{time:YYYYMMDD}", "{time:YYYYMMDD_HHmm}_access.log"), + "format": "{time}|{level}|{extra[ip]}|{extra[method]}|{extra[url]}|{extra[agent]}|{extra[status]}|\ + {message}", + "level": "INFO", + "mode": "a", + "compression": "zip" + } + ], + "extra": { + "ip": "-", + "agent": "-", + "url": "-", + "method": "-", + "status": "---" + } +} + + +class TimeAndSizeBasedRotator: + def __init__(self, *, size, at): + now = datetime.utcnow() + + self._size_limit = size + self._time_limit = now.replace(hour=at.hour, minute=at.minute, second=at.second) + + if now >= self._time_limit: + # The current time is already past the target time so it would rotate already. + # Add one day to prevent an immediate rotation. + self._time_limit += timedelta(days=1) + + def should_rotate(self, message, file): + file.seek(0, 2) + if file.tell() + len(message) > self._size_limit: + return True + if message.record["time"].timestamp() > self._time_limit.timestamp(): + self._time_limit += timedelta(days=1) + return True + return False + + +def log_execution(prefix='', logging_exception=True): + """ Decorator to log function run time + Arguments: + fn {function} -- [description] + prefix {string} -- prefix set to logged message + Returns: + fn {function} -- [description] + """ + + def decorator(fn): + @wraps(fn) + def wrapper(*args, **kwargs): + start_dt = datetime.utcnow() + start = perf_counter() + + log_args = str(args) + log_kwargs = str(kwargs) + try: + logger.info('{0} Function Name: {1}; START; {2}; {3}'.format(prefix, fn.__name__, log_args, log_kwargs)) + result = fn(*args, **kwargs) + except Exception as e: + if logging_exception: + logger.exception(e) + raise e + finally: + end = perf_counter() + end_dt = datetime.utcnow() + logger.info('{0} Function Name: {1}; START: {2}; END: {3}; Execution Time: {4:.6f}s;' + .format(prefix, fn.__name__, start_dt, end_dt, end - start)) + return result + + return wrapper + + return decorator + + +def get_all_file_paths(directory, start_with='', end_with='.log'): + # initializing empty file paths list + file_paths = [] + + # crawling through directory and subdirectories + for root, directories, files in os.walk(directory): + for filename in files: + if filename.startswith(start_with) and filename.endswith(end_with): + filepath = os.path.join(root, filename) + file_paths.append(filepath) + + # returning all file paths + return file_paths + + +def zip_logs(directory, prefix=''): + zip_file_path = os.path.join('log', '{zipped_month}.zip'.format(zipped_month=prefix)) + if os.path.exists(zip_file_path): + return True + + # get all file paths in the directory + file_paths = get_all_file_paths(directory, start_with=prefix, end_with='.log') + + # writing files to a zipfile + try: + with ZipFile(zip_file_path, 'w') as zip_file: + # writing each file one by one + for file in file_paths: + zip_file.write(file) + except Exception as ex: + print(ex) + return False + + return True + + +def delete_sub_folders(directory, prefix=''): + try: + sub_folders = os.listdir(directory) + for sub_folder in sub_folders: + if os.path.isdir(os.path.join(directory, sub_folder)) and sub_folder.startswith(prefix): + shutil.rmtree(os.path.join(directory, sub_folder)) + except Exception as ex: + print(ex) + + +def month_compressor(*kwargs): + log_dir = os.path.join('.', 'log') + + today = date.today() + this_month_1st_day = today.replace(day=1) # just in case + last_month_last_day = this_month_1st_day - timedelta(days=1) + last_month_prefix = last_month_last_day.strftime("%Y%m") + + # zip last month + is_zipped = zip_logs(log_dir, prefix=last_month_prefix) + + # remove old logs folders after compression + if is_zipped: + delete_sub_folders(log_dir, prefix=last_month_prefix) + + +@log_execution() +def set_log_config(): + logger.info("----------------WEB SERVER STARTED---------------------") + # Rotate file if over 50 MB and/or at midnight every day + rotator = TimeAndSizeBasedRotator(size=50000000, at=time(0, 0, 0)) + logger.configure(extra={ + "ip": "-", + "agent": "-", + "url": "-", + "method": "-", + "status": "---" + }) + log_format = "{time}|{level}|{extra[ip]}|{extra[method]}|{extra[url]}|{extra[agent]}|{extra[status]}|{message}" + # logger.add(sink=os.path.join("log", "{time:YYYYMMDD}", "{time:YYYYMMDD_HHmmss}_access.log"), + # rotation=rotator.should_rotate, format=log_format, level="INFO", mode="a", + # compression=month_compressor, + # enqueue=True) + + logger.add(sink=os.path.join("log", "{time:YYYYMMDD}", "{time:YYYYMMDD_HHmmss}_access.log"), + rotation=rotator.should_rotate, format=log_format, level="INFO", mode="a", + compression=month_compressor, encoding="utf8") + + +def bind_user_info(req=None, res=None): + status = LOGGING_CONFIG["extra"]["status"] + + if req is None: + return logger.bind() + if res is not None: + status = res.status_code + + return logger.bind(ip=req.remote_addr, url=req.full_path, agent=req.user_agent.string, + status=status, method=req.method) + + +def log_execution_time(prefix='', logging_exception=True): + """ Decorator to log function run time + Arguments: + fn {function} -- [description] + prefix {string} -- prefix set to logged message + Returns: + fn {function} -- [description] + """ + + def decorator(fn): + @wraps(fn) + def wrapper(*args, **kwargs): + start_dt = datetime.now() + start = perf_counter() + + log_args = '' + log_kwargs = '' + try: + result = fn(*args, **kwargs) + except Exception as e: + if logging_exception: + logger.exception(e) + # log_args = str(args) + # log_kwargs = str(kwargs) + raise e + finally: + end = perf_counter() + end_dt = datetime.now() + logger.info('{0} Function Name: {1}; START: {2}; END: {3}; Execution Time: {4:.6f}s; {5}; {6}' + .format(prefix, fn.__name__, start_dt, end_dt, end - start, log_args, log_kwargs)) + return result + + return wrapper + + return decorator diff --git a/histview2/common/memoize.py b/histview2/common/memoize.py new file mode 100644 index 0000000..3e7a490 --- /dev/null +++ b/histview2/common/memoize.py @@ -0,0 +1,152 @@ +import hashlib +import os +import pickle +import shutil +import time +from collections import OrderedDict +from copy import deepcopy +from functools import wraps +from threading import Lock + +from flask import g + +from histview2 import check_exist +from histview2.common.common_utils import resource_path, get_cache_path, write_to_pickle, read_pickle_file, delete_file +from histview2.common.constants import AbsPath, FlaskGKey, MemoizeKey + +cache = OrderedDict() +lock = Lock() +cache_max_size = 50 +USE_EXPIRED_CACHE_PARAM_NAME = '_use_expired_cache' + + +def is_obsolete(entry, duration=None): + if entry.get('expired'): + return True + + if duration is None: + return False + + return time.time() - entry['time'] > duration + + +# def filter_r_obj(params): +# print(params) +# if isinstance(params, (list, tuple)): +# new_params = [para for para in params if not isinstance(para, RUtils)] +# elif isinstance(params, dict): +# new_params = {key: val for key, val in params.items() if not isinstance(val, RUtils)} +# else: +# new_params = params +# +# return new_params + + +def compute_key(fn, args, kwargs): + key = pickle.dumps((fn.__name__, args, kwargs)) + # try: + # key = pickle.dumps((fn.__name__, args, kwargs)) + # except Exception: + # key = pickle.dumps((fn.__name__, filter_r_obj(args), filter_r_obj(kwargs))) + + return hashlib.sha1(key).hexdigest() + + +def create_cache_file_path(key): + file_path = resource_path(get_cache_path(), key, level=AbsPath.SHOW) + if not os.path.exists(os.path.dirname(file_path)): + os.makedirs(os.path.dirname(file_path)) + + return file_path + + +def delete_cache_file(key): + try: + delete_file(key) + except Exception: + pass + + +def clear_cache_files(): + folder_path = resource_path(get_cache_path(), level=AbsPath.SHOW) + try: + shutil.rmtree(folder_path) + except Exception: + pass + + +def memoize(is_save_file=False, duration=30 * 24 * 60 * 60): + def memoize1(fn): + @wraps(fn) + def memoize2(*args, **kwargs): + + is_use_expired = kwargs.get(USE_EXPIRED_CACHE_PARAM_NAME) + if USE_EXPIRED_CACHE_PARAM_NAME in kwargs: + kwargs.pop(USE_EXPIRED_CACHE_PARAM_NAME) + + key = compute_key(fn, args, kwargs) + + is_stop_using_cache = get_cache_attr(MemoizeKey.STOP_USING_CACHE) + if not is_stop_using_cache and key in cache and (is_use_expired or not is_obsolete(cache[key], duration)): + print('used cache') + if is_save_file: + file_name = cache[key]['file'] + if check_exist(file_name): + return read_pickle_file(file_name) + else: + return cache[key]['value'] + + result = fn(*args, **kwargs) + + with lock: + if is_save_file: + file_name = create_cache_file_path(key) + write_to_pickle(result, file_name) + cache[key] = dict(file=file_name, time=time.time()) + else: + cache[key] = dict(value=deepcopy(result), time=time.time()) + + # resize + while len(cache) > cache_max_size: + key, dic_val = cache.popitem(last=False) + if dic_val.get('file'): + delete_cache_file(key) + + return result + + return memoize2 + + return memoize1 + + +def clear_cache(): + cache.clear() + clear_cache_files() + print('CLEAR ALL CACHE') + + +def set_all_cache_expired(): + for dic_val in cache.values(): + dic_val['expired'] = True + + print('CACHE EXPIRED') + + +def get_cache_g_dict(): + return g.setdefault(FlaskGKey.MEMOIZE, {}) + + +def set_cache_attr(key, data): + if not key: + return False + + g_debug = get_cache_g_dict() + g_debug[key] = data + + return True + + +def get_cache_attr(key): + g_debug = get_cache_g_dict() + data = g_debug.get(key, None) + return data diff --git a/histview2/common/pydn/dblib/__init__.py b/histview2/common/pydn/dblib/__init__.py new file mode 100644 index 0000000..2fb10f9 --- /dev/null +++ b/histview2/common/pydn/dblib/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +print("initialized!") diff --git a/histview2/common/pydn/dblib/db_proxy.py b/histview2/common/pydn/dblib/db_proxy.py new file mode 100644 index 0000000..da25401 --- /dev/null +++ b/histview2/common/pydn/dblib/db_proxy.py @@ -0,0 +1,92 @@ +from typing import Union + +from histview2 import set_sqlite_params, dic_config +from histview2.common.constants import DBType, UNIVERSAL_DB_FILE +from histview2.common.cryptography_utils import decrypt_pwd +from histview2.common.pydn.dblib import sqlite +from histview2.common.pydn.dblib.mssqlserver import MSSQLServer +from histview2.common.pydn.dblib.mysql import MySQL +from histview2.common.pydn.dblib.oracle import Oracle +from histview2.common.pydn.dblib.postgresql import PostgreSQL +from histview2.common.pydn.dblib.sqlite import SQLite3 +from histview2.setting_module.models import CfgDataSourceDB, CfgDataSource + + +class DbProxy: + """ + An interface for client to connect to many type of database + """ + db_instance: Union[SQLite3, None, PostgreSQL, Oracle, MySQL, MSSQLServer] + db_basic: CfgDataSource + db_detail: CfgDataSourceDB + + def __init__(self, data_src, is_universal_db=False): + self.is_universal_db = is_universal_db + self.data_src = data_src + if isinstance(data_src, CfgDataSource): + self.db_basic = data_src + self.db_detail = data_src.db_detail + elif isinstance(data_src, CfgDataSourceDB): + self.db_basic = data_src.cfg_data_source + self.db_detail = data_src + else: + self.db_basic = CfgDataSource.query.get(data_src) + self.db_detail = self.db_basic.db_detail + + def __enter__(self): + self.db_instance = self._get_db_instance() + conn = self.db_instance.connect() + if self.is_universal_db: + set_sqlite_params(conn) + return self.db_instance + + def __exit__(self, exc_type, exc_val, exc_tb): + self.db_instance.disconnect() + return False + + def _get_db_instance(self): + db_type = self.db_basic.type.lower() + if db_type == DBType.SQLITE.value.lower(): + return sqlite.SQLite3(self.db_detail.dbname) + + if db_type == DBType.POSTGRESQL.value.lower(): + target_db_class = PostgreSQL + elif db_type == DBType.ORACLE.value.lower(): + target_db_class = Oracle + elif db_type == DBType.MYSQL.value.lower(): + target_db_class = MySQL + elif db_type == DBType.MSSQLSERVER.value.lower(): + target_db_class = MSSQLServer + else: + return None + + password = self.db_detail.password + if self.db_detail.hashed: + password = decrypt_pwd(password) + + db_instance = target_db_class(self.db_detail.host, self.db_detail.dbname, self.db_detail.username, password) + + # use custom port or default port + if self.db_detail.port: + db_instance.port = self.db_detail.port + + if self.db_detail.schema: + db_instance.schema = self.db_detail.schema + + return db_instance + + +def gen_data_source_of_universal_db(): + """ + create data source cfg object that point to our application universal db + :return: + """ + db_src = CfgDataSource() + db_detail = CfgDataSourceDB() + + db_src.type = DBType.SQLITE.name + db_src.db_detail = db_detail + + db_detail.dbname = dic_config[UNIVERSAL_DB_FILE] + + return db_src diff --git a/histview2/common/pydn/dblib/mssqlserver.py b/histview2/common/pydn/dblib/mssqlserver.py new file mode 100644 index 0000000..36adf0c --- /dev/null +++ b/histview2/common/pydn/dblib/mssqlserver.py @@ -0,0 +1,367 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Author: Masato Yasuda (2018/01/04) + +import traceback + +from dateutil import parser +from pymssql import connect as mssqlconnect + +from histview2.common.common_utils import strip_all_quote + + +# import pyodbc + + +class MSSQLServer: + + def __init__(self, host, dbname, username, password): + self.host = host + self.port = 1433 + self.dbname = dbname + self.username = username + self.password = password + self._schema = None + self._schema_withdot = '' + self.is_connected = False + self.connection = None + self._table_views = None + + @property + def schema(self): + return self._schema + + @schema.setter + def schema(self, value): + if value: + self._schema = value + self._schema_withdot = f'"{value}".' + else: + self._schema = None + self._schema_withdot = '' + + def dump(self): + print("===== DUMP RESULT =====") + print("DB Type: MS SQL Server") + print("self.host: " + self.host) + print("self.port: " + str(self.port)) + print("self.dbname: " + self.dbname) + print("self.username: " + self.username) + print("self.schema: " + str(self.schema or '')) + print("self.is_connected: ", self.is_connected) + print("=======================") + + def connect(self): + # dsn = "Driver={{ODBC Driver 17 for SQL Server}};Server={0:s};".format(self.host) + # dsn += "Database={0:s};".format(self.dbname) + # dsn += "PORT={0:d};".format(self.port) + # dsn += "ClientCharset=UTF-8;" + # dsn += "UID={0:s};".format(self.username) + # dsn += "PWD={0:s}".format(self.password) + # dsn += ';Trusted_Connection=No;' + try: + # self.connection = pyodbc.connect(dsn) + self.connection = mssqlconnect(self.host, self.username, self.password, self.dbname, + port=self.port, login_timeout=3) + + # if alredy use default schema , set to None to avoid replace schema + self.is_connected = True + if self.schema: + if self.schema == self.get_default_schema(): + self.schema = None + + if self.schema: + schemas = self.get_all_schema() + self.is_connected = self._schema in schemas + + return self.connection + except: + print("Cannot connect to db") + print('>>> traceback <<<') + traceback.print_exc() + print('>>> end of traceback <<<') + return False + + def disconnect(self): + if not self._check_connection(): + return False + self.connection.close() + self.is_connected = False + + def get_default_schema(self): + if not self._check_connection(): + return None + + cur = self.connection.cursor() + cur.execute('select SCHEMA_NAME()') + rows = cur.fetchall() + cur.close() + return rows[0][0] + + def get_all_schema(self): + cur = self.connection.cursor() + cur.execute('select schema_name from information_schema.schemata') + rows = cur.fetchall() + rows = [col[0] for col in rows] + print('rows', rows) + cur.close() + return rows + + # As of now, create_table, drop_table, and intert_table aren't added + # because we want to use tables of MS SQL Server as "Read-only" + def create_table(self, tblname, valtypes): + if not self._check_connection(): + return False + sql = f'create table {self._schema_withdot}{tblname} (' + + for idx, val in enumerate(valtypes): + if idx > 0: + sql += "," + sql += val["name"] + " " + val["type"] + sql += ")" + print(sql) + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print(tblname + " created!") + + # テーブル名の配列を取得 + def list_tables(self): + if not self._check_connection(): + return False + # Only list tables of default schema (default schema name can be got by "SCHEMA_NAME()") + schema = f"'{self._schema}'" if self._schema else 'SCHEMA_NAME()' + sql = f"select name from sys.objects where type='U' and SCHEMA_NAME(schema_id)={schema}" + print('sql', sql) + cur = self.connection.cursor() + cur.execute(sql) + results = [] + rows = cur.fetchall() + for row in rows: + results.append(row[0]) + cur.close() + return results + + def list_tables_and_views(self): + if not self._check_connection(): + return False + # Only list tables of default schema (default schema name can be got by "SCHEMA_NAME()") + schema = f"'{self._schema}'" if self._schema else 'SCHEMA_NAME()' + sql = f"select name from sys.objects where SCHEMA_NAME(schema_id)={schema} and (type='U' or type='V')" + print('sql', sql) + cur = self.connection.cursor() + cur.execute(sql) + results = [] + rows = cur.fetchall() + for row in rows: + results.append(row[0]) + cur.close() + return results + + def drop_table(self, tblname): + if not self._check_connection(): + return False + sql = f'drop table if exists {self._schema_withdot}{tblname}' + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print(tblname + " dropped!") + + # テーブルのカラムのタイプを辞書の配列として返す(元々はtbl_get_valtypes関数) + # columns: + # name => カラム名, + # type => カラムタイプ + def list_table_columns(self, tblname): + if not self._check_connection(): + return False + + schema = f"'{self._schema}'" if self._schema else 'SCHEMA_NAME()' + sql = f"""select o.name table_name,c.name column_name,type_name(c.user_type_id) column_type + from sys.columns c + inner join sys.objects o on c.object_id = o.object_id + where SCHEMA_NAME(o.schema_id)={schema} and o.name=N'{tblname}'""" + print('sql', sql) + + cur = self.connection.cursor() + cur.execute(sql) + rows = cur.fetchall() + results = [] + for row in rows: + results.append({ + "name": row[1], + "type": row[2] + }) + cur.close() + return results + + def get_data_type_by_colname(self, tbl, col_name): + col_name = strip_all_quote(col_name) + cols = self.list_table_columns(tbl) + data_type = [col['type'] for col in cols if col['name'] == col_name] + return data_type[0] if data_type else None + + # list_table_columnsのうちcolumn nameだけ必要な場合(元はtbl_get_colnames) + def list_table_colnames(self, tblname): + if not self._check_connection(): + return False + columns = self.list_table_columns(tblname) + colnames = [] + for column in columns: + colnames.append(column["name"]) + return colnames + + def insert_table_records(self, tblname, names, values, add_comma_to_value=True): + if not self._check_connection(): + return False + + sql_template = f'insert into {self._schema_withdot}{tblname}' + + # Generate column names fields + sql_template += "(" + for idx, name in enumerate(names): + if idx > 0: + sql_template += "," + sql_template += name + sql_template += ") " + + # Generate values field + sql_template += "values " + sql = sql_template + end_index = len(values) - 1 + start_batch = True + for idx1, value in enumerate(values): + if not start_batch: + sql += "," + sql += "(" + for idx2, name in enumerate(names): + if idx2 > 0: + sql += "," + + if value[name] in ('', None): + sql += 'Null' + elif add_comma_to_value: + sql += "'" + str(value[name]) + "'" + else: + sql += str(value[name]) + + sql += ")" + + start_batch = False + + if idx1 > 0 and (idx1 % 999 == 0 or idx1 == end_index): + cur = self.connection.cursor() + sql = self._add_schema_to_sql(sql) + cur.execute(sql) + cur.close() + self.connection.commit() + sql = sql_template + start_batch = True + print("Dummy data was inserted to " + tblname) + + # SQLをこのまま実行 + # [以下、結果をDict形式で返す方法] + # https://stackoverflow.com/questions/16519385/ + # output-pyodbc-cursor-results-as-python-dictionary + # cols, rows = db1.run_sql("select * from tbl01") + # という形で呼び出す + def run_sql(self, sql, row_is_dict=True): + if not self._check_connection(): + return False + cur = self.connection.cursor() + sql = self._add_schema_to_sql(sql) + cur.execute(sql) + # cursor.descriptionはcolumnの配列 + # そこから配列名(column[0])を取り出して配列columnsに代入 + cols = [column[0] for column in cur.description] + # columnsは取得したカラム名、rowはcolumnsをKeyとして持つ辞書型の配列 + # rowは取得したカラムに対応する値が順番にrow[0], row[1], ...として入っている + # それをdictでまとめてrowsに取得 + if row_is_dict: + rows = [dict(zip(cols, row)) for row in cur.fetchall()] + else: + rows = cur.fetchall() + + cur.close() + return cols, rows + + def fetch_many(self, sql, size=10_000): + if not self._check_connection(): + return False + + cur = self.connection.cursor() + sql = self._add_schema_to_sql(sql) + cur.execute(sql) + cols = [column[0] for column in cur.description] + yield cols + while True: + rows = cur.fetchmany(size) + if not rows: + break + + yield rows + + cur.close() + + # 現時点ではSQLをそのまま実行するだけ + def select_table(self, sql): + return self.run_sql(sql) + + def get_timezone(self): + try: + rows = self.run_sql('SELECT SYSDATETIMEOFFSET() AS SYSDATETIME') + sys_dt_str = rows[1][0]['SYSDATETIME'] + sys_datetime = parser.parse(sys_dt_str) + return sys_datetime.tzinfo + except: + return None + + # private functions + def _check_connection(self): + if self.is_connected: + return True + # 接続していないなら + print("Connection is not Initialized. Please run connect() to connect to DB") + return False + + def execute_sql(self, sql): + """ For executing any query requires commit action + :param sql: SQL to be executed + :return: Execution result + """ + if not self._check_connection(): + return False + + cur = self.connection.cursor() + sql = self._add_schema_to_sql(sql) + res = cur.execute(sql) + cur.close() + self.connection.commit() + + return res + + def _add_schema_to_sql(self, sql): + """ + add schema to sql + """ + if not self._schema: + return sql + + if not self._table_views: + self._table_views = self.list_tables_and_views() + + print('tables', self._table_views) + + for table in self._table_views: + sql = sql.replace(f'"{table}"', f'{self._schema_withdot}"{table}"') + + return sql + + def is_timezone_hold_column(self, tbl, col): + data_type = self.get_data_type_by_colname(tbl, col) + + if data_type and 'DATETIMEOFFSET' in data_type.upper(): + return True + + return False diff --git a/histview2/common/pydn/dblib/mysql.py b/histview2/common/pydn/dblib/mysql.py new file mode 100644 index 0000000..33ed99c --- /dev/null +++ b/histview2/common/pydn/dblib/mysql.py @@ -0,0 +1,286 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Author: Masato Yasuda (2018/01/04) + +import logging +import traceback + +import pymysql.cursors + +from histview2.common.common_utils import strip_all_quote + +logger = logging.getLogger(__name__) + + +class MySQL: + + def __init__(self, host, dbname, username, password): + self.host = host + self.port = 3306 + self.dbname = dbname + # postgresqlと違い、mysqlにはdbとschemaは同義 + # postgresqlはdbの下にschemaという概念がある。 + # http://d.hatena.ne.jp/sheeg/20070906/1189083744 + self.username = username + self.password = password + self.is_connected = False + self.connection = None + + def dump(self): + print("===== DUMP RESULT =====") + print("DB Type: MySQL") + print("self.host: " + self.host) + print("self.port: " + str(self.port)) + print("self.dbname: " + self.dbname) + print("self.username: " + self.username) + print("self.is_connected: ", self.is_connected) + print("=======================") + + def connect(self): + try: + self.connection = pymysql.connect(host=self.host, + user=self.username, + password=self.password, + db=self.dbname, + port=self.port, + charset='utf8') # , + # cursorclass=pymysql.cursors.DictCursor) + self.is_connected = True + return self.connection + except: + print("Cannot connect to db") + print('>>> traceback <<<') + traceback.print_exc() + print('>>> end of traceback <<<') + return False + + def disconnect(self): + if not self._check_connection(): + return False + self.connection.close() + self.is_connected = False + + def create_table(self, tblname, valtypes): + if not self._check_connection(): + return False + + tblname = tblname.strip('\"') + sql = "create table {0:s}(".format(tblname) + for idx, val in enumerate(valtypes): + if idx > 0: + sql += "," + + sql += val["name"] + " " + val["type"] + sql += ")" + print(sql) + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print(tblname + " created!") + + # 作成済みのテーブルを配列として返す + def list_tables(self): + if not self._check_connection(): + return False + + sql = "show tables" + cur = self.connection.cursor() + cur.execute(sql) + results = [] + for row in cur.fetchall(): + results.append(row[0]) + return results + + def list_tables_and_views(self): + if not self._check_connection(): + return False + # Only list tables of default schema (default schema name can be got by "SCHEMA_NAME()") + sql = "select table_name from information_schema.tables " + sql += "where table_schema = '{0:s}'".format(self.dbname) + cur = self.connection.cursor() + cur.execute(sql) + results = [] + rows = cur.fetchall() + for row in rows: + results.append(row[0]) + cur.close() + return results + + def drop_table(self, tblname): + if not self._check_connection(): + False + + sql = "drop table if exists " + tblname + sql = sql.replace('\"', '`') + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print(tblname + " dropped!") + + # テーブルのカラムのタイプを辞書の配列として返す + # columns: + # name => カラム名, + # type => カラムタイプ + def list_table_columns(self, tblname): + if not self._check_connection(): + return False + sql = "show columns from " + tblname + sql = sql.replace('\"', '`') + cur = self.connection.cursor() + cur.execute(sql) + rows = cur.fetchall() + results = [] + for row in rows: + results.append({ + "name": row[0], + "type": row[1] + }) + return results + + def get_data_type_by_colname(self, tbl, col_name): + col_name = strip_all_quote(col_name) + cols = self.list_table_columns(tbl) + data_type = [col['type'] for col in cols if col['name'] == col_name] + return data_type[0] if data_type else None + + # list_table_columnsのうちcolumn nameだけ必要な場合 + def list_table_colnames(self, tblname): + if not self._check_connection(): + return False + columns = self.list_table_columns(tblname) + colnames = [] + for column in columns: + colnames.append(column["name"]) + return colnames + + def insert_table_records(self, tblname, names, values, add_comma_to_value=True): + if not self._check_connection(): + return False + + sql = "insert into {0:s}".format(tblname) + + # Generate column names field + sql += "(" + for idx, name in enumerate(names): + if idx > 0: + sql += "," + sql += name + sql += ") " + + # Generate values field + sql += "values " + for idx1, value in enumerate(values): + if idx1 > 0: + sql += "," + sql += "(" + for idx2, name in enumerate(names): + if idx2 > 0: + sql += "," + + if value[name] in ('', None): + sql += 'Null' + elif add_comma_to_value: + sql += "'" + str(value[name]) + "'" + else: + sql += str(value[name]) + + sql += ")" + + sql = sql.replace('\"', '`') + # print(sql) + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print("Dummy data was inserted to {}!".format(tblname)) + + # SQLをそのまま実行。 + # cols, rows = db1.run_sql("select * from tbl01") + # という形で呼び出す + def run_sql(self, sql, row_is_dict=True): + if not self._check_connection(): + return False + cur = self.connection.cursor() + sql = sql.replace('\"', '`') + cur.execute(sql) + # cursor.descriptionはcolumnの配列 + # そこから配列名(column[0])を取り出して配列columnsに代入 + cols = [column[0] for column in cur.description] + # columnsは取得したカラム名、rowはcolumnsをKeyとして持つ辞書型の配列 + # rowは取得したカラムに対応する値が順番にrow[0], row[1], ...として入っている + # それをdictでまとめてrowsに取得 + if row_is_dict: + rows = [dict(zip(cols, row)) for row in cur.fetchall()] + else: + rows = cur.fetchall() + + cur.close() + return cols, rows + + def fetch_many(self, sql, size=10_000): + if not self._check_connection(): + return False + + cur = self.connection.cursor() + sql = sql.replace('\"', '`') + cur.execute(sql) + cols = [column[0] for column in cur.description] + yield cols + while True: + rows = cur.fetchmany(size) + if not rows: + break + + yield rows + + cur.close() + + # 現時点ではSQLをそのまま実行するだけ + def select_table(self, sql): + return self.run_sql(sql) + + def get_timezone(self): + try: + # rows = self.run_sql('''SELECT @@global.time_zone as server_tz, @@session.time_zone as session_tz''') + _, rows = self.run_sql('SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP) TZOFFSET') + tz_offset = str(rows[0]['TZOFFSET']) + return tz_offset + + except Exception as e: + print(e) + + return None + + def execute_sql(self, sql): + """ For executing any query requires commit action + :param sql: SQL to be executed + :return: Execution result + """ + if not self._check_connection(): + return False + + cur = self.connection.cursor() + # print(sql) + sql = sql.replace('\"', '`') + print(sql) + res = cur.execute(sql) + cur.close() + self.connection.commit() + + return res + + # private functions + + def _check_connection(self): + if self.is_connected: + return True + # 接続していないなら + print("Connection is not Initialized. Please run connect() to connect to DB") + return False + + def is_timezone_hold_column(self, tbl, col): + # data_type = self.get_data_type_by_colname(tbl, col) + # return True if data_type in [] else False + return False diff --git a/histview2/common/pydn/dblib/oracle.py b/histview2/common/pydn/dblib/oracle.py new file mode 100644 index 0000000..84fe5c0 --- /dev/null +++ b/histview2/common/pydn/dblib/oracle.py @@ -0,0 +1,349 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Author: DuyLSK (2019/05/31) + +import logging +import re +import traceback + +import cx_Oracle + +from histview2.common.common_utils import strip_all_quote +from histview2.common.constants import ENCODING_UTF_8 + +logger = logging.getLogger(__name__) + + +class Oracle: + + def __init__(self, host, service_name, username, password): + self.host = host + self.port = 1521 + self.dbname = service_name + self.username = username + self.password = password + self.is_connected = False + + def dump(self): + print("===== DUMP RESULT =====") + print("DB Type: Oracle") + print("self.host: " + self.host) + print("self.port: " + str(self.port)) + print("self.dbname: " + self.dbname) + print("self.username: " + self.username) + print("self.is_connected: ", self.is_connected) + print("=======================") + + def connect(self): + dsn = cx_Oracle.makedsn(self.host, self.port, + service_name=self.dbname) + try: + self.connection = cx_Oracle.connect( + user=self.username, password=self.password, dsn=dsn, encoding=ENCODING_UTF_8, nencoding=ENCODING_UTF_8) + self.is_connected = True + return self.connection + except: + print("Cannot connect to db") + print('>>> traceback <<<') + traceback.print_exc() + print('>>> end of traceback <<<') + return False + + def disconnect(self): + if not self._check_connection(): + return False + self.connection.close() + self.is_connected = False + + # As of now, create_table, drop_table, and intert_table aren't added + # because we want to use tables of Oracle as "Read-only" + def create_table(self, tblname, valtypes): + if not self._check_connection(): + return False + sql = "create table {0:s}(".format(tblname) + for idx, val in enumerate(valtypes): + if idx > 0: + sql += "," + sql += val["name"] + " " + val["type"] + sql += ")" + print(sql) + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print(tblname + " created!") + + # テーブル名の配列を取得 + def list_tables(self): + if not self._check_connection(): + return False + sql = "select table_name from user_tables" + cur = self.connection.cursor() + cur.execute(sql) + results = [] + rows = cur.fetchall() + for row in rows: + results.append(row[0]) + cur.close() + return results + + def list_tables_and_views(self): + if not self._check_connection(): + return False + # Only list tables of default schema (default schema name can be got by "SCHEMA_NAME()") + sql = 'select view_name as name from user_views union all select table_name as name from user_tables' + cur = self.connection.cursor() + cur.execute(sql) + results = [] + rows = cur.fetchall() + for row in rows: + results.append(row[0]) + cur.close() + return results + + def drop_table(self, tblname): + if not self._check_connection(): + return False + sql = "drop table " + tblname + cur = self.connection.cursor() + try: + cur.execute(sql) + self.connection.commit() + print(tblname + " dropped!") + except Exception: + print(tblname + " is not found!") + finally: + cur.close() + + # テーブルのカラムのタイプを辞書の配列として返す(元々はtbl_get_valtypes関数) + # columns: + # name => カラム名, + # type => カラムタイプ + + def list_table_columns(self, tblname): + if not self._check_connection(): + return False + sql = "select column_id, column_name, data_type " + sql += "from user_tab_columns " + sql += "where table_name='{0:s}'".format(tblname) + cur = self.connection.cursor() + cur.execute(sql) + rows = cur.fetchall() + results = [] + for row in rows: + results.append({ + "name": row[1], + "type": row[2] + }) + cur.close() + return results + + # list_table_columnsのうちcolumn nameだけ必要な場合(元はtbl_get_colnames) + def list_table_colnames(self, tblname): + if not self._check_connection(): + return False + columns = self.list_table_columns(tblname) + colnames = [] + for column in columns: + colnames.append(column["name"]) + return colnames + + def divide_data_to_chunks(self, lst_data, n): + """ Devide a list to chunks of n elements. + + :param lst_data: data needs to be chunked + :param n: size of chunk + :return: an array of chunks with max size of n + """ + for i in range(0, len(lst_data), n): + yield lst_data[i:i + n] + + def insert_table_records(self, tblname, names, values, add_comma_to_value=True): + """ Insert one or many records to Oracle DB. + + Note: Please note that + - TIMESTAMP literal should be in '%Y-%m-%d %H:%M:%S' format. + - DATE literal should be in '%Y-%m-%d' format. + + :param tblname: Table name + :param names: Column names + :param values: records need to be inserted + :return: None + """ + + if not self._check_connection(): + return False + + # Generate column names field + col_name = "(" + for idx, name in enumerate(names): + if idx > 0: + col_name += "," + col_name += name + col_name += ") " + + # Generate values field + data_chunks = self.divide_data_to_chunks(values, 10_000) + + for chunk in data_chunks: + sql = "insert all " + for idx1, value in enumerate(chunk): + sql += " into {0:s} ".format(tblname) + col_name + " values (" + for idx2, name in enumerate(names): + if idx2 > 0: + sql += "," + + if value[name] in ('', None): + sql += 'Null' + elif add_comma_to_value: + sql += "'" + str(value[name]) + "'" + else: + sql += str(value[name]) + + sql += ")" + sql += " SELECT 1 FROM dual" + cur = self._create_cursor_with_date_time_format() + sql = Oracle.convert_sql(sql) + cur.execute(sql) + cur.close() + print("Inserted {} rows".format(len(chunk))) + self.connection.commit() + + print("Dummy data was inserted to {0:s}!".format(tblname)) + + # SQLをこのまま実行 + # [以下、結果をDict形式で返す方法] + # https://stackoverflow.com/questions/16519385/ + # output-pyodbc-cursor-results-as-python-dictionary + # cols, rows = db1.run_sql("select * from tbl01") + # という形で呼び出す + def run_sql(self, sql, row_is_dict=True): + if not self._check_connection(): + return False + print(sql) + cur = self._create_cursor_with_date_time_format() + sql = Oracle.convert_sql(sql) + + cursor = cur.execute(sql) + # cursor.descriptionはcolumnの配列 + # そこから配列名(column[0])を取り出して配列columnsに代入 + cols = [column[0] for column in cursor.description] + # columnsは取得したカラム名、rowはcolumnsをKeyとして持つ辞書型の配列 + # rowは取得したカラムに対応する値が順番にrow[0], row[1], ...として入っている + # それをdictでまとめてrowsに取得 + if row_is_dict: + rows = [dict(zip(cols, row)) for row in cur.fetchall()] + else: + rows = cur.fetchall() + + cursor.close() + return cols, rows + + def fetch_many(self, sql, size=10_000): + if not self._check_connection(): + return False + + cur = self._create_cursor_with_date_time_format() + sql = Oracle.convert_sql(sql) + + cursor = cur.execute(sql) + cols = [column[0] for column in cursor.description] + yield cols + while True: + rows = cur.fetchmany(size) + if not rows: + break + + yield rows + + cursor.close() + + def execute_sql(self, sql): + """ For executing any query requires commit action + :param sql: SQL to be executed + :return: Execution result + """ + if not self._check_connection(): + return False + + cur = self._create_cursor_with_date_time_format() + sql = Oracle.convert_sql(sql) + print(sql) + try: + cur.execute(sql) + self.connection.commit() + except Exception: + return False + finally: + cur.close() + return True + + # 現時点ではSQLをそのまま実行するだけ + def select_table(self, sql): + return self.run_sql(sql) + + def get_timezone(self): + try: + _, rows = self.run_sql('SELECT DBTIMEZONE AS TZOFFSET FROM DUAL') + return str(rows[0]['TZOFFSET']) + + except Exception as e: + print(e) + + return None + + # private functions + def _check_connection(self): + if self.is_connected: + return True + # 接続していないなら + print("Connection is not Initialized. Please run connect() to connect to DB") + return False + + def _create_cursor_with_date_time_format(self): + if not self.is_connected: + return None + cur = self.connection.cursor() + try: + cur.execute("alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'") + cur.execute("alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS.FF3'") + except: + pass + + return cur + + @staticmethod + def convert_sql(sql): + """convert filter datetime before execute sql + + Arguments: + sql {[type]} -- [description] + + Returns: + [type] -- [description] + """ + timestamp_frm = "'YYYY-MM-DD HH24:MI:SS.FF3'" + timestamp_z_frm = "'YYYY-MM-DD HH24:MI:SS.FF3TZR'" + timestamp_tz_frm = "'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM'" + regex_str = r"('\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3}')" + regex_z_str = r"('\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3}Z')" + regex_tz_str = r"('\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3}\s[+-]?\d{1,2}:\d{2}')" + new_sql = re.sub(regex_str, f'TO_TIMESTAMP(\\1,{timestamp_frm})', sql) + new_sql = re.sub(regex_z_str, f'TO_TIMESTAMP_TZ(\\1,{timestamp_z_frm})', new_sql) + new_sql = re.sub(regex_tz_str, f'TO_TIMESTAMP_TZ(\\1,{timestamp_tz_frm})', new_sql) + # new_sql = re.sub(regex_str, f'TO_CHAR(TO_TIMESTAMP(\\1,{timestamp_frm}),{timestamp_frm})', sql) + return new_sql + + def get_data_type_by_colname(self, tbl, col_name): + col_name = strip_all_quote(col_name) + cols = self.list_table_columns(tbl) + data_type = [col['type'] for col in cols if col['name'] == col_name] + return data_type[0] if data_type else None + + def is_timezone_hold_column(self, tbl, col): + data_type = self.get_data_type_by_colname(tbl, col) + + if 'TIME ZONE' in data_type.upper(): + return True + + return False diff --git a/histview2/common/pydn/dblib/postgresql.py b/histview2/common/pydn/dblib/postgresql.py new file mode 100644 index 0000000..58e1ffc --- /dev/null +++ b/histview2/common/pydn/dblib/postgresql.py @@ -0,0 +1,330 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Author: Masato Yasuda (2018/01/04) + +import logging +import traceback + +import psycopg2 +import psycopg2.extras + +from histview2.common.common_utils import strip_all_quote + +logger = logging.getLogger(__name__) + + +class PostgreSQL: + + def __init__(self, host, dbname, username, password): + self.host = host + self.port = 5432 + self.dbname = dbname + # postgresqlはdbの下にschemaという概念がある。 + # http://d.hatena.ne.jp/sheeg/20070906/1189083744 + self.schema = None + self.username = username + self.password = password + self.is_connected = False + self.connection = None + + def dump(self): + print("===== DUMP RESULT =====") + print("DB Type: PostgreSQL") + print("self.host: " + self.host) + print("self.port: " + str(self.port)) + print("self.dbname: " + self.dbname) + print("self.username: " + self.username) + print("self.is_connected: ", self.is_connected) + print("=======================") + + def connect(self): + dsn = "host={0:s} ".format(self.host) + dsn += "port={0:d} ".format(self.port) + dsn += "dbname={0:s} ".format(self.dbname) + dsn += "user={0:s} ".format(self.username) + dsn += "password={0:s}".format(self.password) + try: + self.connection = psycopg2.connect(dsn) + cur = self.connection.cursor() + self.is_connected = True + + if self.schema: + cur.execute( + "SELECT schema_name FROM information_schema.schemata WHERE schema_name = '{}';".format(self.schema)) + if cur.rowcount: + cur.execute("SET search_path TO {0:s}".format(self.schema)) + else: + print("Schema is not exists!!!") + self.disconnect() + else: + # Get current schema + cur.execute("SELECT current_schema();") + default_schema = cur.fetchone() + # Save default schema as constant, use to list current schema's tables + self.schema = default_schema[0] + cur.close() + return self.connection + except: + print("Cannot connect to db") + print('>>> traceback <<<') + traceback.print_exc() + print('>>> end of traceback <<<') + return False + + def disconnect(self): + if not self._check_connection(): + return False + # https://stackoverflow.com/questions/1281875/ + # making-sure-that-psycopg2-database-connection-alive + self.connection.close() + self.is_connected = False + + def create_table(self, tblname, colnames): + if not self._check_connection(): + return False + sql = "create table {0:s}(".format(tblname) + for idx, val in enumerate(colnames): + if idx > 0: + sql += "," + sql += val["name"] + " " + val["type"] + sql += ")" + print(sql) + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print(tblname + " created!") + + # テーブル名を配列として返す + def list_tables(self): + if not self._check_connection(): + return False + sql = "select table_name from information_schema.tables " + sql += "where table_type = 'BASE TABLE' and table_schema = '{0:s}'".format(self.schema) + cur = self.connection.cursor() + cur.execute(sql) + # cursor.descriptionはcolumnの配列 + # そこから配列名(column[0])を取り出して配列columnsに代入 + cols = [column[0] for column in cur.description] + # columnsは取得したカラム名、rowはcolumnsをKeyとして持つ辞書型の配列 + # rowは取得したカラムに対応する値が順番にrow[0], row[1], ...として入っている + # それをdictでまとめてrowsに取得 + rows = [] + for row in cur.fetchall(): + rows.append(dict(zip(cols, row))) + cur.close() + # キーに"table_name"を持つ要素を配列として返す + return [row["table_name"] for row in rows] + + def list_tables_and_views(self): + if not self._check_connection(): + return False + sql = "select table_name from information_schema.tables " + sql += "where table_schema = '{0:s}'".format(self.schema) + cur = self.connection.cursor() + cur.execute(sql) + # cursor.descriptionはcolumnの配列 + # そこから配列名(column[0])を取り出して配列columnsに代入 + cols = [column[0] for column in cur.description] + # columnsは取得したカラム名、rowはcolumnsをKeyとして持つ辞書型の配列 + # rowは取得したカラムに対応する値が順番にrow[0], row[1], ...として入っている + # それをdictでまとめてrowsに取得 + rows = [] + for row in cur.fetchall(): + rows.append(dict(zip(cols, row))) + cur.close() + # キーに"table_name"を持つ要素を配列として返す + return [row["table_name"] for row in rows] + + def drop_table(self, tblname): + if not self._check_connection(): + return False + sql = "drop table if exists " + tblname + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print(tblname + " dropped!") + + # テーブルのカラムのタイプを辞書の配列として返す + # columns: + # name => カラム名, + # type => カラムタイプ + def list_table_columns(self, tblname): + + if not self._check_connection(): + return False + + columns = [] + sql = "select * from information_schema.columns " + sql += "where table_schema = '{0}' and table_name = '{1}'".format(self.schema, tblname) + cur = self.connection.cursor() + cur.execute(sql) + # cursor.descriptionはcolumnの配列 + # そこから配列名(column[0])を取り出して配列columnsに代入 + cols = [column[0] for column in cur.description] + # columnsは取得したカラム名、rowはcolumnsをKeyとして持つ辞書型の配列 + # rowは取得したカラムに対応する値が順番にrow[0], row[1], ...として入っている + # それをdictでまとめてrowsに取得 + rows = [] + for row in cur.fetchall(): + rows.append(dict(zip(cols, row))) + cur.close() + results = [] + + for row in rows: + results.append({ + "name": row["column_name"], + "type": row["data_type"] + }) + return results + + def get_data_type_by_colname(self, tbl, col_name): + col_name = strip_all_quote(col_name) + cols = self.list_table_columns(tbl) + data_type = [col['type'] for col in cols if col['name'] == col_name] + return data_type[0] if data_type else None + + # list_table_columnsのうちcolumn nameだけ必要な場合 + def list_table_colnames(self, tblname): + if not self._check_connection(): + return False + columns = self.list_table_columns(tblname) + colnames = [] + for column in columns: + colnames.append(column["name"]) + return colnames + + # 元はinsert_table関数 + def insert_table_records(self, tblname, names, values, add_comma_to_value=True): + if not self._check_connection(): + return False + + sql = "insert into {0:s}".format(tblname) + + # Generate column names fields + sql += "(" + for idx, name in enumerate(names): + if idx > 0: + sql += "," + sql += name + sql += ") " + + # Generate values field + if not values: + return False + + sql += "values " + for idx1, value in enumerate(values): + if idx1 > 0: + sql += "," + sql += "(" + for idx2, name in enumerate(names): + if idx2 > 0: + sql += "," + + if value[name] in ('', None): + sql += 'Null' + elif add_comma_to_value: + sql += "'" + str(value[name]) + "'" + else: + sql += str(value[name]) + + sql += ")" + + # print(sql) + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print("Dummy data was inserted to {}!".format(tblname)) + + # SQLをそのまま実行 + # colsとdict形式のrowsを返す + # cols, rows = db1.run_sql("select * from tbl01") + # という形で呼び出す + def run_sql(self, sql, row_is_dict=True): + if not self._check_connection(): + return False + cur = self.connection.cursor() + # https://stackoverflow.com/questions/10252247/ + # how-do-i-get-a-list-of-column-names-from-a-psycopg2-cursor + # カラム名がRenameされた場合も対応出来る形に処理を変更 + print(sql) + cur.execute(sql) + # cursor.descriptionはcolumnの配列 + # そこから配列名(column[0])を取り出して配列columnsに代入 + cols = [column[0] for column in cur.description] + # columnsは取得したカラム名、rowはcolumnsをKeyとして持つ辞書型の配列 + # rowは取得したカラムに対応する値が順番にrow[0], row[1], ...として入っている + # それをdictでまとめてrowsに取得 + if row_is_dict: + rows = [dict(zip(cols, row)) for row in cur.fetchall()] + else: + rows = cur.fetchall() + + cur.close() + return cols, rows + + def fetch_many(self, sql, size=10_000): + if not self._check_connection(): + return False + + cur = self.connection.cursor() + cur.execute(sql) + cols = [column[0] for column in cur.description] + yield cols + while True: + rows = cur.fetchmany(size) + if not rows: + break + + yield rows + + cur.close() + + def execute_sql(self, sql): + """ For executing any query requires commit action + :param sql: SQL to be executed + :return: Execution result + """ + if not self._check_connection(): + return False + + cur = self.connection.cursor() + # print(sql) + res = cur.execute(sql) + cur.close() + self.connection.commit() + + return res + + # 現時点ではSQLをそのまま実行するだけ + def select_table(self, sql): + return self.run_sql(sql) + + def get_timezone(self): + try: + _, rows = self.run_sql('show timezone') + tz_offset = str(rows[0]['TimeZone']) + return tz_offset + except Exception as e: + print(e) + return None + + # private functions + + def _check_connection(self): + if self.is_connected: + return True + # 接続していないなら + print("Connection is not Initialized. Please run connect() to connect to DB") + return False + + def is_timezone_hold_column(self, tbl, col): + data_type = self.get_data_type_by_colname(tbl, col) + + if 'WITH TIME ZONE' in data_type.upper(): + return True + + return False diff --git a/histview2/common/pydn/dblib/sqlite.py b/histview2/common/pydn/dblib/sqlite.py new file mode 100644 index 0000000..17edf81 --- /dev/null +++ b/histview2/common/pydn/dblib/sqlite.py @@ -0,0 +1,302 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Author: Masato Yasuda (2019/04/10) + +import logging +import os +import sqlite3 +import traceback + +from dateutil import tz + +from histview2.common.common_utils import strip_all_quote + +logger = logging.getLogger(__name__) + + +class SQLite3: + def __init__(self, dbname): + from histview2 import dic_config, SQLITE_CONFIG_DIR + self.dbname = os.path.join(dic_config[SQLITE_CONFIG_DIR], dbname or '') # sqliteで言うdbnameはファイル名/ + self.is_connected = False + self.connection = None + self.cursor = None + + def dump(self): + print("===== DUMP RESULT =====") + print("DB Type: SQLite3") + print("self.dbname: {}".format(self.dbname)) + print("self.is_connected: {}".format(self.is_connected)) + print("=======================") + + def connect(self): + try: + self.connection = sqlite3.connect(self.dbname) + self.is_connected = True + self.cursor = self.connection.cursor() + return self.connection + except Exception: + print("Cannot connect to db") + print('>>> traceback <<<') + traceback.print_exc() + print('>>> end of traceback <<<') + return False + + def is_sqlite(self): + """Check if dbname is actually a SQLite database file. + + Returns: + boolean -- True if the file is SQLite database file, False otherwise. + """ + if os.path.isfile(self.dbname): + if os.path.getsize(self.dbname) > 100: + with open(self.dbname, 'r', encoding="ISO-8859-1") as f: + header = f.read(100) + if header.startswith('SQLite format'): + return True + + def disconnect(self): + if not self._check_connection(): + return False + self.cursor.close() + self.connection.close() + self.is_connected = False + + def create_table(self, tblname, valtypes): + if not self._check_connection(): + return False + sql = "create table {0:s}(".format(tblname) + for idx, val in enumerate(valtypes): + if idx > 0: + sql += "," + + sql += val["name"] + " " + val["type"] + sql += ")" + print(sql) + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print(tblname + " created!") + + # 作成済みのテーブルを配列として返す + def list_tables(self): + if not self._check_connection(): + return False + + # https://monjudoh.hatenablog.com/entry/20090916/1253104594 + sql = "select name from sqlite_master where type = 'table'" + cur = self.connection.cursor() + + results = [] + for row in cur.execute(sql): + results.append(row[0]) + return results + + def list_tables_and_views(self): + if not self._check_connection(): + return False + + # https://monjudoh.hatenablog.com/entry/20090916/1253104594 + sql = "select name from sqlite_master where type = 'table' or type = 'view' " + cur = self.connection.cursor() + + results = [] + for row in cur.execute(sql): + results.append(row[0]) + return results + + def drop_table(self, tblname): + if not self._check_connection(): + return False + + sql = "drop table if exists " + tblname + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print(tblname + " dropped!") + + # テーブルのカラムのタイプを辞書の配列として返す + # columns: + # name => カラム名, + # type => カラムタイプ + def list_table_columns(self, tblname): + + if not self._check_connection(): + return False + # http://o.inchiki.jp/obbr/4 + sql = "PRAGMA TABLE_INFO({})".format(tblname) + cur = self.connection.cursor() + cur.execute(sql) + rows = cur.fetchall() + results = [] + + for row in rows: + results.append({ + "name": row[1], + "type": row[2] + }) + return results + + def get_data_type_by_colname(self, tbl, col_name): + col_name = strip_all_quote(col_name) + cols = self.list_table_columns(tbl) + data_type = [col['type'] for col in cols if col['name'] == col_name] + return data_type[0] if data_type else None + + # list_table_columnsのうちcolumn nameだけ必要な場合 + def list_table_colnames(self, tblname): + if not self._check_connection(): + return False + columns = self.list_table_columns(tblname) + colnames = [] + for column in columns: + colnames.append(column["name"]) + return colnames + + def insert_table_records(self, tblname, names, values): + if not self._check_connection(): + return False + + sql = "insert into {0:s}".format(tblname) + + # Generate column names field + sql += "(" + for idx, name in enumerate(names): + if idx > 0: + sql += "," + sql += name + sql += ") " + + # Generate values field + sql += "values " + for idx1, value in enumerate(values): + if idx1 > 0: + sql += "," + sql += "(" + for idx2, name in enumerate(names): + if idx2 > 0: + sql += "," + sql += "'" + str(value[name]) + "'" + sql += ")" + + # print(sql) + cur = self.connection.cursor() + cur.execute(sql) + cur.close() + self.connection.commit() + print("Dummy data was inserted to {}!".format(tblname)) + + # SQLをそのまま実行。 + # cols, rows = db1.run_sql("select * from tbl01") + # という形で呼び出す + def run_sql(self, sql, row_is_dict=True): + if not self._check_connection(): + return False + cur = self.connection.cursor() + cur.execute(sql) + # cursor.descriptionはcolumnの配列 + # そこから配列名(column[0])を取り出して配列columnsに代入 + cols = [column[0] for column in cur.description] + # columnsは取得したカラム名、rowはcolumnsをKeyとして持つ辞書型の配列 + # rowは取得したカラムに対応する値が順番にrow[0], row[1], ...として入っている + # それをdictでまとめてrowsに取得 + if row_is_dict: + rows = [dict(zip(cols, row)) for row in cur.fetchall()] + else: + rows = cur.fetchall() + + cur.close() + return (cols, rows) + + def fetch_many(self, sql, size=10_000): + if not self._check_connection(): + return False + + cur = self.connection.cursor() + cur.execute(sql) + cols = [column[0] for column in cur.description] + yield cols + while True: + rows = cur.fetchmany(size) + if not rows: + break + + yield rows + + cur.close() + + def execute_sql(self, sql): + """ For executing any query requires commit action + :param sql: SQL to be executed + :return: Execution result + """ + if not self._check_connection(): + return False + + cur = self.connection.cursor() + # print(sql) + res = cur.execute(sql) + cur.close() + self.connection.commit() + + return res + + def execute_sql_in_transaction(self, sql, rows): + """ For executing any query requires commit action + :param sql: SQL to be executed + :param rows: data + :return: Execution result + """ + self.cursor.executemany(sql, rows) + + # 現時点ではSQLをそのまま実行するだけ + def select_table(self, sql): + return self.run_sql(sql) + + def get_timezone(self): + try: + return tz.tzlocal() + except: + return None + + # private functions + + def _check_connection(self): + if self.is_connected: + return True + # 接続していないなら + print("Connection is not Initialized. Please run connect() to connect to DB") + return False + + def is_timezone_hold_column(self, tbl, col): + # data_type = self.get_data_type_by_colname(tbl, col) + # return True if data_type in [] else False + return False + + def bulk_insert(self, tblname, columns, rows): + """ + insert bulk data to db ( best performance ) + :param tblname: + :param columns: + :param rows: + :return: + """ + if not self._check_connection(): + return False + + sql = self.gen_bulk_insert_sql(tblname, columns) + cur = self.connection.cursor() + cur.executemany(sql, rows) + cur.close() + + return True + + @staticmethod + def gen_bulk_insert_sql(tblname, columns): + cols = ','.join(columns) + params = ','.join(['?'] * len(columns)) + sql = f'INSERT INTO {tblname} ({cols}) VALUES ({params})' + + return sql diff --git a/histview2/common/pysize.py b/histview2/common/pysize.py new file mode 100644 index 0000000..9aec035 --- /dev/null +++ b/histview2/common/pysize.py @@ -0,0 +1,35 @@ +import sys +import inspect + + +def get_size(obj, seen=None): + """Recursively finds size of objects in bytes""" + size = sys.getsizeof(obj) + if seen is None: + seen = set() + obj_id = id(obj) + if obj_id in seen: + return 0 + # Important mark as seen *before* entering recursion to gracefully handle + # self-referential objects + seen.add(obj_id) + try: + if hasattr(obj, '__dict__'): + for cls in obj.__class__.__mro__: + if '__dict__' in cls.__dict__: + d = cls.__dict__['__dict__'] + if inspect.isgetsetdescriptor(d) or inspect.ismemberdescriptor(d): + size += get_size(obj.__dict__, seen) + break + if isinstance(obj, dict): + size += sum((get_size(v, seen) for v in obj.values())) + size += sum((get_size(k, seen) for k in obj.keys())) + elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes, bytearray)): + size += sum((get_size(i, seen) for i in obj)) + + if hasattr(obj, '__slots__'): # can have __slots__ with __dict__ + size += sum(get_size(getattr(obj, s), seen) for s in obj.__slots__ if hasattr(obj, s)) + + return size + except: + return 0 \ No newline at end of file diff --git a/histview2/common/scheduler.py b/histview2/common/scheduler.py new file mode 100644 index 0000000..f4f5cc1 --- /dev/null +++ b/histview2/common/scheduler.py @@ -0,0 +1,200 @@ +from datetime import datetime, timedelta +from enum import Enum +from functools import wraps +from threading import Lock + +from apscheduler.triggers import date +from pytz import utc + +from histview2 import scheduler, close_sessions +from histview2.common.logger import log_execution_time + +# RESCHEDULE_SECONDS +RESCHEDULE_SECONDS = 5 + +# running jobs dict +dic_running_job = {} + +# multi thread lock +lock = Lock() + + +class JobType(Enum): + def __str__(self): + return str(self.name) + + # 1,2 is priority + DEL_PROCESS = 1 + CSV_IMPORT = 2 + FACTORY_IMPORT = 3 + GEN_GLOBAL = 4 + CLEAN_DATA = 5 + FACTORY_PAST_IMPORT = 6 + IDLE_MORNITORING = 7 + SHUTDOWN_APP = 8 + BACKUP_DATABASE = 9 + + +# check parallel running +# ex: (JobType.CSV_IMPORT, JobType.GEN_GLOBAL): False +# csv import and gen global can not run in the same time +# False: Don't need to check key between 2 job (key: job parameter). +# True: Need to check key between 2 job , if key is not the same , they can run parallel +CONFLICT_PAIR = { + (JobType.DEL_PROCESS.name, JobType.DEL_PROCESS.name), + (JobType.DEL_PROCESS.name, JobType.CSV_IMPORT.name), + (JobType.DEL_PROCESS.name, JobType.FACTORY_IMPORT.name), + (JobType.DEL_PROCESS.name, JobType.GEN_GLOBAL.name), + (JobType.DEL_PROCESS.name, JobType.FACTORY_PAST_IMPORT.name), + + # (JobType.CSV_IMPORT.name, JobType.CSV_IMPORT.name), + # (JobType.CSV_IMPORT.name, JobType.FACTORY_IMPORT.name), + (JobType.CSV_IMPORT.name, JobType.GEN_GLOBAL.name), + # (JobType.CSV_IMPORT.name, JobType.FACTORY_PAST_IMPORT.name), + + # (JobType.FACTORY_IMPORT.name, JobType.FACTORY_IMPORT.name), + (JobType.FACTORY_IMPORT.name, JobType.GEN_GLOBAL.name), + # (JobType.FACTORY_IMPORT.name, JobType.FACTORY_PAST_IMPORT.name), + + (JobType.GEN_GLOBAL.name, JobType.GEN_GLOBAL.name), + (JobType.GEN_GLOBAL.name, JobType.FACTORY_PAST_IMPORT.name), + + # (JobType.FACTORY_PAST_IMPORT.name, JobType.FACTORY_PAST_IMPORT.name), +} + +# Jobs that change universal data +IMPORT_DATA_JOBS = [JobType.CSV_IMPORT.name, JobType.FACTORY_IMPORT.name, JobType.FACTORY_PAST_IMPORT.name] + + +@log_execution_time(logging_exception=True) +def scheduler_app_context(fn): + """ application context decorator for background task(scheduler) + + Arguments: + fn {function} -- [description] + + Returns: + [type] -- [description] + """ + + @wraps(fn) + def inner(*args, **kwargs): + global dic_running_job + print(f'--------CHECK_BEFORE_RUN---------') + job_id = kwargs.get('_job_id') + job_name = kwargs.get('_job_name') + + # check before run + with lock: + try: + scheduler.pause() + if scheduler_check_before_run(job_id, job_name, dic_running_job): + dic_running_job[job_id] = [job_name, datetime.utcnow()] + else: + reschedule_job(job_id, job_name, fn, args, kwargs) + return None + + finally: + scheduler.resume() + + flask_app = scheduler.app + with flask_app.app_context(): + print(f'--------{job_id} START---------') + + try: + result = fn(*args, **kwargs) + print(f'--------{job_id} END-----------') + except Exception as e: + raise e + finally: + dic_running_job.pop(job_id, None) + # rollback and close session to avoid database locked. + close_sessions() + + return result + + return inner + + +def scheduler_check_before_run(job_id, job_name, dic_running_job_param): + """check if job can run parallel with other jobs + """ + + print("job_id:", job_id) + print("job_name:", job_name) + print("RUNNING JOBS:", dic_running_job_param) + if dic_running_job_param.get(job_id): + print("The same job is running") + return False + + for running_job_name, *_ in dic_running_job_param.values(): + if (job_name, running_job_name) in CONFLICT_PAIR or (running_job_name, job_name) in CONFLICT_PAIR: + print(f"{job_name} job can not run parallel with {running_job_name}") + return False + + return True + + +def reschedule_job(job_id, job_name, fn, args, kwargs): + """let the job wait about n minutes + + Arguments: + job_id {[type]} -- [description] + job_name {[type]} -- [description] + fn {function} -- [description] + args {[type]} -- [description] + kwargs {[type]} -- [description] + """ + + job = scheduler.get_job(job_id) + run_time = datetime.now().astimezone(utc) + timedelta(seconds=RESCHEDULE_SECONDS) + if job: + job.next_run_time = run_time + if job.trigger: + if type(job.trigger).__name__ == 'DateTrigger': + job.trigger.run_date = run_time + elif type(job.trigger).__name__ == 'IntervalTrigger': + job.trigger.start_date = run_time + job.reschedule(trigger=job.trigger) + else: + # for non-interval job , there is no job in scheduler anymore + # so we must add new job to scheduler. + job = scheduler.add_job(job_id, fn, name=job_name, + trigger=date.DateTrigger(run_date=run_time, timezone=utc), + args=args, kwargs=kwargs) + print(job) + + +@log_execution_time() +def remove_jobs(target_job_names, proc_id=None): + """remove all interval jobs + + Keyword Arguments: + target_job_names {[type]} -- [description] (default: {None}) + """ + with lock: + try: + scheduler.pause() + jobs = scheduler.get_jobs() + for job in jobs: + if job.name not in JobType.__members__ or JobType[job.name] not in target_job_names: + continue + + if proc_id: + if job.id == f'{job.name}_{proc_id}': + job.remove() + else: + job.remove() + + finally: + scheduler.resume() + + +def add_job_to_scheduler(job_id, job_name, trigger, import_func, run_now, dic_import_param): + if run_now: + scheduler.add_job(job_id, import_func, name=job_name, replace_existing=True, trigger=trigger, + next_run_time=datetime.now().astimezone(utc), + kwargs=dic_import_param) + else: + scheduler.add_job(job_id, import_func, name=job_name, replace_existing=True, trigger=trigger, + kwargs=dic_import_param) diff --git a/histview2/common/services/__init__.py b/histview2/common/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/common/services/ana_inf_data.py b/histview2/common/services/ana_inf_data.py new file mode 100644 index 0000000..89505bb --- /dev/null +++ b/histview2/common/services/ana_inf_data.py @@ -0,0 +1,210 @@ +#!/usr/bin/python3 +import logging +from itertools import groupby + +import numpy as np +import pandas as pd +from scipy.stats import gaussian_kde, iqr + +from histview2.common.constants import * + +logger = logging.getLogger(__name__) + + +def all_equal(iterable): + g = groupby(iterable) + return next(g, True) and not next(g, False) + + +def calculate_kde_for_ridgeline(data, grid_points, height=1, use_range=False, use_hist_counts=False): + """ + Calculate KDE + Histogram bins + Histogram labels for an input array. + :param data: input array + :param height: high of the kde line + :param grid_points: (tuple) special of xmin, xmax, x + :param use_range: use range to show the y-axis + :param use_hist_counts: use hist_counts to show histogram by line, do not use for RLP + :return: KDE + Histogram bins + Histogram labels with 100 data points. + """ + hist_counts = [] + + data_np = pd.Series(data) + data_np = data_np[data_np.notnull()].convert_dtypes() + if not len(data_np): + return gen_kde_result() + + try: + data_np = data_np[np.isfinite(data_np)] + except Exception: + pass + + data = data_np.tolist() + if not data: # empty + return gen_kde_result() + else: + try: + # grid points + (xmin, xmax, x) = grid_points + histogram = np.histogram(data, bins=x, range=(xmin, xmax)) if use_range else np.histogram(data, bins=x) + + if all_equal(data): + # use histogram value from numpy + g_kde_values = histogram[0] + else: + g_kde = gen_gaussian_kde_1d_same_as_r(data) + g_kde_values = g_kde(x) * height + + if use_hist_counts: + hist_counts = histogram[0].tolist() + return gen_kde_result(g_kde_values.tolist(), x.tolist(), hist_counts) + except Exception: + return gen_kde_result() + + +def gen_kde_result(kde=(), hist_labels=(), hist_counts=()): + return { + 'kde': kde, + 'hist_labels': hist_labels, + 'hist_counts': hist_counts, + } + + +def calculate_kde_trace_data(plotdata, bins=128, height=1, full_array_y=None): + """ + :param plotdata: + :param bins: + :param height: + :param full_array_y + :param gen_chart_js_hist: + :return: + """ + if full_array_y is None: + data = plotdata[ARRAY_Y] + else: + data = full_array_y + + hist_counts = [] + # remove inf, nan in data + data = pd.Series(data) + data = data[data.notnull()] + if not len(data): + kde = gen_kde_result() + return [kde] * 5 + + data = data[np.isfinite(data)] + if full_array_y and len(data) > 10_000: + sample_data = np.random.choice(data, size=10_000).tolist() + else: + sample_data = data.tolist() + + if not sample_data: + kde = gen_kde_result() + return [kde] * 5 + + # only_one_point = False + # if np.unique(sample_data).size == 1: + # only_one_point = True + + kde_list = [] + scales = [plotdata.get(scale) for scale in (SCALE_SETTING, SCALE_COMMON, SCALE_THRESHOLD, SCALE_AUTO, SCALE_FULL)] + for scale_info in scales: + if not scale_info: + kde = gen_kde_result() + kde_list.append(kde) + continue + + y_min = scale_info[Y_MIN] + y_max = scale_info[Y_MAX] + try: + # grid points + histogram = np.histogram(data, bins=bins, range=(y_min, y_max)) + hist_counts, hist_labels = histogram + + # if only_one_point: + # g_kde_values = gen_kde_from_singular_matrix(sample_data, range=(y_min, y_max)) + # else: + # g_kde = gaussian_kde(dataset=sample_data) + # g_kde_values = g_kde(hist_labels) * height + + g_kde = gen_gaussian_kde_1d_same_as_r(sample_data) + g_kde_values = g_kde(hist_labels) * height + kde = gen_kde_result(g_kde_values.tolist(), hist_labels, hist_counts) + except Exception: + kde = gen_kde_result() + + kde_list.append(kde) + + return kde_list + + +# def gen_kde_from_singular_matrix(singular_matrix, range=(0, 1), bins=128, normalized=True): +# x = np.linspace(range[0], range[1], bins) +# kde = np.zeros(bins) +# for val in singular_matrix: +# kde += norm.pdf(x, loc=val, scale=1) +# +# # normalized the KDE +# if normalized: +# kde /= integrate.simps(kde, x) +# return kde + + +def is_numeric(n): + try: + float(n) + except Exception: + return False + + return True + + +def get_bound(plotdata): + bounds = [] + for _, ridgeline in enumerate(plotdata): + array_x = ridgeline.get(ARRAY_X) + data = [e for e in array_x if is_numeric(e)] + if data: + # remove inf, nan in data + data_np = np.array(data) + data = data_np[np.isfinite(data_np)] + + if data.size: + bounds.append(data.mean() - 2.5 * data.std()) + bounds.append(data.mean() + 2.5 * data.std()) + if bounds: + bmin = np.array(bounds).min() + bmax = np.array(bounds).max() + return [bmin, bmax] + + return [] + + +def get_grid_points(plotdata, bounds=None, bins=128, width=1): + mind = [] + maxd = [] + if bounds: + xmin, xmax = bounds + else: + for _, ridgeline in enumerate(plotdata): + array_x = ridgeline.get(ARRAY_X) + if array_x: + mind.append(min(array_x)) + maxd.append(max(array_x)) + + xmin = 0 + xmax = 0 + if len(mind) and len(maxd): + # default width=1 -> line scope is min/max +- 10% + xmin = mind - 0.1 * width * (maxd - mind) + xmax = maxd + 0.1 * width * (maxd - mind) + + x = np.linspace(xmin, xmax, bins) + return (xmin, xmax, x) + + +def gen_gaussian_kde_1d_same_as_r(x): + # x: 1D ndarray + bw_silverman = 1.06 * np.min([np.std(x), iqr(x) / 1.34]) * (len(x) ** (-0.2)) + factor = bw_silverman / (np.std(x, ddof=1)) + kde = gaussian_kde(x, bw_method=factor) + return kde diff --git a/histview2/common/services/csv_content.py b/histview2/common/services/csv_content.py new file mode 100644 index 0000000..33610d6 --- /dev/null +++ b/histview2/common/services/csv_content.py @@ -0,0 +1,183 @@ +# CSVコンテンツを生成するService +# read(dic_form, local_params)が外向けの関数 + +import csv +import decimal +import math + +# https://stackoverrun.com/ja/q/6869533 +import logging +import re +from datetime import datetime, date, time +from itertools import islice + +from histview2.api.efa.services.etl import detect_file_delimiter +from histview2.common.common_utils import detect_encoding +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.services.normalization import normalize_list + +logger = logging.getLogger(__name__) + + +def gen_csv_fname(export_type="csv"): + timestr = datetime.utcnow().strftime("%Y%m%d_%H%M%S.%f")[:-3] + csv_fname = "{0:s}_out.{1:s}".format(timestr, export_type) + return csv_fname + + +def read_data(f_name, headers=None, skip_head=None, end_row=None, delimiter=',', do_normalize=True): + if do_normalize: + normalize_func = normalize_list + else: + normalize_func = lambda x: x + + encoding = detect_encoding(f_name) + if not delimiter: + delimiter = detect_file_delimiter(f_name, ',') + + with open(f_name, "r", encoding=encoding) as f: + rows = csv.reader(f, delimiter=delimiter) + + # skip tail + if end_row: + rows = islice(rows, end_row + 1) + + # skip head + if skip_head: + for _ in range(skip_head): + next(rows) + + # use specify header( may be get from yaml config) + csv_headers = next(rows) + if headers: + yield normalize_func(headers) + else: + yield normalize_func(csv_headers) + + # send data + for row in rows: + yield normalize_func(row) + + +def gen_data_types(data): + """ + check datatype of a list of columns + :param data: + :return: + """ + data_types = {check_data_type(val) for val in data} + + if DataType.TEXT in data_types: + return DataType.TEXT.value + + if DataType.DATETIME in data_types: + return DataType.DATETIME.value + + if DataType.REAL in data_types: + return check_float_type(data) + + if DataType.INTEGER in data_types: + return DataType.INTEGER.value + + return DataType.TEXT.value + + +# check special float type +def check_float_type(data): + cast_float = data[data.astype(str).str.strip() != ''].astype(float) + # count +inf|-inf of cast data + n = (cast_float == math.inf).sum() or (cast_float == -math.inf).sum() + + if n == 0: + return DataType.REAL.value + else: + # count +inf|-inf of raw data + nr = (data == math.inf).sum() or (data == -math.inf).sum() + if n > nr: + return DataType.TEXT.value + else: + return DataType.REAL.value + + +# check data type of 1 data +def check_data_type(data): + if data is None or data == '': + return DataType.NULL + + if isinstance(data, datetime): + return DataType.DATETIME + + if isinstance(data, (date, time)): + return DataType.TEXT + + if isinstance(data, int): + return DataType.INTEGER + + if isinstance(data, float) or isinstance(data, decimal.Decimal): + return DataType.REAL + + try: + if str(int(data)) == str(data): + return DataType.INTEGER + else: + return DataType.TEXT + except ValueError: + pass + + try: + if float(data) or float(data) == 0: + return DataType.REAL + except ValueError: + pass + + try: + re_dt = r'^\d{4}[-\/]\d{1,2}[-\/]\d{1,2}[\sT]\d{1,2}:\d{1,2}(:\d{1,2})?(\.\d{3,7})?((\s?([+-]?\d{1,2}:\d{2})?)|Z)?$' + matches = re.match(re_dt, data) + if matches: + return DataType.DATETIME + except (ValueError, TypeError): + pass + + return DataType.TEXT + + +def filter_blank_row(data): + """ + filter blank row in csv + :param data: + :return: + """ + for row in data: + if row: + yield row + + +@log_execution_time() +def is_normal_csv(f_name, delimiter=','): + """ + check if csv is normal type + :param f_name: + :param delimiter: + :return: + """ + data = read_data(f_name, end_row=20, delimiter=delimiter, do_normalize=False) + data = filter_blank_row(data) + + data = list(data) + if not data: + return True + + headers = data[0] or [] + rows = data[1:] or [] + + # column name is duplicate + if len(headers) != len(set(headers)): + return False + + # check column number of header vs data + for row in rows: + if len(row) != len(headers): + return False + + return True diff --git a/histview2/common/services/csv_header_wrapr.py b/histview2/common/services/csv_header_wrapr.py new file mode 100644 index 0000000..67ddbf4 --- /dev/null +++ b/histview2/common/services/csv_header_wrapr.py @@ -0,0 +1,826 @@ +import io +import itertools +import pickle +import re +import unicodedata +import datetime +from collections import Counter +from itertools import tee + +import numpy as np +import pandas as pd + +from histview2.common.constants import * +from histview2.common.logger import logger +from histview2.common.services.normalization import normalize_list + + +def load_pkl(filename): + with open(filename, 'rb') as f: + return pickle.load(f) + + +def get_file_info_py(target_file): + no_data_error = 'NoDataError' + error_type = 'err_type' + + is_empty_file = False + try: + out = filechecker(fpath=target_file) + except Exception as e: + logger.error(e) + return e, is_empty_file + + if out: + error = out.get('err', None) + if error: + logger.error(error) + error_type = out.get(error_type, None) + if error_type == no_data_error: + is_empty_file = True + return None, is_empty_file + else: + return out, is_empty_file + + return None, is_empty_file + + +# this func use R script +# def get_file_info(target_file): +# dir_out = get_data_path() +# dir_wrapr = get_wrapr_path() +# file_check_func = 'filecheckr' +# r_file_check = 'FileCheckr_20-06-02.R' +# no_data_error = 'NoDataError' +# error_type = 'err_type' +# +# # define parameters +# dic_data = {} # filecheckr does not need input data +# dic_task = dict(func=file_check_func, file=r_file_check, fpath=target_file) +# +# # define and run pipeline +# is_empty_file = False +# try: +# pipe = wrapr_utils.RPipeline(dir_wrapr, dir_out) +# out = pipe.run(dic_data, [dic_task]) +# except Exception as e: +# logger.error(e) +# return e, is_empty_file +# +# if out: +# error = out.get('err', None) +# if error: +# logger.error(error) +# error_type = out.get(error_type, None) +# if error_type == no_data_error: +# is_empty_file = True +# return None, is_empty_file +# else: +# res = load_pkl(out['fname_out']) +# res = res['results'] +# res = dict(res) +# return res, is_empty_file +# +# return None, is_empty_file + + +def get_skip_head(wrapr): + return int(wrapr['info']['skip']) + + +def get_skip_tail(wrapr): + return 1 + + +def get_columns_name(wrapr): + return list(wrapr['head']['main']) + + +def get_efa_header_flag(wrapr): + return wrapr['info'].get('efa_header_exists') or False + + +def get_data_type(wrapr): + data_types = [] + col_types = [data_type.lower() for data_type in wrapr['type']['class']] + for data_type in col_types: + if data_type in ['character', 'string']: + data_types.append(DataType.TEXT.value) + elif data_type in ['date', 'time', 'datetime', 'datetime64']: + data_types.append(DataType.DATETIME.value) + elif data_type in ['real', 'numeric']: + data_types.append(DataType.REAL.value) + elif data_type in ['int', 'integer', 'integer64']: + data_types.append(DataType.INTEGER.value) + else: + data_types.append(DataType.TEXT.value) + + return data_types + + +def get_etl_headers(wrapr): + etl_headers = { + WR_HEADER_NAMES: [], + WR_VALUES: [], + WR_TYPES: [], + WR_HEAD: [], + } + if not wrapr: + return etl_headers + + # Get first True value from wrapr.head.rplc + header_pos = None + for idx, val in enumerate(wrapr[WR_HEAD][WR_RPLC]): + if val: + header_pos = idx + break + + if header_pos is None: + return etl_headers + + dic_header = wrapr[WR_CTGY] + header_names = list(dic_header) + values = [dic_header[name][header_pos] for name in header_names] + + etl_headers[WR_HEADER_NAMES] = normalize_list(header_names) + etl_headers[WR_VALUES] = normalize_list(values) + for val in values: + try: + int(val) + if '.' in str(val): + dtype = DataType.REAL.value + else: + dtype = DataType.INTEGER.value + except Exception: + dtype = DataType.TEXT.value + + etl_headers[WR_TYPES].append(dtype) + + # etl_headers[WR_TYPES] = [DataType.TEXT.value] * len(header_names) + return etl_headers + + +def merge_etl_heads(new_vals, data): + return [row + new_vals for row in data] + + +# ----- Filechecker ----- + +def filechecker(fpath: str, nrows_to_check=20) -> dict: + ''' Analyze dsv files + + Inputs: + fpath (str) Path to csv/tsv + nrows_to_check (int) Maximum number of rows to check for encoding estimation + + Returns: + results (dict) + - info (dict) Basic info: encoding, separator, na string, rows to skip. + - head (pd.DataFrame) Header info. Column names and units. + - type (pd.DataFrame) Datatype info. Datetime/string/numeric/integer. + - ctgy (pd.DataFrame) Category info. (Machine, Line, Process) + ''' + + results = dict(info=None, head=None, type=None, ctgy=None) + + # read as list + encd = guess_encoding_simply(fpath) + dlist = read_first_nrows_as_list(fpath, nrows_to_check, encd, del_newline=True) + if len(dlist) == 0: + return {'err': 'NoDataError: No data in file', 'err_type': 'NoDataError'} + + sep_str = guess_delimeter(dlist, candidates=[',', '\t', ';']) + ncols = guess_number_of_columns(dlist, delimeter=sep_str) + + # read as array + arr_dat = read_first_nrows_as_array(fpath, 20, ncols, sep_str, encd) + nas = guess_na_str(arr_dat) + + # parse header + info = dict(encd=encd, sepr=sep_str, ncol=ncols, na_s=nas['str'], expt=nas['exc']) + hdr = parse_header(dlist, arr_dat, info) + if len(hdr['row']) == 0: + print("No header detected") + info['skip'] = hdr['skip'] + head = summarize_header_as_df(hdr, info) + ctgy = summarize_category_as_df(hdr, info, head['main'].values) + if arr_dat.shape[0] == hdr['end']: + return {'err': 'NoDataError: No header detected', 'err_type': 'NoDataError'} + + # guess_datatypes + df = read_main_text_as_df(fpath, info, 100, head['main'], hdr['is_header_detected']) + dtypes = guess_datatypes(df) + head['escp'] = guess_escape_strings(df) + + results = dict(info=info, head=head, type=dtypes, ctgy=ctgy) + return results + + +# ========================= +# Read header +# ========================= + +def guess_encoding_simply(fpath: str, max_rows=100) -> str: + ''' Very simple file encoding estimator + Just try utf-8 and shift-jis. + Assume that we can not open shift-jis file with utf-8. + ''' + try: + encd = 'utf-8' + _ = read_first_nrows_as_list(fpath, max_rows, encd) + except UnicodeDecodeError: + encd = 'shift_jis' + _ = read_first_nrows_as_list(fpath, max_rows, encd) + + return encd + + +def read_first_nrows_as_list(fpath: str, nrows: int, encd: str, del_newline=False) -> list: + ''' Read a text file as a list + Each element corresponds to a row of text file. + File encoding must be estimated beforehand. + ''' + dat = [] + with open(fpath, encoding=encd) as f: + try: + for _ in range(nrows): + dat.append(next(f)) + except StopIteration: + pass + if del_newline: + dat = _remove_newline_str(dat) + return dat + + +def read_first_nrows_as_array(fpath: str, nrows: int, ncols: int, sep_str: str, encd: str): + ''' Read a text file as a NumpyArray + File encoding, separator, and number of columns must be estimated beforehand. + ''' + dat = np.full((nrows, ncols), '', dtype=object) + with open(fpath, encoding=encd) as f: + try: + for row in range(nrows): + vals = next(f).split(sep_str) + dat[row, :len(vals)] = np.array(_remove_newline_str(vals)) + except StopIteration: + dat = dat[:row, :] + pass + return dat + + +def _remove_newline_str(x: list, newline_str='\n') -> list: + x = [x.rstrip(newline_str) for x in x] + return x + + +# ========================= +# Guess delimeter / number of columns +# ========================= + +def guess_delimeter(x: list, candidates=[',', '\t', ';']) -> str: + ''' Guess delimeter from a list of strings + The character which maximizes the minimum of occurrence is estimated as a delimeter. + Note that this is just a heuristic. + ''' + ed_row = np.max([0, len(x) - 1]) + st_row = np.max([0, ed_row - 10]) + rows_to_check = np.arange(st_row, ed_row) + x_to_check = [x[i] for i in rows_to_check] + + delim_cnts = _count_characters(x_to_check, candidates) + min_delim_cnts = np.min(delim_cnts, axis=0) + sep_str = candidates[np.argmax(min_delim_cnts)] + return sep_str + + +def _count_characters(x: list, charlist: list): + ''' Count occurrence of characters in each element of x + ''' + cnts = np.zeros((len(x), len(charlist))) + for (i, row) in enumerate(range(len(x))): + chr_counter = Counter(x[row]) + cnts[i, :] = [chr_counter[char] for char in charlist] + return cnts + + +def guess_number_of_columns(x: list, delimeter: str) -> int: + ''' Guess number of columns from a list of strings + Take the maximum count of delimeters + 1 + ''' + delim_cnts = [text.count(delimeter) for text in x] + ncols = np.max(delim_cnts) + 1 # e.g. one separator means 2 columns + return ncols + + +# ========================= +# Parse header +# ========================= + +def parse_header(x, arr_dat, info: dict) -> dict: + ''' Get positions and category info of header + ''' + # does header exist? + hdr = guess_where_header_is(x, info) + hdr['is_header_detected'] + + # trim whitespace on each element + def trimws(x): + return x.strip() + + inf = arr_dat.copy()[:(hdr['end'] + 1), :] + inf = np.vectorize(trimws)(inf) + + values, lengths = rle(inf[hdr['main'], :].flatten()) + values[:] = np.full(len(values), 1) + values[np.where(lengths != 1)] = "0" + + hdm = dict(uni=np.equal(inverse_rle(values, lengths), np.array("1", dtype=object))) + hdr['hdm'] = hdm + + # Get special dsv header information + hdr['uni'] = np.full(info['ncol'], "", dtype=object) + hdr['sb0'] = np.full(info['ncol'], "", dtype=object) + is_known_struct = is_known_header(inf[:, 0]) + if is_known_struct: + hdr = parse_known_header(hdr, inf, info) + + hdr['act'] = hdr['uni'] != "" + hdr['skip'] = hdr['end'] + hdr['inf'] = inf + return hdr + + +def guess_where_header_is(x: list, info: dict) -> dict: + dic_hdr = dict(lst=None, msk=None, str=None, nch=None, flg=None, + top=0, end=0, main=None, skip=0, is_header_detected=None, + ctg=None, inf=None, act=None, hdm=None, uni=None, sb0=None) + + header_starts_from = guess_where_header_starts(x, delimeter=info['sepr'], ncols=info['ncol']) + row_idx_candidate = np.arange(header_starts_from, len(x)) + if len(row_idx_candidate) > 10: + row_idx_candidate = row_idx_candidate[:10] + + x_masked = [] + msk = gen_header_mask(info['sepr'], info['na_s'], info['expt']) + for row in row_idx_candidate: + x_masked.append(re.sub(msk, "", x[row])) + + nchars_in_each_row = [len(text) for text in x_masked] + hdr_flgs = seems_like_header(nchars_in_each_row) + hdr_rows = row_idx_candidate[hdr_flgs] + + dic_hdr['msk'] = msk + dic_hdr['str'] = x_masked + dic_hdr['nch'] = nchars_in_each_row + dic_hdr['flg'] = hdr_flgs + dic_hdr['row'] = hdr_rows + dic_hdr['is_header_detected'] = len(hdr_rows) > 0 + if len(hdr_rows) > 0: + num_informative_cols = guess_number_of_informative_cols(x, info['sepr']) + is_full_row = np.array(num_informative_cols) == info['ncol'] + dic_hdr['top'] = np.min(hdr_rows) + dic_hdr['end'] = np.max(hdr_rows) + dic_hdr['main'] = np.intersect1d(hdr_rows, np.where(is_full_row)[0]) + return dic_hdr + + +def guess_where_header_starts(x: list, delimeter: str, ncols: int) -> int: + nsep = [text.count(delimeter) for text in x] + rows_with_full_vals = np.where(nsep == (ncols - 1))[0] + if len(rows_with_full_vals) == 1: + return rows_with_full_vals + + # detect continuous row group + diff_rows = np.diff(rows_with_full_vals) + values, lengths = rle(diff_rows) + grp = np.max(np.where(values == 1)[0]) + + # select first row in continuous row group + grps_to_ignore = np.setdiff1d(np.arange(1, len(values)), grp) + values[grps_to_ignore] = 0 + reversed_values = inverse_rle(values, lengths) + rows_in_grp = rows_with_full_vals[np.where(reversed_values == 1)[0]] + header_starts_from = np.min(rows_in_grp) + return header_starts_from + + +def gen_header_mask(sep_str: str, na_s, expt) -> str: + # Generate regex string for masking + msk = sep_str + if len(na_s) > 0: + exceptions = list(itertools.chain.from_iterable([sep_str, na_s])) + msk = "|".join(exceptions) + if len(expt) > 0: + exceptions = list(itertools.chain.from_iterable([msk, expt])) + msk = "|".join(exceptions) + msk = "(" + msk + "|/|:|\\d+(\\.|,| )?)" + return msk + + +def seems_like_header(nchars_in_each_row): + # Determine header or not by number of characters + # if data has only one row, treat that row as header + if len(nchars_in_each_row) == 1: + return True + # assume that there is no header + flg = [x < 0 for x in nchars_in_each_row] + med = np.median(nchars_in_each_row) + # in case with header + if med > 0: + if (np.max(nchars_in_each_row) / med) > 1.5: + flg = nchars_in_each_row > med + return flg + + +def guess_number_of_informative_cols(x: list, delimeter: str) -> list: + ''' Guess number of "Informative" columns on each row + Here, informative means that a value is stored in an element. + ''' + reg_nul = "(" + delimeter + " *" + delimeter + "|" + delimeter + " *$)" + nsep = [len(re.findall(delimeter, text)) for text in x] + dsv = [re.sub(reg_nul, "", text) for text in x] + ncol = [len(re.findall(delimeter, text)) + 1 for text in dsv] + return ncol + + +def is_known_header(x) -> bool: + known_header = ["検索期間", "ライン", "工程", "設備", "特性名", "", ""] + if len(x) < len(known_header): + return False + for i in range(len(known_header)): + if x[i] != known_header[i]: + return False + return True + + +def parse_known_header(hdr, inf, info) -> dict: + # Get category of machine + ma0 = np.array([re.sub("#N/A", "-", text) for text in inf[3, :]]) + ma0[0] = "" + machine_values, machine_lengths = rle(ma0) + + # Shifted position to repeat (to the right side to fill the empty cells) + for i in range(1, len(machine_values)): + if machine_values[i] == "": + machine_values[i] = machine_values[i - 1] + + ctg = dict(mac=inverse_rle(machine_values, machine_lengths), + lin=np.full(info['ncol'], re.sub("#N/A", "-", inf[1, 1])), + prc=np.full(info['ncol'], re.sub("#N/A", "-", inf[2, 1]))) + + hdr['ctg'] = ctg + hdr['uni'] = inf[5, :] + hdr['sb0'] = inf[4, :] + hdr['sb0'][0] = "" + hdr['sb1'] = inf[6, :] + hdr['if1'] = inf[0, :] + chars_if1 = np.array([len(text) for text in hdr['if1']]) + hdr['if1'] = hdr['if1'][np.where(chars_if1 > 0)[0]] + return hdr + + +# ========================= +# Summarize +# ========================= + +def summarize_header_as_df(hdr: dict, info: dict): + ''' Summarize header info to data frame + Column names are translated/normalized. + Units are extracted. + If Column names are duplicated, suffix is added (e.g. _01) + ''' + # header information + head = dict(base=hdr['inf'][hdr['main'], :].flatten(), + rplc=np.logical_and(np.logical_not(hdr['hdm']['uni']), hdr['act'])) + + if np.sum(head['rplc']) > 0: + head['main'] = head['base'].copy() + head['long'] = head['base'].copy() + idx = np.where(head['rplc'])[0] + head['main'][idx] = hdr['uni'][idx].copy() + head['long'][idx] = hdr['sb0'][idx].copy() + else: + # extract text before : + head['main'] = np.array([text.split(":")[0] for text in head['base']]) + if len(head['main']) > 1: + head['long'] = head['main'].copy() + else: + head['long'] = np.full(info['ncol'], "") + + # NFKC normalization (e.g. zenkaku -> hankaku) + head['main'] = [unicodedata.normalize("NFKC", text) for text in head['main']] + head['long'] = [unicodedata.normalize("NFKC", text) for text in head['long']] + + # extract units + units = _split_colnames_from_unit(head['long']) + head['units'] = units['unit'] + head['user'] = [x + y for x, y in zip(units['colname'], units['suffix'])] + # translate + head['main'] = _translate_wellknown_jp2en(head['main']) + # if head$main has some same value, add _01, _02, ... + head['main'] = add_suffix_if_duplicated(head['main']) + + df_head = pd.DataFrame(head, index=head['main']) + return df_head + + +def summarize_category_as_df(hdr: dict, info: dict, col_names: list): + ''' Summarize category (Machine, Line, Process) info to data frame + ''' + ctgy = dict(Machine=np.full(info['ncol'], ""), + Line=np.full(info['ncol'], ""), + Process=np.full(info['ncol'], "")) + + if hdr['ctg'] != None: + if len(hdr['ctg']['mac']) > 0: + ctgy['Machine'] = hdr['ctg']['mac'] + if len(hdr['ctg']['lin']) > 0: + ctgy['Line'] = hdr['ctg']['lin'] + if len(hdr['ctg']['prc']) > 0: + ctgy['Process'] = hdr['ctg']['prc'] + + df_ctgy = pd.DataFrame(ctgy, index=col_names) + return df_ctgy + + +def _split_colnames_from_unit(x) -> dict: + units = dict(raw=x, colname=x, unit=np.full(len(x), ""), suffix=np.full(len(x), "")) + x_split = [re.split(" \\[|\\] |\\[|\\]", text) for text in x] + is_unit_detected = np.max([len(split) for split in x_split]) > 0 + if is_unit_detected: + units['colname'] = [x[0] for x in x_split] + units['unit'] = [x[1] if len(x) > 1 else "" for x in x_split] + units['suffix'] = [x[2] if len(x) > 2 else "" for x in x_split] + return units + + +def _translate_wellknown_jp2en(x): + fromto = dict(製品="Product", ロット="Lot", 内="Internal", 日時="DateTime", + 時間="Time", 品番="PartsNo", シリアル="Serial", ナンバー="Number", + 実績種別="ResultCategory", 番号="No", コード="Code", トレー="Tray", + 測定値="Measurement", 判定="Result", 作業者="Worker") + + for key, val in fromto.items(): + x = [text.replace(key, val) for text in x] + return x + + +def add_suffix_if_duplicated(x): + duplicated = [k for k, v in Counter(x).items() if v > 1] + if len(duplicated) == 0: + return x + suffix_format = (f'_{str(x).zfill(2)!s}' for x in range(1, 100)) + dic_suffix = dict(zip(duplicated, tee(suffix_format, len(duplicated)))) + for idx, s in enumerate(x): + try: + suffix = str(next(dic_suffix[s])) + except KeyError: + continue + else: + x[idx] += suffix + return x + + +# ========================= +# Guess datatypes +# ========================= + + +def read_main_text_as_df(fpath: str, info: dict, max_rows=100, col_names=None, header=False): + ''' Based on estimated header position, read data as pd.DataFrame + Remove leading single quotes + ''' + str_to_remove = "^'|(?<=" + info['sepr'] + ")(')" + x = read_first_nrows_as_list(fpath, nrows=max_rows, encd=info['encd'], del_newline=True) + x = [re.sub(str_to_remove, "", text) for text in x] + x = [x[row] for row in range(len(x)) if row >= info['skip'] + header] + df = pd.read_csv(io.StringIO('\n'.join(x)), sep=r',', header=None, names=col_names, + na_values=[''] + info['na_s'], dtype=str) + return df + + +def guess_datatypes(df) -> list: + dtypes = [] + intg = [] + real = [] + dati = [] + for col in df.columns.values: + uniq_vals = df[col].dropna().unique() + dtypes.append(_guess_datatype(uniq_vals)) + intg.append(_can_parse_as_integer(uniq_vals)) + real.append(_can_parse_as_numeric(uniq_vals)) + dati.append(_can_parse_as_datetime(uniq_vals)) + + types = pd.DataFrame({'class': dtypes, 'intg': intg, 'real': real, 'dati': dati}, + index=df.columns.values) + return types + + +def _guess_datatype(uniq_vals) -> str: + ''' Guess datatype of given arrray + ''' + + # initial guess + if len(uniq_vals) == 0: + return "logical" + if _leading_zero_exists(uniq_vals): + return "string" + + # datetime, integer or numeric + if _is_date(uniq_vals) or _is_dati(uniq_vals): + return 'datetime64' + + try: + _ = uniq_vals.astype("int") + return 'integer' + except: + try: + _ = pd.to_numeric(uniq_vals) + return 'numeric' + except: + return "string" + + +def _leading_zero_exists(uniq_vals) -> bool: + ''' Detect leading zeros + Detects: 000123, 0123 + Not detects: 123, 0.123 + ''' + reg_lead_zero = "^0+[0-9]+(?!\.)" + is_leading_zero = np.any([re.search(reg_lead_zero, str(val)) != None for val in uniq_vals]) + return is_leading_zero + + +def _can_parse_as_numeric(uniq_vals) -> bool: + try: + _ = pd.to_numeric(uniq_vals) + return True + except: + return False + + +def _can_parse_as_integer(uniq_vals) -> bool: + try: + _ = uniq_vals.astype("int") + return True + except: + return False + + +def _can_parse_as_datetime(uniq_vals) -> bool: + try: + _ = pd.to_datetime(uniq_vals) + return True + except: + return False + + +def _is_date(x) -> bool: + ''' + detect date format + x: 1-d array or list of strings + ''' + + formats = ["%Y-%m-%d", "%Y/%m/%d", # YMD + "%d-%m-%Y", "%d/%m/%Y", # DMY + "%m-%d-%Y", "%m/%d/%Y", # MDY + "%m-%d", "%m/%d", # MD + "%d-%m", "%d/%m"] # DM + + dttm = np.vectorize(datetime.datetime.strptime) + + date_detected = False + for fmt in formats: + try: + dttm(x, fmt) + date_detected = True + except: + continue + + return date_detected + + +def _is_dati(x) -> bool: + ''' + detect datetime format + x: 1-d array or list of strings + ''' + + formats = ["%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M:%S", # YMD HMS + "%Y-%m-%dT%H:%M:%S", "%Y/%m/%dT%H:%M:%S", + "%Y-%m-%d %H:%M:%S.%f", "%Y/%m/%d %H:%M:%S.%f", # YMD HMS.f + "%Y-%m-%dT%H:%M:%S.%f", "%Y/%m/%dT%H:%M:%S.%f", + "%Y-%m-%d %H:%M", "%Y/%m/%d %H:%M", # YMD HM + "%Y-%m-%dT%H:%M", "%Y/%m/%dT%H:%M", + + "%d-%m-%Y %H:%M:%S", "%d/%m/%Y %H:%M:%S", # DMY HMS + "%d-%m-%YT%H:%M:%S", "%d/%m/%YT%H:%M:%S", + "%d-%m-%Y %H:%M:%S.%f", "%d/%m/%Y %H:%M:%S.%f", # DMY HMS.f + "%d-%m-%YT%H:%M:%S.%f", "%d/%m/%YT%H:%M:%S.%f", + "%d-%m-%Y %H:%M", "%d/%m/%Y %H:%M", # DMY HM + "%d-%m-%YT%H:%M", "%d/%m/%YT%H:%M", + + "%m-%d-%Y %H:%M:%S", "%m/%d/%Y %H:%M:%S", # MDY HMS + "%m-%d-%YT%H:%M:%S", "%m/%d/%YT%H:%M:%S", + "%m-%d-%Y %H:%M:%S.%f", "%m/%d/%Y %H:%M:%S.%f", # MDY HMS.f + "%m-%d-%YT%H:%M:%S.%f", "%m/%d/%YT%H:%M:%S.%f", + "%m-%d-%Y %H:%M", "%m/%d/%Y %H:%M", + "%m-%d-%YT%H:%M", "%m/%d/%YT%H:%M"] + + dttm = np.vectorize(datetime.datetime.strptime) + + date_detected = False + for fmt in formats: + try: + dttm(x, fmt) + date_detected = True + except: + continue + + return date_detected + + +# ========================= +# Utilities +# ========================= + +def rle(x): + ''' Run Length Encoding (RLE) + ''' + seq_index = np.concatenate(([True], x[1:] != x[:-1], [True])).nonzero()[0] + vals = x[seq_index[:-1]] + lens = np.ediff1d(seq_index) + return vals, lens + + +def inverse_rle(vals, lens): + ''' Inverse of RLE + ''' + x = [None] * np.sum(lens) + cnt = 0 + for i in range(len(vals)): + for j in range(cnt, cnt + lens[i]): + x[j] = vals[i] + cnt += lens[i] + x = np.array(x) + return x + + +def guess_na_str(arr_dat) -> dict: + ''' Guess NA strings included in the data + ''' + + dic_nas = dict(lst=None, exc=None, sts=None, str=None) + + # count occurrence of each item + counter = Counter(arr_dat.flatten()) + + # decreasing order + idx = np.argsort(list(counter.values()))[::-1] + sorted_freqs = np.array(list(counter.values()))[idx] + sorted_items = np.array(list(counter.keys()))[idx] + + # remove items with nchars > 7 + nchars = np.array([len(x) for x in sorted_items]) + idx = np.where(nchars <= 7)[0] + sorted_freqs = sorted_freqs[idx] + sorted_items = sorted_items[idx] + + # remove non-na characters + nas = [re.sub("([ ,;:]|\\[.*\\]|\\(.*\\)|[^\x01-\x7E])", "", x) for x in sorted_items] + nas = [re.sub("( |\\d{1,2}/\\d|-?\\d*\\.?\\d+$)", "", x) for x in nas] + nas = [re.sub("(^[a-z]+$|^[A-Z]{3,}$)", "", x) for x in nas] + nas = [x for x in nas if x != ""] + + exceptions = ["Inf", "-Inf", "inf", "-inf", "#DIV/0!", "#VALUE!"] + exc = np.intersect1d(nas, exceptions) + sts = np.intersect1d(nas, np.setdiff1d(sorted_items, exc)) + + dic_nas['lst'] = nas + dic_nas['exc'] = exc + dic_nas['sts'] = sts + dic_nas['str'] = [sts[0]] + return dic_nas + + +def guess_escape_strings(df) -> list: + ''' Guess escape strings included in the data + e.g. 99999, -99999 sometime indicates Inf, -Inf, respectively. + ''' + escape_str = [] + for col in df.columns.values: + escape_str.append(_extract_escape_str(df[col])) + return escape_str + + +def _extract_escape_str(col) -> str: + cnts = col.value_counts() + vals_more_than_once = np.array(cnts.index[cnts > 1]) + if len(vals_more_than_once) == 0: + return "" + is_escape_str_detected = np.array([re.search("^(9+\\.?9*)$", str(val)) != None for val in vals_more_than_once]) + detected_str = vals_more_than_once[is_escape_str_detected] + if len(detected_str) == 0: + return "" + else: + return detected_str[0] diff --git a/histview2/common/services/form_env.py b/histview2/common/services/form_env.py new file mode 100644 index 0000000..eaf4d93 --- /dev/null +++ b/histview2/common/services/form_env.py @@ -0,0 +1,428 @@ +# dic_formの形式をparseして以下を実施 +# TBLS(テーブルの数)を取得し、配列を初期化 +# 1〜配列数分のarray_formvalの配列を初期化 +# 要素はNUM_ELEMENTS他 +import json +import logging +import re + +from histview2.common.common_utils import as_list +from histview2.common.constants import * +from histview2.common.services.request_time_out_handler import request_timeout_handling +from histview2.common.yaml_utils import BasicConfigYaml +from histview2.setting_module.services.process_config import get_all_process_no_nested, get_all_visualizations +from histview2.trace_data.schemas import DicParam, CommonParam, ConditionProc, CategoryProc, EndProc, \ + ConditionProcDetail + +logger = logging.getLogger(__name__) + +# common key receive from client +common_startwith_keys = ('start_proc', 'START_DATE', 'START_TIME', 'END_DATE', 'END_TIME', + CATE_VARIABLE, CATE_VALUE_MULTI, COMPARE_TYPE, 'isRemoveOutlier', 'client_timezone', + CYCLIC_DIV_NUM, CYCLIC_INTERVAL, CYCLIC_WINDOW_LEN, + HM_STEP, HM_MODE, HM_TRIM, HM_FUNCTION_REAL, HM_FUNCTION_CATE, + X_OPTION, SERIAL_COLUMN, SERIAL_ORDER, SERIAL_PROCESS, THRESHOLD_BOX, CAT_EXP_BOX, + IS_VALIDATE_DATA, DIC_CAT_FILTERS, TEMP_X_OPTION, TEMP_SERIAL_ORDER, TEMP_SERIAL_COLUMN, + TEMP_SERIAL_PROCESS, TEMP_CAT_EXP, TEMP_CAT_PROCS, DIV_BY_CAT, DIV_BY_DATA_NUM, COLOR_VAR, + CYCLIC_DIV_NUM, CYCLIC_WINDOW_LEN, CYCLIC_INTERVAL, MATRIX_COL, COLOR_ORDER, + IS_EXPORT_MODE, IS_IMPORT_MODE, VAR_TRACE_TIME, TERM_TRACE_TIME, CYCLIC_TRACE_TIME, TRACE_TIME) + +conds_startwith_keys = ('filter-', 'cond_', 'machine_id_multi') + +category_startwith_keys = (CATE_PROC, GET02_CATE_SELECT) + +multiple_selections = (GET02_VALS_SELECT, GET02_CATE_SELECT) + + +# フォームの環境変数の解析 + + +def parse(dic_form): + # TBLSがキー存在しない場合(index.pyを直打ちの場合) + if "TBLS" not in dic_form: + return {"TBLS": 1, "ARRAY_FORMVAL": []} + + # num_tbls = dic_form["TBLS"].value + num_tbls = dic_form["TBLS"] + dic_parsed = {"TBLS": num_tbls} + array_formval = [] + # まずは空のキーで辞書の配列を初期化 + for idx in range(int(num_tbls)): + array_formval.append({}) + + # NUM_ELEMENT + 数字の形式のキーを取得 + for key in dic_form.keys(): + + # array_value = dic_form.getlist(key) + + array_value = [] + raw_value = dic_form[key] + if raw_value.__class__.__name__ == 'list': + array_value.extend(raw_value) + else: + array_value.append(raw_value) + + value = array_value[0] + # Keyの文字列を解析 + m = re.match("(^.+)(\d+)$", key) + m2 = re.match(".+_multi\d+$", key) + if m: + matched_key = m.group(1) + matched_idx = int(m.group(2)) - 1 + # debugstr = "matched_key= {}\n".format(matched_key) + # debugstr += "matched_idx= {}\n".format(matched_idx) + # 昔の不要な環境変数が残っている場合は無視 + if matched_idx >= int(num_tbls): + continue + + # キーの最後に"_multi"がついている場合は配列として扱う + # マルチセレクタなどで使う + if m2: + value = array_value + array_formval[matched_idx][matched_key] = value + else: + dic_parsed[key] = value + + # デバッグ用の値 + dic_parsed[ARRAY_FORMVAL] = array_formval + return dic_parsed + + +@request_timeout_handling() +def parse_multi_filter_into_one(dic_form): + # count select cols of endpoint + num_tbls = len([key for key in dic_form if key.startswith('end_proc')]) + + dic_parsed = {TBLS: num_tbls, COMMON: {}} + cond_procs = [{}] + cate_procs = [{}] + array_formval = [] + + # まずは空のキーで辞書の配列を初期化 + for idx in range(int(num_tbls)): + array_formval.append({}) + + # NUM_ELEMENT + 数字の形式のキーを取得 + for key in dic_form.keys(): + raw_value = dic_form[key] + + value = remove_const_keyword(raw_value, [SELECT_ALL, None, '']) + # filter-line-machine-id + if key.startswith('filter-line-machine-id') and value == []: + value = [NO_FILTER] + + # Keyの文字列を解析 + m = re.match("(.*?)(\d+)$", key) + + # common keys + if key.startswith(common_startwith_keys): + if key == DIC_CAT_FILTERS: + dic_cat_filters = json.loads(value) + if dic_cat_filters: + dic_parsed[COMMON][key] = {int(col): sorted(vals) for col, vals in dic_cat_filters.items()} + elif key in (TEMP_CAT_EXP, TEMP_CAT_PROCS): + dic_parsed[COMMON][key] = json.loads(value) + else: + dic_parsed[COMMON][key] = value + + elif key.startswith(conds_startwith_keys): + matched_key = m.group(1) + matched_idx = int(m.group(2)) - 1 + for i in range(matched_idx - len(cond_procs) + 1): + cond_procs.append({}) + cond_procs[matched_idx][matched_key] = value + # category + elif key.startswith(category_startwith_keys): + matched_key = m.group(1) + matched_idx = int(m.group(2)) - 1 + for i in range(matched_idx - len(cate_procs) + 1): + cate_procs.append({}) + + # remove NO FILTER for category procs + edit_value = remove_const_keyword(value, [NO_FILTER]) + cate_procs[matched_idx][matched_key] = edit_value + + # end proc + elif m: + matched_key = m.group(1) + matched_idx = int(m.group(2)) - 1 + # 昔の不要な環境変数が残っている場合は無視 + for i in range(matched_idx - len(array_formval) + 1): + array_formval.append({}) + + # キーの最後に"_multi"がついている場合は配列として扱う + # マルチセレクタなどで使う + edit_value = remove_const_keyword(value, [NO_FILTER], + key.startswith(multiple_selections)) + array_formval[matched_idx][matched_key] = edit_value + + # end procs + dic_parsed[ARRAY_FORMVAL] = [ele for ele in array_formval if ele and ele.get(GET02_VALS_SELECT)] + dic_parsed[TBLS] = len(dic_parsed[ARRAY_FORMVAL]) + + # cond_procs + dic_parsed[COMMON][COND_PROCS] = [ele for ele in cond_procs if ele] + + # category procs + dic_parsed[COMMON][CATE_PROCS] = [ele for ele in cate_procs if ele and ele.get(GET02_CATE_SELECT)] + + dic_parsed[TBLS] = len(dic_parsed[ARRAY_FORMVAL]) + + # cond_procs + dic_parsed[COMMON][OBJ_VAR] = dic_form.get(OBJ_VAR) or [] + + # set default start process + if START_PROC not in dic_parsed[COMMON]: + dic_parsed[COMMON][START_PROC] = dic_parsed[ARRAY_FORMVAL][0].get(END_PROC) + + return dic_parsed + + +def parse_form_param_stratified_plot(dic_form): + # count select cols of endpoint + num_tbls = len([key for key in dic_form if key.startswith('end_proc')]) + + # num_conds = max([key for key in dic_form if key.startswith('cond_proc')]) + common_key = "COMMON" + dic_parsed = {"TBLS": num_tbls, common_key: {}} + # dic_parsed[common_key][cond_procs_key] = [{}] * num_conds + cond_procs = [{}] + array_formval = [] + + # まずは空のキーで辞書の配列を初期化 + for idx in range(int(num_tbls)): + array_formval.append({}) + + # NUM_ELEMENT + 数字の形式のキーを取得 + for key in dic_form.keys(): + + # raw_value = dic_form.getlist(key) + raw_value = dic_form[key] + + if isinstance(raw_value, (list, tuple)): + if len(raw_value) > 1: + value = raw_value + else: + value = raw_value[0] + else: + value = raw_value + + # Keyの文字列を解析 + m = re.match("(^.+)(\d+)$", key) + + # common keys + if key.startswith(common_startwith_keys): + + # condition + if m: + matched_key = m.group(1) + matched_idx = int(m.group(2)) - 1 + for i in range(matched_idx - len(cond_procs) + 1): + cond_procs.append({}) + cond_procs[matched_idx][matched_key] = value + else: + dic_parsed[common_key][key] = value + + continue + + # end proc + if m: + matched_key = m.group(1) + matched_idx = int(m.group(2)) - 1 + # debugstr = "matched_key= {}\n".format(matched_key) + # debugstr += "matched_idx= {}\n".format(matched_idx) + # 昔の不要な環境変数が残っている場合は無視 + for i in range(matched_idx - len(array_formval) + 1): + array_formval.append({}) + # キーの最後に"_multi"がついている場合は配列として扱う + # マルチセレクタなどで使う + array_formval[matched_idx][matched_key] = value + + # end procs + dic_parsed["ARRAY_FORMVAL"] = [ele for ele in array_formval if ele and ele.get('GET02_VALS_SELECT')] + dic_parsed['TBLS'] = len(dic_parsed["ARRAY_FORMVAL"]) + + # cond_procs + dic_parsed[common_key]["cond_procs"] = [ele for ele in cond_procs if ele] + # dic_parsed["debugstr"] = debugstr + return dic_parsed + + +def parse_request_params(request): + """ + Parse request parameters of Flask HTTP request to a dictionary. + Note: This function support parsing multiple values of keys. + + :param request: HTTP request in Flask + :return: Dictionary of key-value of request parameter. + """ + dic_form = {} + for key in request.args.keys(): + values = request.args.getlist(key) + if len(values) > 1: + dic_form[key] = values + else: + dic_form[key] = values[0] + return dic_form + + +def remove_const_keyword(value, keywords, is_multiple_selection=False): + if isinstance(value, (list, tuple)): + edit_value = [val for val in value if val not in keywords] + if len(edit_value) == 1: + edit_value = edit_value[0] + elif value in keywords: + edit_value = [] + elif is_multiple_selection: + edit_value = [value] + else: + edit_value = value + + return edit_value + + +def bind_condition_procs(dic_param): + # condition + cond_procs = [] + for dic_cond in dic_param[COMMON][COND_PROCS]: + proc_id = dic_cond[COND_PROC] + if not proc_id: + continue + + if isinstance(proc_id, list): + proc_id = ''.join(proc_id) + + # remove invalid + if not str(proc_id).isnumeric(): + continue + + cond_details = [] + for key, vals in dic_cond.items(): + if key == COND_PROC: + continue + + cond_details.append(ConditionProcDetail(vals)) + + cond_proc = ConditionProc(proc_id, cond_details) + cond_procs.append(cond_proc) + + return cond_procs + + +def bind_dic_param_to_class(dic_param): + dic_common = dic_param[COMMON] + + # chart count + chart_count = dic_param[TBLS] + + # conditions + cond_procs = bind_condition_procs(dic_param) + + # categories + cate_procs = [] + if dic_common.get(CATE_PROCS): + cate_procs = [CategoryProc(proc.get(CATE_PROC), proc[GET02_CATE_SELECT]) for proc in dic_common[CATE_PROCS]] + + # category expand + cat_exps = [] + + cat_exp = dic_common.get(CAT_EXP_BOX) + if cat_exp: + if isinstance(cat_exp, list): + cat_exps = cat_exp + else: + cat_exps.append(cat_exp) + + for i in range(1, 10): + cat_exp = dic_common.get(f'{CAT_EXP_BOX}{i}') + if cat_exp: + cat_exps.append(cat_exp) + + objective_var = dic_common.get(OBJ_VAR)[0] if dic_common.get(OBJ_VAR) else None + # common + common = CommonParam(dic_common[START_PROC], dic_common[START_DATE], dic_common[START_TM], dic_common[END_DATE], + dic_common[END_TM], cond_procs, cate_procs, + hm_step=dic_common.get(HM_STEP), + hm_mode=dic_common.get(HM_MODE), + hm_function_real=dic_common.get(HM_FUNCTION_REAL), + hm_function_cate=dic_common.get(HM_FUNCTION_CATE), + hm_trim=dic_common.get(HM_TRIM), + client_timezone=dic_common.get(CLIENT_TIMEZONE), + x_option=dic_common.get(X_OPTION), + serial_processes=as_list(dic_common.get(SERIAL_PROCESS)), + serial_columns=as_list(dic_common.get(SERIAL_COLUMN)), + serial_orders=as_list(dic_common.get(SERIAL_ORDER)), + threshold_boxes=as_list(dic_common.get(THRESHOLD_BOX)), + cat_exp=cat_exps, + is_validate_data=dic_common.get(IS_VALIDATE_DATA), + objective_var=objective_var, + color_var=dic_common.get(COLOR_VAR), + div_by_data_number=dic_common.get(DIV_BY_DATA_NUM), + div_by_cat=dic_common.get(DIV_BY_CAT), + cyclic_div_num=dic_common.get(CYCLIC_DIV_NUM), + cyclic_window_len=dic_common.get(CYCLIC_WINDOW_LEN), + cyclic_interval=dic_common.get(CYCLIC_INTERVAL), + cyclic_terms=dic_common.get(CYCLIC_TERMS), + compare_type=dic_common.get(COMPARE_TYPE) + ) + + # array_formval + array_formval = [EndProc(proc.get(END_PROC), proc[GET02_VALS_SELECT]) for proc in dic_param[ARRAY_FORMVAL]] + + if not common.start_proc: + common.start_proc = array_formval[0].proc_id + + cyclic_terms = [] + out_param = DicParam(chart_count, common, array_formval, cyclic_terms) + + return out_param + + +def get_common_config_data(get_visualization_config=True): + processes = get_all_process_no_nested() + procs = [(proc.get('id'), proc.get('name')) for proc in processes] + graph_filter_detail_ids = [] + if get_visualization_config: + graph_filter_detail_ids = get_all_visualizations() + + output_dict = { + 'list_procs': json.dumps(processes), # use in each page + 'procs': procs, # use in jinja of macro + 'graph_filter_detail_ids': graph_filter_detail_ids, + } + + # get title + basic_config_yml = BasicConfigYaml() + title = BasicConfigYaml.get_node(basic_config_yml.dic_config, ['info', 'title'], '') + output_dict.update({ + "title": title, + }) + + return output_dict + + +def bind_multiple_end_proc_rlp(dic_form): + multiple_dic_form = [] + common_dic_form = dic_form.copy() + remove_keys = [] + for key in common_dic_form: + # find end_proc from dic_form + if (key.startswith('end_proc')): + [proc_idx] = re.findall('[0-9]+', key) + start_proc_key = START_PROC + str(proc_idx) + get_val_selects = GET02_VALS_SELECT + str(proc_idx) + dic_form_item = { + key: common_dic_form[key], + START_PROC: common_dic_form.get(start_proc_key) or common_dic_form.get(START_PROC), + get_val_selects: common_dic_form[get_val_selects] + } + multiple_dic_form.append(dic_form_item) + remove_keys.append(key) + remove_keys.append(start_proc_key) + remove_keys.append(get_val_selects) + + common_dic_form = {k: v for k, v in common_dic_form.items() if k not in remove_keys} + for dform in multiple_dic_form: + dform.update(common_dic_form) + + return multiple_dic_form diff --git a/histview2/common/services/http_content.py b/histview2/common/services/http_content.py new file mode 100644 index 0000000..a28eec9 --- /dev/null +++ b/histview2/common/services/http_content.py @@ -0,0 +1,93 @@ +import json +from datetime import date, datetime, time +from decimal import Decimal +from fractions import Fraction +from functools import singledispatch + +import numpy as np +import pandas as pd +from numpy import ndarray, int32, float64, int16, int8, float32, int64 +from pandas import DataFrame, Series + +from histview2.common.constants import DataType +from histview2.common.logger import logger + + +@singledispatch +def json_serial(obj): + try: + # call toJSON method in customize class. + # create toJSON method in your class + return obj.toJSON() + except AttributeError: + if pd.isnull(obj): + return None + + if np.isnan(obj): + return None + + logger.warning('failed - trying to use vars...', obj) + print('\tfailed - trying to use vars...', obj) + + try: + return vars(obj) + except TypeError: + logger.warning('failed - using string representation...', obj) + print('\tfailed - using string representation...', obj) + return str(obj) + + +@json_serial.register(date) +@json_serial.register(datetime) +@json_serial.register(time) +def _(obj): + # print(type(obj), obj) + return obj.isoformat() + + +@json_serial.register(Decimal) +@json_serial.register(Fraction) +@json_serial.register(float32) +@json_serial.register(float64) +def _(obj): + return float(obj) + + +@json_serial.register(int8) +@json_serial.register(int16) +@json_serial.register(int32) +@json_serial.register(int64) +def _(obj): + return int(obj) + + +@json_serial.register(DataType) +def _(obj): + # print(type(obj), obj) + return obj.value + + +@json_serial.register(Series) +def _(obj): + if hasattr(obj, 'to_list'): + return obj.to_list() + return json.JSONEncoder.default(obj) + + +@json_serial.register(set) +def _(obj): + return list(obj) + + +@json_serial.register(ndarray) +def _(obj): + if hasattr(obj, 'tolist'): + return obj.tolist() + return json.JSONEncoder.default(obj) + + +@json_serial.register(DataFrame) +def _(obj): + if hasattr(obj, 'to_dict'): + return obj.to_dict('list') + return json.JSONEncoder.default(obj) diff --git a/histview2/common/services/import_export_config_n_data.py b/histview2/common/services/import_export_config_n_data.py new file mode 100644 index 0000000..2eec14e --- /dev/null +++ b/histview2/common/services/import_export_config_n_data.py @@ -0,0 +1,277 @@ +### EXPORT +import io +import os +import pickle +from pathlib import Path +from zipfile import ZipFile, ZIP_DEFLATED + +import pandas as pd +from flask import make_response + +from histview2.api.trace_data.services.time_series_chart import get_proc_ids_in_dic_param +from histview2.common.common_utils import get_basename, resource_path, get_export_path, read_pickle_file, set_debug_data +from histview2.common.constants import AbsPath, DIC_FORM_NAME, CONFIG_DB_NAME, DF_NAME, DebugKey, CsvDelimiter, \ + COMMON, IS_EXPORT_MODE, IS_IMPORT_MODE, MemoizeKey, USER_SETTING_NAME +from histview2.common.memoize import set_cache_attr +from histview2.common.services.form_env import bind_dic_param_to_class, parse_multi_filter_into_one +from histview2.common.trace_data_log import get_log_attr, TraceErrKey, EventAction, Target +from histview2.setting_module.models import CfgProcess, DataTraceLog, make_session, CfgProcessColumn, CfgFilter, \ + CfgTrace, CfgVisualization, CfgFilterDetail, CfgTraceKey, CfgUserSetting, CfgDataSource, CfgDataSourceDB, \ + CfgDataSourceCSV, CfgCsvColumn +from histview2.setting_module.schemas import ProcessFullSchema, CfgUserSettingSchema + + +def export_debug_info(dataset_id, user_setting_id): + recs = DataTraceLog.get_dataset_id(dataset_id, EventAction.SAVE.value) + file_dic_form = None + file_df = None + for rec in recs: + if rec.target == Target.TSV.value: + file_df = rec.dumpfile + elif rec.target == Target.PICKLE.value: + file_dic_form = rec.dumpfile + + config_buffer = None + if file_dic_form: + dic_form = read_pickle_file(file_dic_form) + dic_param = parse_multi_filter_into_one(dic_form) + graph_param = bind_dic_param_to_class(dic_param) + config_buffer = io.BytesIO() + config_data = gen_config_json(graph_param) + pickle.dump(config_data, config_buffer, pickle.HIGHEST_PROTOCOL) + + # user_setting_id + user_setting_buffer = io.BytesIO() + user_setting = gen_user_setting_json(user_setting_id) + pickle.dump(user_setting, user_setting_buffer, pickle.HIGHEST_PROTOCOL) + + # TODO : naming zip file + response = download_zip_file('export_file', [file_dic_form, file_df, config_buffer, user_setting_buffer], + [f'{DIC_FORM_NAME}.pickle', f'{DF_NAME}.tsv', f'{CONFIG_DB_NAME}.pickle', + f'{USER_SETTING_NAME}.pickle']) + return response + + # output_file = create_export_file_path('FPP') + # zip_a_file(output_file, [file_dic_form, file_df, config_buffer], ['dic_form', 'df', 'config_db']) + # return output_file + + +def check_if_using_cache(dic_param): + if dic_param[COMMON].get(IS_EXPORT_MODE) or dic_param[COMMON].get(IS_IMPORT_MODE): + set_cache_attr(MemoizeKey.STOP_USING_CACHE, True) + + +def set_export_dataset_id_to_dic_param(dic_param): + if dic_param[COMMON].get(IS_EXPORT_MODE): + dic_param[IS_EXPORT_MODE] = get_log_attr(TraceErrKey.DATASET) + + return True + + +def create_export_file_path(key): + file_path = resource_path(get_export_path(), key, level=AbsPath.SHOW) + if not os.path.exists(os.path.dirname(file_path)): + os.makedirs(os.path.dirname(file_path)) + + return file_path + + +def gen_config_json(graph_param): + """ + get all process from graph_param then gen its json + :param graph_param: + :return: + """ + process_schema = ProcessFullSchema() + proc_ids = get_proc_ids_in_dic_param(graph_param) + processes = CfgProcess.get_procs(proc_ids) + return process_schema.dump(processes, many=True) + + +def gen_user_setting_json(user_setting_id): + """ + get user setting json + :param user_setting_id: + :return: + """ + + user_setting = CfgUserSetting.get_by_id(setting_id=user_setting_id) + user_setting_schema = CfgUserSettingSchema() + return user_setting_schema.dump(user_setting) + + +def zip_a_file(zip_file, target_file_paths, target_file_names=None): + with ZipFile(zip_file, 'w', ZIP_DEFLATED, compresslevel=9) as zipf: + for idx, target_file in enumerate(target_file_paths): + if not target_file: + continue + + if target_file_names: + file_name = target_file_names[idx] + else: + file_name = get_basename(target_file) + + if isinstance(target_file, io.BytesIO): + zipf.writestr(file_name, target_file.getvalue()) + else: + zipf.write(target_file, arcname=file_name) + + return True + + +def download_zip_file(zip_file_name, target_file_paths, target_file_names=None): + file_obj = io.BytesIO() + zip_a_file(file_obj, target_file_paths, target_file_names) + file_obj.seek(0) + + response = make_response(file_obj.read()) + response.headers.set('Content-Type', 'zip') + # response.headers.set('Content-Disposition', 'attachment', f'filename={zip_file_name}.zip') + response.headers.set('Content-Disposition', 'attachment', filename=f'{zip_file_name}.zip') + return response + + +# IMPORT +def import_user_setting_db(zip_file): + user_setting = extract_one_file(zip_file, f'{USER_SETTING_NAME}.pickle') + user_setting = pickle.loads(user_setting) + clear_config_db([CfgUserSetting]) + insert_data_to_table(CfgUserSetting, user_setting) + + return user_setting + + +def get_dic_form_from_debug_info(dic_param): + # check if using cache or not (export mode) + check_if_using_cache(dic_param) + + filename = dic_param[COMMON].get(IS_IMPORT_MODE) + debug_info_file = get_zip_full_path(filename) + if debug_info_file: + dic_form = get_dic_form(debug_info_file) + dic_param = parse_multi_filter_into_one(dic_form) + dic_param[COMMON][IS_EXPORT_MODE] = None + + return dic_param + + +def get_dic_form(zip_file): + dic_form = extract_one_file(zip_file, f'{DIC_FORM_NAME}.pickle') + dic_form = pickle.loads(dic_form) + + df = extract_one_file(zip_file, f'{DF_NAME}.tsv') + df = pd.read_csv(io.BytesIO(df), sep=CsvDelimiter.TSV.value) + set_debug_data(DebugKey.GET_DATA_FROM_DB.name, df) + set_debug_data(DebugKey.IS_DEBUG_MODE.name, True) + + return dic_form + + +def get_data_source_info(config_db): + dic_ds = {} + dic_ds_db = {} + dic_ds_csv = {} + dic_csv_col = {} + for proc in config_db: + ds = proc[CfgProcess.data_source.key] + ds_id = ds[CfgDataSource.id.key] + + # already exist + if ds_id in dic_ds: + continue + + # add data source + dic_ds[ds_id] = ds + + # add data source db + ds_db = ds.get(CfgDataSource.db_detail.key) + if ds_db: + dic_ds_db[ds_id] = ds_db + + # add data source csv + ds_csv = ds.get(CfgDataSource.csv_detail.key) + if ds_csv: + dic_ds_csv[ds_id] = ds_csv + dic_csv_col[ds_id] = ds_csv[CfgDataSourceCSV.csv_columns.key] + + return dic_ds, dic_ds_db, dic_ds_csv, dic_csv_col + + +def import_config_db(zip_file): + data_source_tables = [CfgDataSource, CfgDataSourceDB, CfgDataSourceCSV, CfgCsvColumn] + process_tables = [CfgProcess, CfgProcessColumn, CfgFilter, CfgFilterDetail, CfgTrace, CfgTraceKey, CfgVisualization] + + # clear db first + clear_config_db(reversed(process_tables)) + clear_config_db(reversed(data_source_tables)) + + config_db = extract_one_file(zip_file, f'{CONFIG_DB_NAME}.pickle') + config_db = pickle.loads(config_db) + # insert data source + data_source_data = get_data_source_info(config_db) + for ds_table, dic_data in zip(data_source_tables, data_source_data): + for values in dic_data.values(): + insert_data_to_table(ds_table, values) + + # insert process + insert_data_to_table(CfgProcess, config_db) + for cfg_process in config_db: + cfg_columns = cfg_process[CfgProcess.columns.key] + for col in cfg_columns: + col[CfgProcessColumn.process_id.key] = cfg_process[CfgProcess.id.key] + + cfg_filters = cfg_process[CfgProcess.filters.key] + cfg_traces = cfg_process[CfgProcess.traces.key] + cfg_visuals = cfg_process[CfgProcess.visualizations.key] + + # columns + insert_data_to_table(CfgProcessColumn, cfg_columns) + + # filter & detail + insert_data_to_table(CfgFilter, cfg_filters) + for cfg_filter in cfg_filters: + insert_data_to_table(CfgFilterDetail, cfg_filter[CfgFilter.filter_details.key]) + # trace & trace key + insert_data_to_table(CfgTrace, cfg_traces) + for cfg_trace in cfg_traces: + insert_data_to_table(CfgTraceKey, cfg_trace[CfgTrace.trace_keys.key]) + # visualize + insert_data_to_table(CfgVisualization, cfg_visuals) + + return config_db + + +def clear_config_db(models): + with make_session() as meta_session: + for model in models: + meta_session.query(model).delete() + + return True + + +def insert_data_to_table(model, records): + if not records: + return False + + # insert data + with make_session() as meta_session: + meta_session.execute(model.__table__.insert(), records) + + return True + + +def extract_zip(zip_file): + input_zip = ZipFile(zip_file) + return {Path(name).stem: input_zip.read(name) for name in input_zip.namelist()} + + +def extract_one_file(zip_file, file_name): + input_zip = ZipFile(zip_file) + return input_zip.read(file_name) + + +def get_zip_full_path(filename): + if not filename: + return None + + return os.path.join(get_export_path(), get_basename(filename)) diff --git a/histview2/common/services/jp_to_romaji_utils.py b/histview2/common/services/jp_to_romaji_utils.py new file mode 100644 index 0000000..6ff40d8 --- /dev/null +++ b/histview2/common/services/jp_to_romaji_utils.py @@ -0,0 +1,125 @@ +import collections +import re +from functools import lru_cache + +import cutlet + +from histview2.common.services.normalization import normalize_str + +conv = cutlet.Cutlet() + + +@lru_cache(maxsize=1000) +def to_romaji(input_str): + normalized_input = input_str + # normalize (also replace Full Space to Half Space) + # normalized_input = jaconv.normalize(normalized_input) + normalized_input = normalize_str(normalized_input) + + # `[μµ]` in `English Name` should be replaced in to `u`. + # convert u before kakasi applied to keep u instead of M + normalized_input = re.sub(r'[μµ]', 'uu', normalized_input) + + # convert to romaji + # normalized_input = p.do(normalized_input) + normalized_input = conv.romaji(normalized_input, title=True) + + # snake to camel + # normalized_input = normalized_input.title() + + # remove space and tab + normalized_input = re.sub(r'[\s\t\+\*…・:;!\?\$\&\"\'\`\=\@\#\\\/。、\.,~\|]', '', normalized_input) + + # `[\(\)\[\]<>\{\}【】]` in string in `English Name` should be replaced into `_`. + normalized_input = re.sub(r'[\(\)\[\]<>\{\}【】]', '_', normalized_input) + + # hyphen: remove last, multi + normalized_input = re.sub(r'-+', '-', normalized_input) + normalized_input = re.sub(r'_+', '_', normalized_input) + + # under score: remove first , last, multi + normalized_input = re.sub(r'^[_-]', '', normalized_input) + normalized_input = re.sub(r'[_-]$', '', normalized_input) + + # `[℃°]` in `English Name` should be replaced in to `deg`. + normalized_input = re.sub(r'[℃°]', 'deg', normalized_input) + + # `℉` in `English Name` should be replaced in to `degF`. + normalized_input = re.sub(r'℉', 'degF', normalized_input) + normalized_input = re.sub(r'%', 'pct', normalized_input) + + # `[Δ, △]` in English Name should be replaced into `d`. + normalized_input = re.sub(r'[Δ△]', 'd', normalized_input) + + # `Ω` in English Name should be replaced into `ohm`. + normalized_input = re.sub(r'Ω', 'ohm', normalized_input) + + # convert postal mark in string to `_` + normalized_input = re.sub(r'[\u3012\u3020\u3036]', 'post', normalized_input) + + # # replace for μµ + normalized_input = re.sub(r'Uu|uu', 'u', normalized_input) + + # `Mm` in English Name should be replaced into `mm`. + normalized_input = re.sub(r'Mm', 'mm', normalized_input) + + return normalized_input + + +# def remove_irregular(input_str): +# """ +# Use this function to remove irregular string +# Applied for half-width katakana, number in symbol, and other irregular string +# Eg: カ、①、〒 +# :param input_str: input string +# :return: string without irregular symbol +# """ +# +# regular_rule = r'[^\uFF01-\uFF5E\u3041-\u3096\u30A0-\u30FF\u3400-\u4DB5\u4E00-\u9FCB\uF900-\uFA6A0-9a-zA-Z _+-:.]' +# irregular_str = ('⓪', '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', +# '⑪', '⑫', '⑬', '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', +# '❶', '❷', '❸', '❹', '❺', '❻', '❼', '❽', '❾', '❿', +# '⓫', '⓬', '⓭', '⓮', '⓯', '⓰', '⓱', '⓲', '⓳', '⓴', +# '➀', '➁', '➂', '➃', '➄', '➅', '➆', '➇', '➈', '➉', +# '➊', '➋', '➌', '➍', '➎', '➏', '➐', '➑', '➒', '➓', +# '⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾') +# regular_str = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', +# '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', +# '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', +# '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', +# '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', +# '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', +# '1', '2', '3', '4', '5', '6', '7', '8', '9', '10') +# +# def convert_irregular(element): +# if (element in irregular_str): +# return regular_str[irregular_str.index(element)] +# return element +# +# try: +# # input_str = "GD-I4 調整検査7号あア①〒㋐ア" +# # normalize japanese string +# input_str = jaconv.h2z(input_str) +# input_str = jaconv.normalize(input_str) +# # convert special characters +# output_str = "".join([convert_irregular(i) for i in input_str]) +# output_str = re.sub(regular_rule, '_', output_str) +# # output_str = 'GD-I4 調整検査7号あア1_アア' +# return output_str +# except Exception: +# return input_str + + +def change_duplicated_columns(columns): + col_name = [col['romaji'] for col in columns] + duplicated_items = [item for item, count in collections.Counter(col_name).items() if count > 1] + + idx = 1 + duplicated = False + if duplicated_items: + duplicated = True + for col in columns: + if col['romaji'] in duplicated_items: + col['romaji'] += "_" + str(idx) + idx += 1 + return columns, duplicated diff --git a/histview2/common/services/normalization.py b/histview2/common/services/normalization.py new file mode 100644 index 0000000..29ee575 --- /dev/null +++ b/histview2/common/services/normalization.py @@ -0,0 +1,86 @@ +import re +import unicodedata + +import pandas as pd +from pandas import DataFrame + +from histview2.common.logger import log_execution_time + +NORMALIZE_FORM = 'NFKC' +ZEN_SPACE = ' ' +HAN_SPACE = ' ' +REPLACE_PAIRS = (('°C', '℃'), ('°F', '℉')) +DIC_IGNORE_NORMALIZATION = {'cfg_data_source_csv': ['directory', 'etl_func'], + 'cfg_data_source_db': ['host', 'dbname', 'schema', 'username', 'password'] + } + + +def unicode_normalize_nfkc(text): + # normalize (also replace Full Space to Half Space) + text = unicodedata.normalize(NORMALIZE_FORM, text) + # replace multi-spaces + text = re.sub(r'\s+', HAN_SPACE, text) + # trim space + text = text.strip() + # replace ℃ + for replace_from, replace_to in REPLACE_PAIRS: + text = text.replace(replace_from, replace_to) + + return text + + +@log_execution_time() +def normalize_str(val): + return unicode_normalize_nfkc(val) if isinstance(val, str) else val + + +@log_execution_time() +def normalize_list(vals): + return [normalize_str(val) for val in vals] + + +@log_execution_time() +def normalize_df(df: DataFrame, col): + if df[col].dtype.name.lower() != 'string': + df[col] = df[col].astype(str) + + df[col] = df[col].str.normalize(NORMALIZE_FORM) + df[col] = df[col].str.replace(r'\s+', HAN_SPACE, regex=True) + df[col] = df[col].str.strip() + + for replace_from, replace_to in REPLACE_PAIRS: + df[col] = df[col].str.replace(replace_from, replace_to) + + +@log_execution_time() +def normalize_big_rows(rows, headers=None, strip_quote=True, return_dataframe=True): + df = pd.DataFrame(rows, columns=headers) + for col in df.columns: + if strip_quote: + df[col] = df[col].str.strip("'") + + normalize_df(df, col) + + if return_dataframe: + return df + + return df.to_records(index=False).tolist() + + +def is_ignore_column(table_name, column_name): + if column_name in DIC_IGNORE_NORMALIZATION.get(table_name, []): + return True + + return False + + +def model_normalize(target): + table_name = target.__class__.__table__.name + cols = target.__class__.__table__.columns.keys() + for col in cols: + if is_ignore_column(table_name, col): + continue + + val = getattr(target, col) + new_val = normalize_str(val) + setattr(target, col, new_val) diff --git a/histview2/common/services/request_time_out_handler.py b/histview2/common/services/request_time_out_handler.py new file mode 100644 index 0000000..f3c5e57 --- /dev/null +++ b/histview2/common/services/request_time_out_handler.py @@ -0,0 +1,50 @@ +import os +import time +from flask import g +from functools import wraps +from histview2.common.constants import appENV + +class requestTimeOutAPI(Exception): + status_code = 408 + + def __init__(self, message, status_code=None, payload=None, response=None): + super().__init__() + self.message = message + if status_code is not None: + self.status_code = status_code + self.payload = payload + self.response = response + + def to_dict(self): + rv = dict(self.payload or ()) + rv['message'] = self.message + return rv + + +def request_timeout_handling(max_timeout=1): + """ Decorator to log function run time + Arguments: + fn {function} -- [description] + max_timeout {number} -- tracing timeout (minutes) + Returns: + fn {function} -- [description] + """ + def decorator(fn): + @wraps(fn) + def wrapper(*args, **kwargs): + result = fn(*args, **kwargs) + + current_env = os.environ.get('ANALYSIS_INTERFACE_ENV', appENV.DEVELOPMENT.value) + if (current_env == appENV.PRODUCTION.value): + start_func_time = time.time() + request_start_time = getattr(g, 'request_start_time', None) + if (request_start_time): + timeout = (start_func_time - request_start_time) / 60 # to minutes + if (timeout > max_timeout): + raise requestTimeOutAPI('Request timeout: {} seconds'.format(str(timeout))) + + return result + + return wrapper + + return decorator diff --git a/histview2/common/services/sse.py b/histview2/common/services/sse.py new file mode 100644 index 0000000..38c1e6e --- /dev/null +++ b/histview2/common/services/sse.py @@ -0,0 +1,71 @@ +import json +import queue +from enum import Enum, auto +from functools import wraps + +from histview2.common.services import http_content + + +class AnnounceEvent(Enum): + JOB_RUN = auto() + PROC_LINK = auto() + SHUT_DOWN = auto() + DATA_TYPE_ERR = auto() + EMPTY_FILE = auto() + PCA_SENSOR = auto() + SHOW_GRAPH = auto() + DISK_USAGE = auto() + + +class MessageAnnouncer: + + def __init__(self): + self.listeners = [] + + def listen(self): + self.listeners.append(queue.Queue(maxsize=10)) + return self.listeners[-1] + + @staticmethod + def format_sse(data, event=None) -> str: + """Formats a string and an event name in order to follow the event stream convention. + format_sse(data=json.dumps({'abc': 123}), event='Jackson 5') + 'event: Jackson 5\\ndata: {"abc": 123}\\n\\n' + """ + msg = json.dumps(data, ensure_ascii=False, default=http_content.json_serial) + msg = f'data: {msg}\n\n' + if event is not None: + msg = f'event: {event}\n{msg}' + return msg + + def announce(self, data, event): + # We go in reverse order because we might have to delete an element, which will shift the + # indices backward + msg = self.format_sse(data, event) + for i in reversed(range(len(self.listeners))): + try: + self.listeners[i].put_nowait(msg) + except queue.Full: + del self.listeners[i] + + +# background job status +background_announcer = MessageAnnouncer() + + +def notify_progress(percent): + """ Decorator to notify progress + """ + + def decorator(fn): + @wraps(fn) + def wrapper(*args, **kwargs): + try: + result = fn(*args, **kwargs) + background_announcer.announce(percent, AnnounceEvent.SHOW_GRAPH.name) + except Exception as e: + raise e + + return result + return wrapper + return decorator diff --git a/histview2/common/services/statistics.py b/histview2/common/services/statistics.py new file mode 100644 index 0000000..ceb1148 --- /dev/null +++ b/histview2/common/services/statistics.py @@ -0,0 +1,339 @@ +import numpy as np +import pandas as pd +from pandas import Series + +from histview2.common.common_utils import DATE_FORMAT_STR, reformat_dt_str, calc_overflow_boundary +from histview2.common.constants import * +from histview2.common.logger import log_execution_time +from histview2.common.services.request_time_out_handler import request_timeout_handling +from histview2.common.sigificant_digit import signify_digit + + +@log_execution_time() +def calc_summary_elements(plot): + none_ids = plot.get(NONE_IDXS) + array_y = plot.get(ARRAY_Y) or [] + array_x = plot.get(ARRAY_X) or [] + df = pd.DataFrame({ARRAY_X: array_x, ARRAY_Y: array_y}) + # must check None , because [] is no None value + if none_ids is None: + pass + else: + if none_ids: + df = df[(df[ARRAY_Y].notnull()) | (df.index.isin(none_ids))] + else: + df.dropna(subset=[ARRAY_Y], inplace=True) + + chart_infos = plot.get(CHART_INFOS_ORG) or [{}] + empty_summary = { + 'count': {}, + 'basic_statistics': {}, + 'non_parametric': {} + } + if not len(df) or df[ARRAY_Y].dtype.name.lower() in ['string', 'object']: + return [empty_summary for _ in chart_infos] + + summaries = [] + array_y_num = df.loc[np.isfinite(df[ARRAY_Y]), ARRAY_Y] + + # calc overflow lower/upper + num_over_upper = None + num_over_lower = None + overflow_lower, overflow_upper = calc_overflow_boundary(array_y_num) + if overflow_upper is not None and overflow_lower is not None: + overflow_upper = float(overflow_upper) + overflow_lower = float(overflow_lower) + num_over_upper = array_y_num[array_y_num > overflow_upper].size + num_over_lower = array_y_num[array_y_num < overflow_lower].size + + x_not_none = df.loc[df[ARRAY_X].notnull(), ARRAY_X] + min_x = x_not_none.min() + max_x = x_not_none.max() + for chart_info in chart_infos: + if not len(x_not_none): + summaries.append(empty_summary) + continue + + # param + th_high = chart_info.get(THRESH_HIGH) + th_low = chart_info.get(THRESH_LOW) + th_high_process = chart_info.get(PRC_MAX) + th_low_process = chart_info.get(PRC_MIN) + + act_from = chart_info.get(ACT_FROM) + act_from_formatted = reformat_dt_str(act_from, DATE_FORMAT_STR) + if not act_from_formatted: + act_from_formatted = reformat_dt_str('1970-01-01', DATE_FORMAT_STR) + + act_to = chart_info.get(ACT_TO) + act_to_formatted = reformat_dt_str(act_to, DATE_FORMAT_STR) + if not act_to_formatted: + act_to_formatted = reformat_dt_str('9999-01-01', DATE_FORMAT_STR) + + if (not act_from and not act_to) or (act_from_formatted <= min_x and max_x <= act_to_formatted): + df_threshold = df + else: + df_threshold = df[(df[ARRAY_X] >= act_from_formatted) & (df[ARRAY_X] < act_to_formatted)] + + count_unlinked = len(df_threshold[df_threshold[ARRAY_X].isnull()]) + + array_y_th = df_threshold.loc[np.isfinite(df_threshold[ARRAY_Y]), ARRAY_Y] + mode = get_mode(array_y_th) + + # count process + ntotal = len(df_threshold) + p = None + p_minus = None + p_plus = None + pn_minus = 0 + pn_plus = 0 + if th_high is not None: + pn_plus = array_y_th[array_y_th >= th_high].size + if th_low is not None: + pn_minus = array_y_th[array_y_th <= th_low].size + pn = pn_minus + pn_plus + if ntotal: + p = (pn / ntotal) * 100 + p_minus = (pn_minus / ntotal) * 100 + p_plus = (pn_plus / ntotal) * 100 + + p_proc = 0 + p_proc_minus = 0 + p_proc_plus = 0 + pn_proc_minus = 0 + pn_proc_plus = 0 + if th_high_process is not None: + pn_proc_plus = array_y_th[array_y_th >= th_high_process].size + if th_low_process is not None: + pn_proc_minus = array_y_th[array_y_th <= th_low_process].size + pn_proc = pn_proc_minus + pn_proc_plus + if ntotal: + p_proc = (pn_proc / ntotal) * 100 + p_proc_minus = (pn_proc_minus / ntotal) * 100 + p_proc_plus = (pn_proc_plus / ntotal) * 100 + + # noneを事前に分ける必要あり + null_idxs = df_threshold[ARRAY_Y].isnull() + array_y_null = df_threshold.loc[null_idxs, ARRAY_Y] + array_y_not_null = df_threshold.loc[~null_idxs, ARRAY_Y] + pn_na = len(array_y_null) - count_unlinked + pn_nan = 0 + + pn_inf = (array_y_not_null == float('inf')).sum() + pn_neg_inf = (array_y_not_null == -float('inf')).sum() + p_na = 0 + p_inf = 0 + p_nan = 0 + p_neg_inf = 0 + len_array_y_all = len(df_threshold) + if len_array_y_all: + p_na = (pn_na / len_array_y_all) * 100 + p_inf = (pn_inf / len_array_y_all) * 100 + p_nan = (pn_nan / len_array_y_all) * 100 + p_neg_inf = (pn_neg_inf / len_array_y_all) * 100 + + p_max = max((pn or 0), (pn_proc or 0)) + n_abnormal = pn_na + pn_nan + pn_inf + pn_neg_inf + pn_total = p_max + n_abnormal + # Calc the N in basic statistics + # `N` is number of data points except a number of NA, NaN, Inf+/- + # but including number of outCL and outAL + n_stats = max(ntotal - n_abnormal, 0) + + p_total = None + if ntotal: + p_total = (pn_total / ntotal) * 100 + + # normal distribution process + average = None + sigma = None + sigma_3 = None + if array_y_th.size: + average = array_y_th.mean() + sigma = np.std(array_y_th, ddof=1) # 不変標準偏差を使っています + sigma_3 = sigma * 3 + + cp = None + cpk = None + if th_high is not None and th_low is not None and sigma: + cp = (th_high - th_low) / (6 * sigma) + cpk = min((th_high - average) / sigma_3, (average - th_low) / sigma_3) + + max_input_arr = None + min_input_arr = None + p95 = None + p75 = None # Q3 + median = None + p25 = None # Q1 + p5 = None + + if array_y_th.size: + max_input_arr = array_y_th.max() + min_input_arr = array_y_th.min() + + iqr = None + whisker_lower = None + whisker_upper = None + lower_range = None + upper_range = None + trimmed_average = None + trimmed_sigma = None + trimmed_sigma_3 = None + t_cp = None + t_cpk = None + t_max_input_arr = None + t_min_input_arr = None + t_n_stats = 0 + if array_y_th.size: + # non parametric process + p5, p25, median, p75, p95 = np.percentile(array_y_th, [5, 25, 50, 75, 95]) + iqr = p75 - p25 + whisker_lower = p25 - 1.5 * iqr + whisker_upper = p75 + 1.5 * iqr + + # get trimming data from Q1 - 2.5*IQR + # to Q3 + 2.5*IQR + lower_range = p25 - 2.5 * iqr + upper_range = p75 + 2.5 * iqr + trimmed_array_y = array_y_th[(array_y_th >= lower_range) & (array_y_th <= upper_range)] + trimmed_average = trimmed_array_y.mean() + trimmed_sigma = np.std(trimmed_array_y, ddof=1) # 不変標準偏差を使っています + trimmed_sigma_3 = trimmed_sigma * 3 + t_n_stats = trimmed_array_y.size + t_max_input_arr = trimmed_array_y.max() + t_min_input_arr = trimmed_array_y.min() + + if th_high is not None and th_low is not None and trimmed_sigma: + t_cp = (th_high - th_low) / (6 * trimmed_sigma) + t_cpk = min((th_high - trimmed_average) / trimmed_sigma_3, + (trimmed_average - th_low) / trimmed_sigma_3) + + avg_p_3sigma = None + avg_p_sigma = None + avg_m_sigma = None + avg_m_3sigma = None + t_avg_p_3sigma = None + t_avg_p_sigma = None + t_avg_m_sigma = None + t_avg_m_3sigma = None + if average is not None and sigma is not None: + avg_p_3sigma = average + sigma_3 + avg_p_sigma = average + sigma + avg_m_sigma = average - sigma + avg_m_3sigma = average - sigma_3 + t_avg_p_3sigma = trimmed_average + trimmed_sigma_3 + t_avg_p_sigma = trimmed_average + trimmed_sigma + t_avg_m_sigma = trimmed_average - trimmed_sigma + t_avg_m_3sigma = trimmed_average - trimmed_sigma_3 + + linked_pct = (ntotal - count_unlinked) * 100 / (ntotal or 1) + linked_pct = signify_digit(linked_pct) + unlinked_pct = signify_digit(100 - linked_pct) + + niqr = (iqr * 0.7413) if iqr else None + summaries.append({ + 'count': { + 'ntotal': ntotal, + 'count_unlinked': count_unlinked, + 'pn': signify_digit(pn), + 'pn_minus': signify_digit(pn_minus), + 'pn_plus': signify_digit(pn_plus), + 'p': signify_digit(p), + 'p_minus': signify_digit(p_minus), + 'p_plus': signify_digit(p_plus), + 'pn_proc': signify_digit(pn_proc), + 'pn_proc_minus': signify_digit(pn_proc_minus), + 'pn_proc_plus': signify_digit(pn_proc_plus), + 'p_proc': signify_digit(p_proc), + 'p_proc_minus': signify_digit(p_proc_minus), + 'p_proc_plus': signify_digit(p_proc_plus), + 'pn_na': signify_digit(pn_na), + 'p_na': signify_digit(p_na), + 'pn_nan': signify_digit(pn_nan), + 'p_nan': signify_digit(p_nan), + 'pn_inf': signify_digit(pn_inf), + 'p_inf': signify_digit(p_inf), + 'pn_neg_inf': signify_digit(pn_neg_inf), + 'p_neg_inf': signify_digit(p_neg_inf), + 'pn_total': signify_digit(pn_total), + 'p_total': signify_digit(p_total), + 'linked_pct': linked_pct, + 'no_linked_pct': unlinked_pct, + }, + 'basic_statistics': { + 'n_stats': signify_digit(n_stats), + 't_n_stats': signify_digit(t_n_stats), + 'average': signify_digit(average), + 't_average': signify_digit(trimmed_average), + 'sigma': signify_digit(trimmed_sigma), + 't_sigma': signify_digit(sigma), + 'sigma_3': signify_digit(sigma_3), + 't_sigma_3': signify_digit(trimmed_sigma_3), + 'Cp': signify_digit(cp), + 'Cpk': signify_digit(cpk), + 't_cp': signify_digit(t_cp), + 't_cpk': signify_digit(t_cpk), + 'Max': signify_digit(max_input_arr), + 't_max': signify_digit(t_max_input_arr), + 'max_org': max_input_arr, + 'Min': signify_digit(min_input_arr), + 't_min': signify_digit(t_min_input_arr), + 'min_org': min_input_arr, + 'avg_p_3sigma': signify_digit(avg_p_3sigma), + 'avg_p_sigma': signify_digit(avg_p_sigma), + 'avg_m_sigma': signify_digit(avg_m_sigma), + 'avg_m_3sigma': signify_digit(avg_m_3sigma), + 't_avg_p_3sigma': signify_digit(t_avg_p_3sigma), + 't_avg_p_sigma': signify_digit(t_avg_p_sigma), + 't_avg_m_sigma': signify_digit(t_avg_m_sigma), + 't_avg_m_3sigma': signify_digit(t_avg_m_3sigma), + + }, + 'non_parametric': { + 'p95': signify_digit(p95), + 'p75': signify_digit(p75), + 'p75_org': p75, + 'median': signify_digit(median), + 'median_org': median, + 'p25': signify_digit(p25), + 'p25_org': p25, + 'whisker_lower': signify_digit(whisker_lower), + 'whisker_lower_org': whisker_lower, + 'whisker_upper': signify_digit(whisker_upper), + 'whisker_upper_org': whisker_upper, + 'p5': signify_digit(p5), + 'num_over_upper': signify_digit(num_over_upper), + 'num_over_lower': signify_digit(num_over_lower), + 'iqr': signify_digit(iqr), + 'niqr': signify_digit(niqr), + 'lower_range': signify_digit(lower_range), + 'lower_range_org': lower_range, + 'upper_range': signify_digit(upper_range), + 'upper_range_org': upper_range, + 'mode': signify_digit(mode), + } + }) + + return summaries + + +@log_execution_time() +@request_timeout_handling() +def calc_summaries(dic_param): + for plot in dic_param['array_plotdata'] or []: + plot[SUMMARIES] = calc_summary_elements(plot) + + +@log_execution_time() +def calc_summaries_cate_var(dic_param): + array_plotdatas = dic_param['array_plotdata'] or {} + for end_col, plotdatas in array_plotdatas.items(): + for plotdata in plotdatas or []: + plotdata[SUMMARIES] = calc_summary_elements(plotdata) + + +def get_mode(series: Series): + try: + return series.mode()[0] + except Exception: + return None diff --git a/histview2/common/sigificant_digit.py b/histview2/common/sigificant_digit.py new file mode 100644 index 0000000..5a49ac7 --- /dev/null +++ b/histview2/common/sigificant_digit.py @@ -0,0 +1,144 @@ +import math + +import numpy as np +from loguru import logger + +from histview2.common.constants import ARRAY_PLOTDATA, ARRAY_Y +from histview2.common.logger import log_execution_time +from histview2.common.services.request_time_out_handler import request_timeout_handling + + +def signify_digit_pca(x, sig_dig=4): + """ + Signify a number array. + :param x: + :param sig_dig: + :return: Signified numbers. + """ + if sig_dig < 1: + sig_dig = 1 + elif sig_dig > 6: + sig_dig = 6 + + digit = np.floor(np.log10(abs(x))).astype(int) + if digit < -3 or 6 < digit: + fmt = "{:." + str(sig_dig - 1) + "e}" + elif isinstance(x, int) or isinstance(x, float) and x.is_integer(): + x = int(x) + fmt = "{:,d}" + elif digit > sig_dig - 3: + fmt = "{:,.1f}" + else: + fmt = "{:,." + (sig_dig - digit - 1).astype(str) + "f}" + return fmt.format(x) + + +def signify_digit_pca_vector(input_array, sig_dig=4): + """ + Signify an array. + :param input_array: + :param sig_dig: + :return: Array of signified numbers. + """ + if not input_array: + return [] + + try: + vsig_digits = np.vectorize(signify_digit_pca) + signified_digits = vsig_digits(np.array(input_array), sig_dig=sig_dig) + except Exception: + signified_digits = input_array + + return signified_digits + + +# def signification_digit(n, sig_digit=4): +# if n and n > 0: +# sd = np.round(n * 10**(-1 * np.floor(np.log10(n))), sig_digit-1) * 10**(np.floor(np.log10(n))) +# +# sd_splited = str(sd).split('.') +# if len(sd_splited[0]) > sig_digit: +# return int(sd_splited[0]) +# return np.round(sd, sig_digit - len(sd_splited[0])) +# if np.isnan(n): +# n = np.where(np.isnan(n), None, n) +# return n + + +def signify_digit2(n, sig_digit=4): + if not isinstance(n, float): + return n + + try: + return round(n, sig_digit - int(math.floor(math.log10(abs(n)))) - 1) + except: + if n is None or np.isnan(n): + return None + return n + + +def signify_digit(n, sig_digit=4): + if not n or isinstance(n, int): # None -> None, 0 -> 0 + return n + if np.isnan(n): # NaN -> None + return None + + res = n + try: + n_abs = abs(n) + if n_abs: + sd = np.round(n_abs * 10 ** (-1 * np.floor(np.log10(n_abs))), sig_digit - 1) * 10 ** ( + np.floor(np.log10(n_abs))) + sd_splited = str(sd).split('.') + if len(sd_splited[0]) > sig_digit: + res = int(sd_splited[0]) + else: + res = np.round(sd, sig_digit - len(sd_splited[0])) + if n < 0: + return -res + # Python automatically convert 0.0000001 to '1e-07' -> error -> Fixed by using try-catch to handle + except Exception as ex: + logger.exception(ex) + return n + return res + + +@log_execution_time() +def signify_digit_vector(input_array, sig_dig=4): + """ + Signify an array. + :param input_array: + :param sig_dig: + :return: Array of signified numbers. + """ + if input_array is None: + return [] + + try: + # TODO use vector + vsig_digits = np.vectorize(signify_digit2) + signified_digits = vsig_digits(np.array(input_array), sig_digit=sig_dig) + return signified_digits.tolist() + + # return [signify_digit2(x, sig_dig) if isinstance(x, float) else x for x in input_array] + except Exception as ex: + logger.exception(ex) + return input_array + + +@log_execution_time() +@request_timeout_handling() +def round_data(dic_param): + array_plotdata = dic_param.get(ARRAY_PLOTDATA) + for num, plotdata in enumerate(array_plotdata): + plotdata[ARRAY_Y] = signify_digit_vector(plotdata.get(ARRAY_Y), sig_dig=4) + # plotdata[Y_MAX] = signify_digit(plotdata.get(Y_MAX)) + # plotdata[Y_MIN] = signify_digit(plotdata.get(Y_MIN)) + + +@log_execution_time() +def round_data_stp(dic_param): + array_plotdatas = dic_param.get(ARRAY_PLOTDATA) or {} + for end_col, plotdatas in array_plotdatas.items(): + for plotdata in plotdatas or []: + plotdata[ARRAY_Y] = signify_digit_vector(plotdata[ARRAY_Y], sig_dig=4) diff --git a/histview2/common/timezone_utils.py b/histview2/common/timezone_utils.py new file mode 100644 index 0000000..70577e3 --- /dev/null +++ b/histview2/common/timezone_utils.py @@ -0,0 +1,274 @@ +import re +from datetime import datetime +from datetime import timedelta, timezone +from functools import partial + +# import pytz +from dateutil import parser, tz +from loguru import logger + +from histview2.common.common_utils import DATE_FORMAT_STR, DATE_FORMAT_STR_CSV, add_double_quotes +from histview2.common.logger import log_execution_time +from histview2.common.pydn.dblib import oracle, mssqlserver, mysql + + +@log_execution_time() +def detect_timezone(dt_input): + """ + Detect timezone of a datetime string. e.g. 2019-05-14T09:01:04.000000Z/2019-05-14T09:01:04.000000,... + Return timezone object + """ + try: + if isinstance(dt_input, str): + dt = parser.parse(dt_input) + return dt.tzinfo + elif isinstance(dt_input, datetime): + return dt_input.tzinfo + except Exception as ex: + logger.error(ex) + return None + + +# def convert_str_utc_by_timezone_pytz(time_zone: pytz.tzinfo.DstTzInfo, dt_str): +# """ +# timezone: pytz.tzinfo.DstTzInfo object. e.g. pytz.timezone('Asia/Tokyo') +# dt_str: datetime string: e.g.2019-05-14T20:01:04.000000Z +# """ +# try: +# dt_obj = parser.parse(dt_str) +# dt_obj = time_zone.localize(dt_obj) +# dt_obj = dt_obj.astimezone(tz.tzutc()) +# except Exception: +# return None +# +# return datetime.strftime(dt_obj, DATE_FORMAT_STR) +# +# +def convert_str_utc_by_timezone(time_zone, dt_str): + """ + timezone: timezone object. e.g. dateutil.tz.tzutc(), dateutil.tz.tzlocal() + dt_str: datetime string: e.g.2019-05-14T20:01:04.000000Z + """ + try: + dt_obj = parser.parse(dt_str) + dt_obj = dt_obj.replace(tzinfo=time_zone) + dt_obj = dt_obj.astimezone(tz.tzutc()) + except Exception: + return None + + return datetime.strftime(dt_obj, DATE_FORMAT_STR) + + +def convert_dt_str_to_timezone(time_zone, dt_str): + """ + timezone: timezone object. e.g. dateutil.tz.tzutc(), dateutil.tz.tzlocal() + dt_str: datetime string: e.g.2019-05-14T20:01:04.000000Z + """ + if not dt_str: + return dt_str + + try: + dt_obj = parser.parse(dt_str) + dt_obj = dt_obj.astimezone(time_zone) + except Exception: + return None + + return datetime.strftime(dt_obj, DATE_FORMAT_STR_CSV) + + +def convert_str_utc(dt_str): + """ + dt_str: datetime string: e.g.2019-05-14T20:01:04.000000Z + """ + try: + dt_obj = parser.parse(dt_str) + dt_obj = dt_obj.astimezone(tz.tzutc()) + except Exception: + return None + + return datetime.strftime(dt_obj, DATE_FORMAT_STR) + + +def convert_date_utc_by_timezone(time_zone, date_obj): + """ + timezone: timezone object. e.g. dateutil.tz.tzutc(), dateutil.tz.tzlocal() + date_obj: date object: e.g.2019-05-14 + """ + date_obj = datetime.combine(date_obj, datetime.min.time()) + date_obj = date_obj.replace(tzinfo=time_zone) + date_obj = date_obj.astimezone(tz.tzutc()) + + return datetime.strftime(date_obj, DATE_FORMAT_STR) + + +# def convert_dt_utc_by_timezone_pytz(time_zone: pytz.tzinfo.DstTzInfo, dt_obj): +# """ +# timezone: pytz.tzinfo.DstTzInfo object. e.g. pytz.timezone('Asia/Tokyo') +# dt_obj: datetime object: e.g.2019-05-14T20:01:04.000000Z +# """ +# dt_obj = time_zone.localize(dt_obj) +# dt_obj = dt_obj.astimezone(tz.tzutc()) +# +# return datetime.strftime(dt_obj, DATE_FORMAT_STR) +# +# +def convert_dt_utc_by_timezone(time_zone, dt_obj): + """ + timezone: timezone object. e.g. dateutil.tz.tzutc(), dateutil.tz.tzlocal() + dt_obj: datetime object: e.g.2019-05-14T20:01:04.000000Z + """ + dt_obj = dt_obj.replace(tzinfo=time_zone) + dt_obj = dt_obj.astimezone(tz.tzutc()) + + return datetime.strftime(dt_obj, DATE_FORMAT_STR) + + +def convert_other_utc_by_timezone(time_zone, val): + """ + Return the same value for types that are not str/datetime + Note: Don't remove time_zone param + """ + return val + + +def convert_dt_utc(dt_obj): + """ + dt_str: datetime string: e.g.2019-05-14T20:01:04.000000Z + """ + dt_obj = dt_obj.astimezone(tz.tzutc()) + + return datetime.strftime(dt_obj, DATE_FORMAT_STR) + + +def keep_same_val(val): + return val + + +def choose_utc_convert_func(date_val, time_zone=None): + # detect timezone + detected_timezone = detect_timezone(date_val) + + if detected_timezone: + # convert utc time func + if isinstance(date_val, str): + convert_utc_func = convert_str_utc + elif isinstance(date_val, datetime): + convert_utc_func = convert_dt_utc + else: + convert_utc_func = keep_same_val + else: + detected_timezone = time_zone or tz.tzlocal() + # convert utc time func + if isinstance(date_val, str): + convert_utc_func = partial(convert_str_utc_by_timezone, detected_timezone) + elif isinstance(date_val, datetime): + convert_utc_func = partial(convert_dt_utc_by_timezone, detected_timezone) + else: + convert_utc_func = partial(convert_other_utc_by_timezone, None) + + return convert_utc_func, detected_timezone + + +def gen_sql(db_instance, table_name, get_date_col): + get_date_col = add_double_quotes(get_date_col) + if not isinstance(db_instance, mysql.MySQL): + table_name = add_double_quotes(table_name) + + sql = f'from {table_name} where {get_date_col} is not null' + if isinstance(db_instance, mssqlserver.MSSQLServer): + sql = f'select TOP 1 {get_date_col}, 0 {sql}' + elif isinstance(db_instance, oracle.Oracle): + data_type = db_instance.get_data_type_by_colname(table_name.strip('\"'), get_date_col.strip('\"')) + format_str = 'TZR' if data_type and 'TIME ZONE' in data_type else None + if format_str: + sql = f"select {get_date_col}, to_char({get_date_col},'{format_str}') {sql} and rownum = 1" + else: + sql = f'select {get_date_col}, 0 {sql} and rownum = 1' + else: + sql = f'select {get_date_col}, 0 {sql} limit 1' + + return sql + + +def calc_offset_between_two_tz(from_tz, to_tz): + cur_datetime = datetime.now() + + dt_frm = cur_datetime.replace(tzinfo=from_tz) + dt_frm = dt_frm.astimezone(tz.tzutc()) + + dt_to = cur_datetime.replace(tzinfo=to_tz) + dt_to = dt_to.astimezone(tz.tzutc()) + + # get timezone offset + if dt_to == dt_frm: + return None + + if dt_to >= dt_frm: + sign = '+' + diff_tm = str(dt_to - dt_frm) + else: + sign = '-' + diff_tm = str(dt_frm - dt_to) + + if diff_tm[1] == ':': + sign += '0' + + diff_tm = sign + diff_tm + + return diff_tm + + +def get_db_timezone(db_instance): + # get timezone offset + tz_offset = db_instance.get_timezone() + db_timezone = timezone_regex(tz_offset) + + return db_timezone + + +def timezone_regex(tz_offset): + if isinstance(tz_offset, str): + matches = re.match('^([+\-]?)(\d{1,2}):?(\d{2})?', tz_offset) + + if matches: + sign, hours, minutes = matches.groups() + time_zone = timezone(timedelta(hours=float('{}{}'.format(sign, hours)), minutes=float(minutes or 0))) + else: + time_zone = tz.gettz(tz_offset or None) or tz.tzlocal() + # time_zone = pytz.timezone(tz_offset) + else: + time_zone = tz_offset + + return time_zone + + +@log_execution_time() +def get_utc_offset(time_zone): + """ + get utc time offset + :param time_zone: str, timezone object + :return: timedelta(seconds) + """ + if isinstance(time_zone, str): + time_zone = tz.gettz(time_zone) + + time_in_tz = datetime.now(tz=time_zone) + time_offset = time_in_tz.utcoffset().seconds + time_offset = timedelta(seconds=time_offset) + + return time_offset + + +def get_time_info(date_val, time_zone=None): + # detect timezone + detected_timezone = detect_timezone(date_val) + + if detected_timezone: + is_timezone_inside = True + else: + is_timezone_inside = False + detected_timezone = time_zone or tz.tzlocal() + + utc_offset = get_utc_offset(detected_timezone) + + return is_timezone_inside, detected_timezone, utc_offset diff --git a/histview2/common/trace_data_log.py b/histview2/common/trace_data_log.py new file mode 100644 index 0000000..01edaea --- /dev/null +++ b/histview2/common/trace_data_log.py @@ -0,0 +1,322 @@ +import http.client +import time +from enum import Enum, auto +from functools import wraps +from inspect import getgeneratorstate +from urllib.parse import urlencode + +from flask import current_app, g +from pandas import DataFrame + +from histview2.common.common_utils import create_file_path, write_to_pickle +from histview2.common.constants import MPS, FlaskGKey, CsvDelimiter +from histview2.common.logger import log_execution_time + +# some environment can not access to google analytics , +# in this case send ga will be stoped in the next time +is_send_google_analytics = True + + +def _gen_dataset_id(): + from histview2.setting_module.models import DataTraceLog + dataset_id = DataTraceLog.get_max_id() + while True: + dataset_id += 1 + yield dataset_id + + +# to gen unique dataset id , use this func +gen_dataset_id_inst = _gen_dataset_id() + + +def send_gtag(**kwargs): + try: + GA_TRACKING_ID = current_app.config.get('GA_TRACKING_ID') + data = { + 'v': '1', # API Version. + 'tid': GA_TRACKING_ID, # Tracking ID / Property ID. + # Anonymous Client Identifier. Ideally, this should be a UUID that + # is associated with particular user, device, or browser instance. + 'cid': '555', + 't': 'event', # Event hit type. + } + data.update(kwargs) + conn = http.client.HTTPSConnection(MPS) + payload = '' + headers = {} + querystring = urlencode(data) + print(querystring) + conn.request("POST", "/collect?" + querystring, payload, headers) + res = conn.getresponse() + return res.status + except Exception: + return False + + +class EventCategory(Enum): + EXEC_TIME = 'ExecTime' + INPUT_DATA = 'InputData' + + +class EventType(Enum): + PCA = 'PCA' + MSP = 'MSP' + FPP = 'FPP' + STP = 'StP' + RLP = 'RLP' + SKD = 'SkD' + PCP = 'PCP' + CHM = 'CHM' + SCP = 'ScP' + + +class EventAction(Enum): + READ = 'Read' + SAVE = 'Save' + CALLPKG = 'CallPkg' + PLOT = 'Plot' + + +class Target(Enum): + TSV = 'Tsv' + PICKLE = 'Pickle' + DATABASE = 'Database' + GRAPH = 'Graph' + + +class Location(Enum): + PYTHON = 'Mt' + JAVASCRIPT = 'Js' + R = 'R_' + + +class TraceErrKey(Enum): + DATASET = 'dataset_id' + DUMPFILE = 'dumpfile' + TYPE = 'event_type' + ACTION = 'event_action' + LOCATION = 'location' + TARGET = 'target' + CODE = 'return_code' + MSG = 'message' + DATETIME = 'date_time' + EXE_TIME = 'exe_time' + DATA_SIZE = 'data_size' + ROWS = 'rows' + COLS = 'cols' + + IS_OUTPUT = auto() + IS_EXPORT_MODE = auto() + + +class ReturnCode(Enum): + NORMAL = 'OK' + UNKNOWN_ERR = 'Unknown Error' + + +class LogLevel(Enum): + ERROR = 'ERROR' + WARNING = 'WARNING' + + +def save_trace_log_db(is_err=False, data_frame=None): + """ + save trace log to database + """ + from histview2.setting_module.models import AbnormalTraceLog, DataTraceLog + g_trace_error = _get_g_dict() + if not g_trace_error: + return + + # gen dataset id + if not get_log_attr(TraceErrKey.DATASET): + if getgeneratorstate(gen_dataset_id_inst) == 'GEN_CREATED': + next(gen_dataset_id_inst) + + set_log_attr(TraceErrKey.DATASET, next(gen_dataset_id_inst)) + + # gen dumpfile info + _set_dumpfile_details(data_frame) + + rec = AbnormalTraceLog() if is_err else DataTraceLog() + for key, val in g_trace_error.items(): + new_key = key + new_val = val + if isinstance(key, Enum): + new_key = key.value + + if isinstance(val, Enum): + new_val = val.value + + if hasattr(rec, new_key): + setattr(rec, new_key, new_val) + + from histview2.setting_module.models import make_session + with make_session() as meta_session: + meta_session.add(rec) + + +def set_log_attr(keys, vals): + """ + set error attributte + """ + if not keys: + return + + g_trace_error = _get_g_dict() + if isinstance(keys, (list, tuple)): + for key, val in zip(keys, vals): + g_trace_error[key] = val + else: + g_trace_error[keys] = vals + + +def get_log_attr(key, get_enum=False): + g_trace_error = _get_g_dict() + val = g_trace_error.get(key, None) + if not val: + return None + + if not get_enum: + if isinstance(val, Enum): + return val.value + + return val + + +def trace_log(keys=None, vals=None, save_log=True, output_key=None, send_ga=False, method_key=False): + """ + decorator to manage trace data and trace log + """ + + def _trace_log(fn): + @wraps(fn) + def __trace_log(*args, **kwargs): + global is_send_google_analytics + # set attribute first time + set_log_attr(keys, vals) + try: + st = time.time() + result = fn(*args, **kwargs) + # set attribute second time + set_log_attr(keys, vals) + et = time.time() + 0.005 # To avoid zero executive time, add 5 miliseconds + exec_time = round((et - st) * 1000) # miliseconds + + # save exec_time + set_log_attr(TraceErrKey.EXE_TIME, exec_time) + + # dumpfile + if output_key: + set_log_attr(output_key, result) + + # save trace data + if save_log: + df = None + for param in args: + if isinstance(param, DataFrame): + df = param + break + + save_trace_log_db(data_frame=df) + + # send data to GA + if is_send_google_analytics and send_ga: + is_send_google_analytics = send_google_analytic() + + except Exception as e: + # save trace error + set_log_attr(TraceErrKey.MSG, str(e)) + save_trace_log_db(is_err=True) + raise e + + return result + + return __trace_log + + return _trace_log + + +def _get_g_dict(): + """ + private function to get g object + """ + return g.setdefault(FlaskGKey.TRACE_ERR, {}) + + +def _set_dumpfile_details(data_frame: DataFrame = None): + """save dumpfile details information ( data size, rows , cols) + + Returns: + [type] -- [description] + """ + if data_frame is None: + return False + + # already get data size + if get_log_attr(TraceErrKey.COLS): + return False + + keys = (TraceErrKey.DATA_SIZE, TraceErrKey.COLS, TraceErrKey.ROWS) + data_size = int(data_frame.memory_usage(index=False, deep=True).sum()) + cols = data_frame.columns.size + rows = len(data_frame) + + vals = (data_size, cols, rows) + set_log_attr(keys, vals) + + return True + + +def send_google_analytic(): + """ + send info to google analytic + """ + event_type = get_log_attr(TraceErrKey.TYPE) + event_action = get_log_attr(TraceErrKey.ACTION) + event_location = get_log_attr(TraceErrKey.LOCATION) or Location.PYTHON.value + event_label = event_location + if event_action is EventAction.CALLPKG: + event_label += f'({get_log_attr(TraceErrKey.Target)})' + else: + event_label += event_action + + exe_time = get_log_attr(TraceErrKey.EXE_TIME) + data_size = get_log_attr(TraceErrKey.DATA_SIZE) + + send_result = send_gtag(ec=EventCategory.EXEC_TIME.value, ea=event_type + '_et', + el=event_label, ev=exe_time) + + if not send_result: + return False + + send_result = send_gtag(ec=EventCategory.INPUT_DATA.value, ea=event_type + '_ds', + el=event_label, ev=data_size) + + if not send_result: + return False + + return True + + +@log_execution_time() +@trace_log((TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventAction.SAVE, Target.PICKLE), output_key=TraceErrKey.DUMPFILE) +def save_input_data_to_file(input_form, prefix=None): + if prefix: + set_log_attr(TraceErrKey.TYPE, prefix) + + event_type = get_log_attr(TraceErrKey.TYPE) or '' + file_path = create_file_path('input_' + event_type, suffix='.pickle') + write_to_pickle(input_form, file_path) + return file_path + + +@log_execution_time() +@trace_log((TraceErrKey.ACTION, TraceErrKey.TARGET), + (EventAction.SAVE, Target.TSV), output_key=TraceErrKey.DUMPFILE) +def save_df_to_file(df: DataFrame): + event_type = get_log_attr(TraceErrKey.TYPE) or '' + file_path = create_file_path('dat_' + event_type) + df.to_csv(file_path, sep=CsvDelimiter.TSV.value, index=False) + return file_path diff --git a/histview2/common/yaml_utils.py b/histview2/common/yaml_utils.py new file mode 100644 index 0000000..ece208d --- /dev/null +++ b/histview2/common/yaml_utils.py @@ -0,0 +1,1317 @@ +import os +import traceback +from collections import OrderedDict +from itertools import chain, takewhile, zip_longest +from json import dumps, loads +from typing import List + +import ruamel.yaml as yaml +from flask import g +from ruamel.yaml import add_constructor, resolver + +from histview2 import dic_yaml_config_file +from histview2.common.common_utils import (dict_deep_merge, convert_json_to_ordered_dict, parse_int_value, + guess_data_types, clear_special_char, detect_encoding) +from histview2.common.constants import * +from histview2.common.cryptography_utils import decrypt, encrypt_db_password +from histview2.common.services.jp_to_romaji_utils import to_romaji +from histview2.setting_module.models import CfgDataSource, CfgProcess, CfgDataSourceCSV, CfgProcessColumn, \ + CfgDataSourceDB, CfgCsvColumn, CfgFilter, CfgFilterDetail, CfgVisualization, CfgTrace, CfgTraceKey, make_session + + +class Singleton(type): + """ Metaclass that creates a Singleton base type when called. """ + + def __call__(cls, *args, **kwargs): + try: + instances = g.setdefault(FlaskGKey.YAML_CONFIG, {}) + except Exception: + # for unit test + instances = {} + + if cls not in instances: + instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + + return instances[cls] + + +class YamlConfig: + """ + Common Config Yaml class + """ + + def __init__(self, fname_config_yaml): + self.fname_config_yaml = fname_config_yaml + self.dic_config = self.read_yaml() + + def read_yaml(self): + # Read YAML and return dic + # https://qiita.com/konomochi/items/f5f53ba8efa07ec5089b + # 入力時に順序を保持する + add_constructor(resolver.BaseResolver.DEFAULT_MAPPING_TAG, + lambda loader, node: OrderedDict(loader.construct_pairs(node))) + + # get encoding + encoding = detect_encoding(self.fname_config_yaml) + + with open(self.fname_config_yaml, "r", encoding=encoding) as f: + data = yaml.load(f, Loader=yaml.Loader) + + return data + + def write_json_to_yml_file(self, dict_obj): + # get encoding + encoding = detect_encoding(self.fname_config_yaml) + + try: + dict_obj = convert_json_to_ordered_dict(dict_obj) + with open(self.fname_config_yaml, 'w', encoding=encoding) as outfile: + yaml.dump(dict_obj, outfile, default_flow_style=False, allow_unicode=True) + + return True + except Exception: + print('>>> traceback <<<') + traceback.print_exc() + print('>>> end of traceback <<<') + return False + + def update_yaml(self, dict_obj, key_paths=None): + try: + self.clear_specified_parts(key_paths) + updated_obj = dict_deep_merge(dict_obj, self.dic_config) + + self.write_json_to_yml_file(updated_obj) + return True + except Exception: + return False + + def save_yaml(self): + """ + save yaml object to file + Notice: This method will save exactly yaml obj to file, so in case many users save the same time, the last one will win. + so use update_yaml is better in these case. update_yaml only save changed node(key) only. + :return: + """ + + try: + self.write_json_to_yml_file(self.dic_config) + return True + except Exception: + return False + + # get node . if node not exist return None (no error) + @staticmethod + def get_node(dict_obj, keys, default_val=None): + node = dict_obj + for key in keys: + if not node or not isinstance(node, dict): + if default_val is None: + return node + else: + return default_val + + node = node.get(key, default_val) + + return node + + # clear node by a specified key array + def clear_node_by_key_path(self, keys): + node = self.dic_config + for key in keys: + # if node is empty, we dont need to care its children + if not node: + return + + # use parent node reference to delete its children + if key == keys[-1] and key in node: + del (node[key]) + return + + # move current node reference to 1 layer deeper + node = node.get(key) + + # clear specified key in specified node. + def clear_specified_parts(self, key_paths): + if not key_paths: + return + + for keys in key_paths: + self.clear_node_by_key_path(keys) + + +class BasicConfigYaml(YamlConfig, metaclass=Singleton): + """ + Basic Config Yaml class + """ + + # keywords + INFO = 'info' + VERSION = 'version' + + def __init__(self, fname_config_yaml=None): + if fname_config_yaml: + super().__init__(fname_config_yaml) + else: + super().__init__(dic_yaml_config_file[YAML_CONFIG_BASIC]) + + def get_version(self): + return YamlConfig.get_node(self.dic_config, [self.INFO, self.VERSION], '0') + + def set_version(self, ver): + self.dic_config[self.INFO][self.VERSION] = ver + + +class DBConfigYaml(YamlConfig, metaclass=Singleton): + """ + DB Config Yaml class + """ + + # keywords + DB = 'db' + TYPE = 'type' + UNIVERSAL_DB = 'universal_db' + PROC_ID = 'proc_id' + COLUMN_NAMES = 'column_names' + UNUSED_COLUMN_NAMES = 'unused_column_names' + DATA_TYPES = 'data_types' + DIRECTORY = 'directory' + MASTER_NAME = 'master-name' + HOST = 'host' + DBNAME = 'dbname' + PORT = 'port' + SCHEMA = 'schema' + USERNAME = 'username' + PASSWORD = 'password' + HASHED = 'hashed' + FREQUENCY = 'polling-frequency' + CSV_SKIP_HEAD = 'skip_head' + CSV_SKIP_TAIL = 'skip_tail' + ETL_FUNC = 'etl_func' + USE_OS_TIMEZONE = 'use_os_timezone' + DELIMITER = 'delimiter' + COMMENT = 'comment' + + class PollingFrequency(Enum): + ONCE = '0' + HOURLY = '1' + DAILY = '24' + + def __init__(self, fname_config_yaml=None): + if fname_config_yaml: + super().__init__(fname_config_yaml) + else: + super().__init__(dic_yaml_config_file[YAML_CONFIG_DB]) + + self.db_instance = None + + def decrypt_db_password(self): + from histview2.common.cryptography_utils import decrypt_db_password + + if not self.dic_config: # for a rare case, self.dic_config in None for unknown reason + self.__init__() + db_config_dumped = dumps(self.dic_config) + db_config_json = loads(db_config_dumped) + decoded_db_config_json = decrypt_db_password(db_config_json) + + for key in decoded_db_config_json[DBConfigYaml.DB]: + db_config = decoded_db_config_json[DBConfigYaml.DB][key] + if db_config and db_config.get(DBConfigYaml.PASSWORD): + password = db_config.get(DBConfigYaml.PASSWORD) + + if self.dic_config[DBConfigYaml.DB].get(key): + self.dic_config[DBConfigYaml.DB][key][DBConfigYaml.PASSWORD] = password + self.dic_config[DBConfigYaml.DB][key][DBConfigYaml.HASHED] = False + + def get_db_master_name(self, db_code): + try: + return YamlConfig.get_node(self.dic_config, (DBConfigYaml.DB, db_code, DBConfigYaml.MASTER_NAME)) or db_code + except Exception: + return db_code + + def get_comment(self, db_code): + return YamlConfig.get_node(self.dic_config, (DBConfigYaml.DB, db_code, DBConfigYaml.COMMENT)) + + def get_type(self, db_code): + return YamlConfig.get_node(self.dic_config, (DBConfigYaml.DB, db_code, DBConfigYaml.TYPE)) + + def get_csv_folder(self, db_code): + return YamlConfig.get_node(self.dic_config, [DBConfigYaml.DB, db_code, DBConfigYaml.DIRECTORY], None) + + def get_dbs(self): + return [key for key in self.dic_config[YAML_DB].keys()] + + def is_csv(self, db_code): + if isinstance(db_code, str) \ + and self.get_node(self.dic_config, [DBConfigYaml.DB, db_code, DBConfigYaml.TYPE]) == DBType.CSV.value: + return True + else: + return False + + def get_db_infos(self): + dbs = self.get_dbs() + return { + db: YamlConfig.get_node(self.dic_config, [DBConfigYaml.DB, db]) for db in dbs + } + + def get_db_info(self, ds_id): + dbs = self.get_db_infos() + return dbs[ds_id] + + def get_csv_procs(self): + dbs = self.get_dbs() + return { + db: YamlConfig.get_node(self.dic_config, [DBConfigYaml.DB, db, + DBConfigYaml.UNIVERSAL_DB, DBConfigYaml.PROC_ID]) + for db in dbs if self.is_csv(db) + } + + def get_proc_ids(self, db_id): + dbs = self.get_dbs() + procs = [ + YamlConfig.get_node(self.dic_config, [DBConfigYaml.DB, db, DBConfigYaml.UNIVERSAL_DB, DBConfigYaml.PROC_ID]) + for db in dbs if db == db_id] + return procs[0] + + def get_etl_func(self, db_code): + return YamlConfig.get_node(self.dic_config, [DBConfigYaml.DB, db_code, DBConfigYaml.ETL_FUNC]) + + def get_use_os_timezone(self, db_code): + return YamlConfig.get_node(self.dic_config, [DBConfigYaml.DB, db_code, DBConfigYaml.USE_OS_TIMEZONE]) + + def get_factory_procs(self): + dbs = self.get_dbs() + return { + db: YamlConfig.get_node(self.dic_config, [DBConfigYaml.DB, db, + DBConfigYaml.UNIVERSAL_DB, DBConfigYaml.PROC_ID]) + for db in dbs if not self.is_csv(db) + } + + def gen_proc_id(self, db_code, proc_code, proc_id): # TODO support factory only -> need to support csv + proc_id_node = YamlConfig.get_node( + self.dic_config, [DBConfigYaml.DB, db_code, DBConfigYaml.UNIVERSAL_DB, DBConfigYaml.PROC_ID]) + + if proc_id_node: + self.dic_config[DBConfigYaml.DB][db_code][DBConfigYaml.UNIVERSAL_DB][DBConfigYaml.PROC_ID][proc_code] \ + = proc_id + universal_db_node = YamlConfig.get_node(self.dic_config, + [DBConfigYaml.DB, db_code, DBConfigYaml.UNIVERSAL_DB], + {}) + return { + DBConfigYaml.DB: + {db_code: + {DBConfigYaml.UNIVERSAL_DB: universal_db_node} + } + } + else: + return { + DBConfigYaml.DB: + {db_code: + {DBConfigYaml.UNIVERSAL_DB: + {DBConfigYaml.PROC_ID: + {proc_code: proc_id} + } + } + } + } + + # return universal_db_node + + def del_db_config(self, db_code): + if self.get_node(self.dic_config, [DBConfigYaml.DB, db_code]): + del self.dic_config[DBConfigYaml.DB][db_code] + self.update_yaml(dict_obj=self.dic_config) + + def del_proc_from_cfg(self, db_code, proc_code): + db_configs = self.dic_config[DBConfigYaml.DB] + for code, db_config in db_configs.items(): + if code == db_code \ + and YamlConfig.get_node(db_configs, [db_code, DBConfigYaml.UNIVERSAL_DB, YAML_PROC_ID, proc_code]): + del self.dic_config[DBConfigYaml.DB][db_code][DBConfigYaml.UNIVERSAL_DB][YAML_PROC_ID][proc_code] + break + self.update_yaml(dict_obj=self.dic_config) + + def run_sql(self, sql, row_is_dict=True): + res = self.db_instance.run_sql(sql, row_is_dict) + return res + + def fetch_many(self, sql, size=10_000): + data = self.db_instance.fetch_many(sql, size) + if not data: + yield None + + for row in data: + yield row + + def self_init_db(self, db_code): + self.decrypt_db_password() + self.db_instance = DBConfigYaml.init_db(self.dic_config, db_code) + return self.db_instance + + @classmethod + def init_db(cls, dic_config, db_code): + dic_db_info = cls.get_node(dic_config, [DBConfigYaml.DB, db_code]) + if not dic_db_info: + return None + + # get port-no from db config yml file. + port = None + if DBConfigYaml.PORT in dic_db_info.keys(): + port = parse_int_value(dic_db_info.get(DBConfigYaml.PORT)) + + schema = None + if dic_db_info.get(DBConfigYaml.SCHEMA) is not None: + schema = dic_db_info.get(DBConfigYaml.SCHEMA) + + if dic_db_info.get(DBConfigYaml.TYPE) == DBType.POSTGRESQL.value: + from histview2.common.pydn.dblib import postgresql + db_instance = postgresql.PostgreSQL(dic_db_info.get(DBConfigYaml.HOST), + dic_db_info.get(DBConfigYaml.DBNAME), + dic_db_info.get(DBConfigYaml.USERNAME), + dic_db_info.get(DBConfigYaml.PASSWORD)) + # use custom port or default port + if port: + db_instance.port = port + + if schema: + db_instance.schema = schema + + return db_instance + elif dic_db_info.get(DBConfigYaml.TYPE) == DBType.ORACLE.value: + from histview2.common.pydn.dblib import oracle + db_instance = oracle.Oracle(dic_db_info.get(DBConfigYaml.HOST), + dic_db_info.get(DBConfigYaml.DBNAME), + dic_db_info.get(DBConfigYaml.USERNAME), + dic_db_info.get(DBConfigYaml.PASSWORD)) + # use custom port or default port + if port: + db_instance.port = port + + return db_instance + elif dic_db_info.get(DBConfigYaml.TYPE) == DBType.MYSQL.value: + from histview2.common.pydn.dblib import mysql + db_instance = mysql.MySQL(dic_db_info.get(DBConfigYaml.HOST), + dic_db_info.get(DBConfigYaml.DBNAME), + dic_db_info.get(DBConfigYaml.USERNAME), + dic_db_info.get(DBConfigYaml.PASSWORD)) + # use custom port or default port + if port: + db_instance.port = port + + return db_instance + elif dic_db_info.get(DBConfigYaml.TYPE) == DBType.MSSQLSERVER.value: + from histview2.common.pydn.dblib import mssqlserver + db_instance = mssqlserver.MSSQLServer(dic_db_info.get(DBConfigYaml.HOST), + dic_db_info.get(DBConfigYaml.DBNAME), + dic_db_info.get(DBConfigYaml.USERNAME), + dic_db_info.get(DBConfigYaml.PASSWORD)) + # use custom port or default port + if port: + db_instance.port = port + + if schema: + db_instance.schema = schema + + return db_instance + + elif dic_db_info.get(DBConfigYaml.TYPE) == DBType.SQLITE.value: + from histview2.common.pydn.dblib import sqlite + return sqlite.SQLite3(dic_db_info.get(DBConfigYaml.DBNAME)) + elif dic_db_info.get(DBConfigYaml.TYPE) == DBType.CSV.name and dic_db_info.get(DBConfigYaml.DBNAME): + from histview2.common.pydn.dblib import sqlite + return sqlite.SQLite3(dic_db_info.get(DBConfigYaml.DBNAME)) + else: + return None + + def connect_db(self, db_code): + if not self.db_instance: + self.self_init_db(db_code) + + res = self.db_instance.connect() + return res + + def disconnect_db(self): + if self.db_instance: + self.db_instance.disconnect() + + def get_csv_data_types(self, db_code): + if not self.is_csv(db_code): + return None + + dic_universal = YamlConfig.get_node(self.dic_config, [DBConfigYaml.DB, db_code, DBConfigYaml.UNIVERSAL_DB], {}) + data_types = dic_universal.get(DBConfigYaml.DATA_TYPES) + + return data_types + + def save_ds_config(self, ds_code, ds_info): + try: + dic_db_config = self.dic_config[YAML_DB] + current_ds_info = dic_db_config.get(ds_code) or {} + + # encrypt password if hashed + hash_flag = current_ds_info.get(YAML_HASHED) + if hash_flag and current_ds_info.get(YAML_PASSWORD) is not None: + current_ds_info[YAML_PASSWORD] = decrypt(current_ds_info.get(YAML_PASSWORD)) + current_ds_info[YAML_HASHED] = False + + # just assign to add new or update + dic_db_config[ds_code] = dict_deep_merge(ds_info, current_ds_info) + + # encrypt db password + self.dic_config = encrypt_db_password(self.dic_config) + + # update yaml + self.update_yaml(self.dic_config) + except Exception: + traceback.print_exc() + return False + + return True + + +class ProcConfigYaml(YamlConfig, metaclass=Singleton): + """ + Proc Config Yaml class + """ + + def __init__(self, fname_config_yaml=None): + if fname_config_yaml: + super().__init__(fname_config_yaml) + else: + super().__init__(dic_yaml_config_file[YAML_CONFIG_PROC]) + + def get_all_traces(self, proc_name): + common_keys = [YAML_PROC, proc_name, YAML_TRACE, YAML_TRACE_FORWARD] + nodes = YamlConfig.get_node(self.dic_config, common_keys) + + return nodes + + def get_proc_master_names(self): + return [(key, val[YAML_MASTER_NAME]) for key, val in self.dic_config[YAML_PROC].items()] + + def get_proc_master_name(self, proc_name): + try: + return YamlConfig.get_node(self.dic_config, (YAML_PROC, proc_name, YAML_MASTER_NAME)) or proc_name + except Exception: + return proc_name + + def get_proc_info(self, proc_name): + return YamlConfig.get_node(self.dic_config, (YAML_PROC, proc_name)) + + def get_db_id(self, proc_name): + """ + get db_id of proc + """ + return YamlConfig.get_node(self.dic_config, (YAML_PROC, proc_name, YAML_DB)) + + def get_checked_columns(self, proc_name, dic_key_is_alias=False): + output = {} + keys = (YAML_PROC, proc_name, YAML_CHECKED_COLS) + checked_cols = self.get_node(self.dic_config, keys) + if checked_cols: + dic_key = YAML_COL_NAMES + if dic_key_is_alias: + dic_key = YAML_ALIASES + + for i in range(len(checked_cols[YAML_COL_NAMES])): + output[checked_cols[dic_key or YAML_COL_NAMES][i]] = { + YAML_COL_NAMES: checked_cols[YAML_COL_NAMES][i], + YAML_ALIASES: checked_cols[YAML_ALIASES][i], + YAML_MASTER_NAMES: checked_cols[YAML_MASTER_NAMES][i], + YAML_OPERATORS: checked_cols[YAML_OPERATORS][i], + YAML_COEFS: checked_cols[YAML_COEFS][i], + YAML_DATA_TYPES: checked_cols[YAML_DATA_TYPES][i], + } + + return output + + # get key_cols aliases + def get_key_aliases(self, proc_name, key_cols, key_origcols): + if not key_origcols: + return key_origcols + + # get key aliases + checked_cols = self.get_checked_columns(proc_name) + # checked column not exists + if not checked_cols: + return key_cols + + return [checked_cols[oricol][YAML_ALIASES] or col for col, oricol in zip(key_cols, key_origcols)] + + def get_leaf_procs(self, is_traceback=True): + """ + Get Trace Points + Returns: + dictionary - - list start points and end points + """ + + procs = self.dic_config[YAML_PROC] + trace_direct = YAML_TRACE_BACK + if not is_traceback: + trace_direct = YAML_TRACE_FORWARD + + non_leafs = [] + for val in procs.values(): + traces = YamlConfig.get_node(val, [YAML_TRACE, trace_direct]) + if traces: + non_leafs.append([trace[YAML_PROC] for trace in traces]) + + return list(set(procs) - set(chain.from_iterable(non_leafs))) + + def get_substr_match_info(self, start_proc, trace_target_proc, is_traceback=None): + """ get substr match infor of 2 procs + + Arguments: + start_proc {[type]} - - [description] + trace_target_proc {[type]} - - [description] + + Keyword Arguments: + is_traceback {[type]} - - [description](default: {None}) + + Returns: + [type] - - [description] + """ + trace_direct = YAML_TRACE_BACK + if not is_traceback: + trace_direct = YAML_TRACE_FORWARD + + keys = (YAML_PROC, start_proc, YAML_TRACE, trace_direct) + + trace_infos = self.get_node(self.dic_config, keys) + if not trace_infos: + return {}, {} + + trace = None + for trace_info in trace_infos: + if trace_info[YAML_PROC] == trace_target_proc: + trace = trace_info + break + + if not trace: + return {}, {} + + self_cols = trace[YAML_TRACE_SELF_COLS] + target_cols = trace[YAML_TRACE_TARGET_COLS] + + # self-substring key maybe blank , so create an list of empty list + self_substr = trace.get(YAML_TRACE_MATCH_SELF, [[]] * len(self_cols)) + target_substr = trace.get(YAML_TRACE_MATCH_TARGET, [[]] * len(target_cols)) + + self_output = {col: substr for col, substr in zip(self_cols, self_substr) if substr} + target_output = {col: substr for col, substr in zip(target_cols, target_substr) if substr} + return self_output, target_output + + # get sub string information for key columns + @staticmethod + def get_substr_info(trace, start_proc, end_proc): + # get substring match information + dic_start_substr = None + dic_end_substr = None + for trace_direct in (True, False): + dic_start_substr, dic_end_substr \ + = trace.proc_yaml.get_substr_match_info(start_proc, end_proc, trace_direct) + if dic_start_substr or dic_end_substr: + break + + dic_col_2_checked_columns = trace.proc_yaml.get_checked_columns(end_proc, False) + new_dic_end_substr = {} + for end_orig_col in list(dic_end_substr): + col_substr = dic_end_substr.get(end_orig_col) + end_key_col_alias = dic_col_2_checked_columns.get(end_orig_col).get(YAML_ALIASES) + new_dic_end_substr[end_key_col_alias] = col_substr + + return dic_start_substr, new_dic_end_substr + + @classmethod + def convert_data_type(cls, dict_yml: dict): + procs = dict_yml[YAML_PROC] + + for proc_val in procs.values(): + checked_cols = proc_val.get(YAML_CHECKED_COLS, None) + if not checked_cols: + continue + if checked_cols[YAML_DATA_TYPES]: + checked_cols[YAML_DATA_TYPES] \ + = [guess_data_types(data_type).name for data_type in checked_cols[YAML_DATA_TYPES]] + + def del_proc_config(self, proc_code): + if self.get_node(self.dic_config, [YAML_PROC, proc_code]): + del self.dic_config[YAML_PROC][proc_code] + self.update_yaml(dict_obj=self.dic_config) + + def get_table_name(self, proc_name): + proc_yaml_keys = [YAML_PROC, proc_name] + table_name = self.get_node(self.dic_config, proc_yaml_keys + [YAML_SQL, YAML_FROM]) + return table_name + + def set_trace_edges(self, proc_code, edges=[], is_forward=True): + direction = YAML_TRACE_FORWARD + if not is_forward: + direction = YAML_TRACE_BACK + + if self.get_node(self.dic_config, [YAML_PROC, proc_code]): + if self.get_node(self.dic_config, [YAML_PROC, proc_code, YAML_TRACE]): + self.dic_config[YAML_PROC][proc_code][YAML_TRACE][direction] = edges + else: + self.dic_config[YAML_PROC][proc_code][YAML_TRACE] = {} + self.dic_config[YAML_PROC][proc_code][YAML_TRACE][direction] = edges + + def update_alias_in_trace_config(self, dict_yml: dict): + procs = dict_yml[YAML_PROC] or {} + + def create_new_edges(old_edges, alias_2_colname, colname_2_new_alias): + new_edges = [] + for edge in old_edges: + aliases = edge.get(YAML_TRACE_SELF_COLS) or [] + new_self_cols = [] + for alias in aliases: + # use column name for reference + colname = self.get_node(alias_2_colname, [alias, YAML_COL_NAMES]) + # get new alias name, if column is uncheck, new_alias is null + new_alias = colname_2_new_alias.get(colname) + new_self_cols.append(new_alias) + edge[YAML_TRACE_SELF_COLS] = new_self_cols + new_edges.append(edge) + return new_edges + + for proc_code, proc_config in procs.items(): + # get current trace setting from proc_config.yaml + forward_edges = self.get_node(self.dic_config, [YAML_PROC, proc_code, YAML_TRACE, YAML_TRACE_FORWARD], + []) or [] + backward_edges = self.get_node(self.dic_config, [YAML_PROC, proc_code, YAML_TRACE, YAML_TRACE_BACK], + []) or [] + old_alias_2_colname = self.get_checked_columns(proc_code, dic_key_is_alias=True) + + # get new column settings + colnames = self.get_node(proc_config, [YAML_CHECKED_COLS, YAML_COL_NAMES], []) or [] + new_aliases = self.get_node(proc_config, [YAML_CHECKED_COLS, YAML_ALIASES], []) or [] + colname_2_new_alias = dict(zip(colnames, new_aliases)) + + if forward_edges: + create_new_edges(forward_edges, old_alias_2_colname, colname_2_new_alias) + if backward_edges: + create_new_edges(backward_edges, old_alias_2_colname, colname_2_new_alias) + + # def get_key_aliases(self, proc_id, key_cols, key_origcols): + + +class Hist2ConfigYaml(YamlConfig, metaclass=Singleton): + """ + Histview 2 Config Yaml class + """ + + def __init__(self, fname_config_yaml=None): + if fname_config_yaml: + super().__init__(fname_config_yaml) + else: + super().__init__(dic_yaml_config_file[YAML_CONFIG_HISTVIEW2]) + + def get_date_col(self, proc_name): + keys = (YAML_PROC, proc_name, YAML_DATE_COL) + return clear_special_char(self.get_node(self.dic_config, keys)) + + def get_serial_col(self, proc_name): + keys = (YAML_PROC, proc_name, YAML_SERIAL_COL) + return clear_special_char(self.get_node(self.dic_config, keys)) + + def get_auto_increment_col(self, proc_name): + keys = (YAML_PROC, proc_name, YAML_AUTO_INCREMENT_COL) + return clear_special_char(self.get_node(self.dic_config, keys)) + + def get_filter_column_name(self, proc_name, key): + keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES] + if key.startswith(FILTER_MACHINE): + keys = keys + [YAML_FILTER_LINE_MACHINE_ID, YAML_MACHINE_ID, YAML_COL_NAME] + else: + keys = keys + [key, YAML_COL_NAME] + + # it will return column-name of filter , or return key if key is a column not a filter. + return YamlConfig.get_node(self.dic_config, keys, key) + + def get_filter_sql_statement(self, proc_name, key, ids): + if not ids: + return None + + keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES, key] + value_master = YamlConfig.get_node(self.dic_config, keys + [YAML_VALUE_MASTER]) + if not value_master: + return None + + sql_statements = YamlConfig.get_node(self.dic_config, keys + [YAML_SQL_STATEMENTS]) + if not sql_statements: + sql_statements = YamlConfig.get_node(self.dic_config, keys + [YAML_VALUE_LIST]) + + return [val for key, val in zip(value_master, sql_statements) if key in ids] + + def get_vals_from_sql(self, proc_name, node_name=FILTER_PARTNO, list_sqls=tuple()): # TODO commonize + if not list_sqls: + return [] + + keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES, node_name] + value_list = YamlConfig.get_node(self.dic_config, keys + [YAML_VALUE_LIST]) + sql_statements = YamlConfig.get_node(self.dic_config, keys + [YAML_SQL_STATEMENTS]) + if not value_list or not sql_statements: + return [] + return [value for value, sql_statement in zip(value_list, sql_statements) if sql_statement in list_sqls] + + def get_filters(self, proc_name): + keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES] + return YamlConfig.get_node(self.dic_config, keys) + + def get_filter_value_masters(self, proc_name, key): + if not key: + return None + + keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES, key] + value_masters = YamlConfig.get_node(self.dic_config, keys + [YAML_VALUE_MASTER]) + + return value_masters + + def get_filter_condition(self, proc_name, key): + if not key: + return None + + keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES, key] + conditions = YamlConfig.get_node(self.dic_config, keys + [YAML_SQL_STATEMENTS]) + if conditions is None: + conditions = YamlConfig.get_node(self.dic_config, keys + [YAML_VALUE_LIST]) + + return conditions + + def get_filter_name_by_column_name(self, proc_name, col_name): + common_keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES] + nodes = YamlConfig.get_node(self.dic_config, common_keys) + for key, dic_val in nodes.items(): + if key.startswith(FILTER_MACHINE): + node = YamlConfig.get_node(dic_val, [YAML_FILTER_LINE_MACHINE_ID, YAML_MACHINE_ID]) + else: + node = dic_val + + if node.get(YAML_COL_NAME) == col_name: + return key + + return None + + def get_all_filters(self, proc_name): + common_keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES] + nodes = YamlConfig.get_node(self.dic_config, common_keys) + return nodes + + def get_all_chart_infos(self, proc_name): + common_keys = [YAML_PROC, proc_name, YAML_SQL, YAML_SELECT_OTHER_VALUES] + nodes = YamlConfig.get_node(self.dic_config, common_keys) or {} + dic_chart_infos = {} + proc_yaml = ProcConfigYaml() + checked_columns = proc_yaml.get_checked_columns(proc_name, True) + for alias_name, node in nodes.items(): + chart_infos = node.get('chart-info') + if chart_infos: + col_name = checked_columns[alias_name][YAML_COL_NAMES] + dic_chart_infos[col_name] = chart_infos + + return dic_chart_infos + + def get_filters_by_column_name(self, proc_name, col_name): + common_keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES] + nodes = YamlConfig.get_node(self.dic_config, common_keys) + for key, dic_val in nodes.items(): + if col_name in (dic_val.get(YAML_COL_NAME), dic_val.get(YAML_ORIG_COL_NAME)): + return {key: dic_val} + + return None + + def get_machine_filter_by_column_name(self, proc_name, col_name): + common_keys = [YAML_PROC, proc_name, YAML_SQL, YAML_WHERE_OTHER_VALUES] + nodes = YamlConfig.get_node(self.dic_config, common_keys) + for key, dic_val in nodes.items(): + if dic_val.get(YAML_MACHINE_ID): + if col_name in (dic_val.get(YAML_COL_NAME), dic_val.get(YAML_ORIG_COL_NAME)): + return {key: dic_val} + + return None + + def get_filter_name_by_alias_name(self, proc_name, alias_name): + proc_yaml = ProcConfigYaml() + checked_cols = proc_yaml.get_checked_columns(proc_name, True) + dic_col = checked_cols.get(alias_name) + if not dic_col: + return None + + col_name = dic_col[YAML_COL_NAMES] + key = self.get_filter_name_by_column_name(proc_name, col_name) + + return key + + def get_proc(self, proc_name): + keys = [YAML_PROC, proc_name] + return YamlConfig.get_node(self.dic_config, keys) + + def del_proc_config(self, proc_code): + if self.get_node(self.dic_config, [YAML_PROC, proc_code]): + del self.dic_config[YAML_PROC][proc_code] + self.update_yaml(dict_obj=self.dic_config) + + +def parse_bool_value(value): + variants = {'true': True, 't': True, '1': True, 'false': False, 'f': False, '0': False} + + if isinstance(value, bool): + return value + lower_value = str(value).strip().lower() + return variants.get(lower_value) or False + + +class TransformYamlToDb: + db_yaml: DBConfigYaml + proc_yaml: ProcConfigYaml + histview2_yaml: Hist2ConfigYaml + data_source_cfgs: List[CfgDataSource] + + def __init__(self): + self.data_source_cfgs = [] + + @staticmethod + def is_db_config_data_exists(): + return CfgDataSource.query.first() + + @staticmethod + def is_yaml_config_exists(): + """ + Check if specified file is exists, return false if one of them is not exists. + Arguments: + dic_yaml_config_file {dict} list of all yaml file path. + Returns: + True if all of file are exist + """ + yaml_keys = [YAML_CONFIG_BASIC, YAML_CONFIG_DB, YAML_CONFIG_PROC, YAML_CONFIG_HISTVIEW2] + for key, file_path in dic_yaml_config_file.items(): + if key in yaml_keys: + if not os.path.exists(file_path): + return False + + return True + + def __load_yaml(self): + self.db_yaml = DBConfigYaml() + self.proc_yaml = ProcConfigYaml() + self.histview2_yaml = Hist2ConfigYaml() + self.data_source_cfgs = [] + + def transform(self): + if not self.is_yaml_config_exists(): + return + + if self.is_db_config_data_exists(): + return + + self.import_to_db() + + # save + with make_session() as meta_session: + print('YAML Transform: START') + for ds in self.data_source_cfgs: + print(ds.__dict__) + meta_session.add(ds) + + with make_session() as meta_session: + data_source_cfgs = meta_session.query(CfgDataSource).all() + print('Number of data source:', len(data_source_cfgs)) + proc_cfgs = meta_session.query(CfgProcess).all() + print('Number of process:', len(proc_cfgs)) + + print('YAML Transform: DONE!!!') + + def import_to_db(self): + self.__load_yaml() + + dic_proc_columns = {} + data_sources = self.get_data_sources() or [] + for ds_id in data_sources: + # ds + is_csv = self.db_yaml.is_csv(ds_id) + data_src_cfg = self.set_data_source(ds_id) + self.data_source_cfgs.append(data_src_cfg) + + # csv ds + if is_csv: + ds_detail_cfg = self.set_csv_data_source(ds_id) + data_src_cfg.csv_detail = ds_detail_cfg + csv_columns = self.set_csv_columns(ds_id) + ds_detail_cfg.csv_columns = csv_columns + else: + ds_detail_cfg = self.set_db_data_source(ds_id) + data_src_cfg.db_detail = ds_detail_cfg + + # process + data_src_cfg.processes = [] + dic_procs = self.get_process_ids(ds_id) or {} + for proc_id, proc_new_id in dic_procs.items(): + proc_cfg = self.set_process(proc_id, proc_new_id) + + # there is no proc in proc yaml + if not proc_cfg: + continue + + data_src_cfg.processes.append(proc_cfg) + + # checked columns + dic_checked_columns = self.get_columns(proc_id) or {} + + # serials + serial_cols = [s.split()[0] for s in self.histview2_yaml.get_serial_col(proc_id) if s] + + # get date + date_col = self.histview2_yaml.get_date_col(proc_id) + date_col = date_col.split()[0] + + # auto_incremental_col + auto_incremental_col = self.histview2_yaml.get_auto_increment_col(proc_id) + + # column_type + dic_columns = {} + dic_proc_columns[proc_id] = (proc_cfg, dic_columns) + for col_name, col_info in dic_checked_columns.items(): + column_cfg = self.set_process_column(col_name, col_info, date_col, serial_cols, + auto_incremental_col) + + proc_cfg.columns.append(column_cfg) + dic_columns[col_name] = column_cfg + + # filters and filter details + dic_chart_infos = {} + filters = self.histview2_yaml.get_all_filters(proc_id) or {} + for filter_type, filter_info in filters.items(): + col_name = filter_info.get('orig_column') or filter_info.get('column_name') + # line, part no, other + filter_type = self.get_filter_type(filter_type) + if not filter_type: + continue + + filter_cfg, dic_chart_info_key = self.set_filter(filter_type, filter_info) + dic_chart_infos.update(dic_chart_info_key) + if col_name: + filter_cfg.column = dic_columns.get(col_name) + + proc_cfg.filters.append(filter_cfg) + + # machine + if filter_type == CfgFilterType.LINE.name: + machine_info = filter_info.get('machine-id') + filter_machine_cfg = self.set_machine_filter(filter_type, machine_info, filter_cfg) + if filter_machine_cfg: + machine_col_name = machine_info.get('orig_column') or machine_info.get('column_name') + if machine_col_name: + filter_machine_cfg.column = dic_columns.get(machine_col_name) + + proc_cfg.filters.append(filter_machine_cfg) + filter_machine_cfg.parent = filter_cfg + + # visualization( chart infos) + dic_charts = self.histview2_yaml.get_all_chart_infos(proc_id) or {} + for col_name, chart_infos in dic_charts.items(): + visual_cfgs = self.set_visualization(chart_infos, dic_columns.get(col_name), dic_chart_infos) + for visual_cfg in visual_cfgs: + proc_cfg.visualizations.append(visual_cfg) + + # trace + self.set_trace(dic_proc_columns) + + def get_data_sources(self): + return self.db_yaml.get_db_infos() + + def get_process_ids(self, ds_id): + dic_procs = self.db_yaml.get_proc_ids(ds_id) + return dic_procs + + def get_columns(self, proc_id): + return self.proc_yaml.get_checked_columns(proc_id) + + def get_traces(self, proc_id): + # traces + dic_traces = self.proc_yaml.get_all_traces(proc_id) or {} + for target_proc, dic_trace in dic_traces.items(): + pass + + def set_data_source(self, ds_id): + db_info = self.db_yaml.get_db_info(ds_id) + + data_src_cfg = CfgDataSource() + data_src_cfg.name = db_info.get('master-name') + data_src_cfg.comment = db_info.get('comment') + db_type = db_info.get('type') + if db_type: + data_src_cfg.type = DBType(db_type).name + return data_src_cfg + + def set_csv_data_source(self, ds_id): + db_info = self.db_yaml.get_db_info(ds_id) + + ds_csv = CfgDataSourceCSV() + + ds_csv.etl_func = db_info.get('etl_func') + ds_csv.delimiter = db_info.get('delimiter') + ds_csv.directory = db_info.get('directory') + ds_csv.skip_head = parse_int_value(db_info.get('skip_head')) + ds_csv.skip_tail = parse_int_value(db_info.get('skip_tail')) + ds_csv.csv_columns = [] + + return ds_csv + + def set_db_data_source(self, ds_id): + db_info = self.db_yaml.get_db_info(ds_id) + + ds_db = CfgDataSourceDB() + ds_db.host = db_info.get('host') + ds_db.port = db_info.get('port') or None + ds_db.dbname = db_info.get('dbname') + ds_db.schema = db_info.get('schema') or None + ds_db.username = db_info.get('username') + ds_db.password = db_info.get('password') + ds_db.hashed = parse_bool_value(db_info.get('hashed')) + ds_db.use_os_timezone = parse_bool_value(db_info.get('use_os_timezone')) + + return ds_db + + def set_csv_columns(self, ds_id): + proc_info = self.db_yaml.get_db_info(ds_id) + universal_info = proc_info.get('universal_db') + + column_names = universal_info.get('column_names') + data_types = universal_info.get('data_types') + + csv_columns = [] + if column_names: + for col_name, data_type in zip(column_names, data_types): + csv_column = CfgCsvColumn() + csv_column.column_name = col_name + csv_column.data_type = data_type + csv_columns.append(csv_column) + + return csv_columns + + def set_process(self, proc_id, proc_new_id): + proc_info = self.proc_yaml.get_proc_info(proc_id) + if not proc_info: + return None + + proc_cfg = CfgProcess() + + proc_cfg.id = proc_new_id + proc_cfg.name = proc_info['master-name'] + proc_cfg.table_name = clear_special_char(proc_info['sql']['from']) + proc_cfg.comment = proc_info['comment'] + proc_cfg.columns = [] + proc_cfg.filters = [] + proc_cfg.visualizations = [] + + return proc_cfg + + @staticmethod + def set_process_column(col_name, col_info, get_date, serials, auto_incremental_col): + column_cfg = CfgProcessColumn() + + column_cfg.column_name = col_name + column_cfg.name = col_info['master-names'] + column_cfg.english_name = to_romaji(col_info['alias-names']) + column_cfg.operator = col_info['operators'] + column_cfg.coef = col_info['coefs'] + column_cfg.data_type = col_info['data-types'] + + column_cfg.is_get_date = 0 + if col_name == get_date: + column_cfg.is_get_date = 1 + + column_cfg.is_auto_increment = 0 + if col_name == auto_incremental_col: + column_cfg.is_auto_increment = 1 + + column_cfg.is_serial_no = 0 + if col_name in serials: + column_cfg.is_serial_no = 1 + + return column_cfg + + def set_trace(self, dic_proc_columns): + for proc_id, (proc_cfg, dic_columns) in dic_proc_columns.items(): + checked_cols = self.proc_yaml.get_checked_columns(proc_id, True) + traces = self.proc_yaml.get_all_traces(proc_id) or [] + proc_cfg.traces = [] + for dic_trace in traces: + # self + self_proc_cfg = proc_cfg + self_col_cfgs = [dic_columns[checked_cols[col][YAML_COL_NAMES]] for col in + dic_trace['self-alias-columns']] + + self_substrs = dic_trace.get('self-substr') + if self_substrs is None: + self_substrs = [[]] * len(self_col_cfgs) + + # target + tar_proc_id = dic_trace.get('proc') + tar_proc_cfg, dic_tar_columns = dic_proc_columns.get(tar_proc_id) + if not tar_proc_cfg or not dic_tar_columns: + continue + + tar_col_cfgs = [dic_tar_columns[col] for col in dic_trace['target-orig-columns']] + tar_substrs = dic_trace.get('target-substr') + if tar_substrs is None: + tar_substrs = [[]] * len(tar_col_cfgs) + + # save to db + trace_cfg = CfgTrace() + proc_cfg.traces.append(trace_cfg) + + trace_cfg.self_process = self_proc_cfg + trace_cfg.target_process = tar_proc_cfg + trace_cfg.trace_keys = [] + for self_col, self_sub, tar_col, tar_sub in zip(self_col_cfgs, self_substrs, tar_col_cfgs, tar_substrs): + trace_key_cfg = CfgTraceKey() + trace_cfg.trace_keys.append(trace_key_cfg) + + # self + trace_key_cfg.self_column = self_col + if self_sub: + trace_key_cfg.self_column_substr_from = self_sub[0] + trace_key_cfg.self_column_substr_to = self_sub[1] + + # target + trace_key_cfg.target_column = tar_col + if tar_sub: + trace_key_cfg.target_column_substr_from = tar_sub[0] + trace_key_cfg.target_column_substr_to = tar_sub[1] + + return None + + def set_machine_filter(self, filter_type, machine_info, filter_cfg: CfgFilter): + if not machine_info: + return None + + machine_cfg = CfgFilter() + machine_cfg.filter_type = CfgFilterType.MACHINE_ID.name + + # detail + machine_cfg.filter_details = [] + dic_details = machine_info.get('line-list') or {} + for line_detail_id, detail_info in dic_details.items(): + value_list = detail_info.get('value_list') + value_masters = detail_info.get('value_masters') + detail_cfgs, _ = self.set_filter_details(None, value_list, value_masters) + for detail_cfg in detail_cfgs: + parents = list(filter(lambda x: x.filter_condition == line_detail_id, filter_cfg.filter_details)) + detail_cfg.parent = parents[0] + machine_cfg.filter_details.append(detail_cfg) + + return machine_cfg + + def set_filter(self, filter_type, filter_info): + filter_cfg = CfgFilter() + + filter_cfg.filter_type = filter_type + sql_statements = filter_info.get('sql_statements') + value_list = filter_info.get('value_list') + value_masters = filter_info.get('value_masters') + filter_details, dic_chart_info_key = self.set_filter_details(sql_statements, value_list, value_masters) + filter_cfg.filter_details = filter_details + + return filter_cfg, dic_chart_info_key + + @staticmethod + def gen_filter_info_from_sql_statement(sql_statement: str): + value = sql_statement.lstrip('_').lstrip('%').lstrip(SQL_REGEX_PREFIX).rstrip('%') + + filter_position = None + if sql_statement.startswith('_') and sql_statement.endswith('%'): + filter_function = FilterFunc.SUBSTRING.name + filter_position = len(list(takewhile(lambda c: c == '_', sql_statement))) + 1 + value = sql_statement.lstrip('_').rstrip('%') + elif sql_statement.startswith(SQL_REGEX_PREFIX): + filter_function = FilterFunc.REGEX.name + value = sql_statement.lstrip(SQL_REGEX_PREFIX) + elif sql_statement.startswith('%') and sql_statement.endswith('%'): + filter_function = FilterFunc.CONTAINS.name + value = sql_statement.lstrip('%').rstrip('%') + elif sql_statement.startswith('%'): + filter_function = FilterFunc.ENDSWITH.name + value = sql_statement.lstrip('%') + elif sql_statement.endswith('%'): + filter_function = FilterFunc.STARTSWITH.name + value = sql_statement.rstrip('%') + else: + filter_function = FilterFunc.MATCHES.name + value = sql_statement + + return value, filter_function, filter_position + + def set_filter_details(self, sql_statements, value_list, value_masters): + detail_cfgs = [] + dic_chart_info_key = {} + for statement, filter_old_id, name in zip_longest(sql_statements or [], value_list or [], value_masters or []): + detail_cfg = CfgFilterDetail() + detail_cfg.name = name + if statement: + value, func, pos = self.gen_filter_info_from_sql_statement(statement) + else: + value = filter_old_id + func = FilterFunc.MATCHES.name + pos = None + + detail_cfg.filter_condition = value + detail_cfg.filter_function = func + detail_cfg.filter_from_pos = pos + # TODO: check visualization exist and add + detail_cfgs.append(detail_cfg) + + # for chart info + dic_chart_info_key[filter_old_id] = detail_cfg + + return detail_cfgs, dic_chart_info_key + + @staticmethod + def set_visualization(dic_chart_infos, control_column, dic_cfg_details): + visual_cfgs = [] + for key, chart_info in dic_chart_infos.items(): + visual_cfg = CfgVisualization() + visual_cfg.lcl = chart_info.get('thresh-low') + visual_cfg.ucl = chart_info.get('thresh-high') + visual_cfg.lpcl = chart_info.get('prc-min') + visual_cfg.upcl = chart_info.get('prc-max') + visual_cfg.ymax = chart_info.get('y-max') + visual_cfg.ymin = chart_info.get('y-min') + visual_cfg.control_column = control_column + filter_detail_cfg = dic_cfg_details.get(key) + if filter_detail_cfg: + visual_cfg.filter_detail = filter_detail_cfg + visual_cfg.filter_column = filter_detail_cfg.cfg_filter.column + + visual_cfgs.append(visual_cfg) + return visual_cfgs + + @staticmethod + def get_filter_type(key): + if 'line' in key: + return CfgFilterType.LINE.name + + if 'machine' in key: + return CfgFilterType.MACHINE_ID.name + + if 'partno' in key: + return CfgFilterType.PART_NO.name + + if 'other' in key: + return CfgFilterType.OTHER.name + + return None + + +class TileInterfaceYaml(YamlConfig, metaclass=Singleton): + """ + Tile Interface Yaml class + """ + + # keywords + # VERSION = 'version' + + def __init__(self, dn7=True): + if dn7: + super().__init__(dic_yaml_config_file[YAML_TILE_INTERFACE_DN7]) + else: + super().__init__(dic_yaml_config_file[YAML_TILE_INTERFACE_AP]) diff --git a/histview2/config/basic_config.yml b/histview2/config/basic_config.yml new file mode 100644 index 0000000..dfb2f48 --- /dev/null +++ b/histview2/config/basic_config.yml @@ -0,0 +1,12 @@ +!!omap +- info: !!omap + - version: '1' + - port-no: 6868 + - hide-setting-page: false + - r-path: + - auto-backup-universal: false +- bridge_station: !!omap + - host: localhost + - port: 8000 + - bridge_data: true + - reps_id: 20000 diff --git a/histview2/config/image/CHM.png b/histview2/config/image/CHM.png new file mode 100644 index 0000000..efb2adf Binary files /dev/null and b/histview2/config/image/CHM.png differ diff --git a/histview2/config/image/COG.png b/histview2/config/image/COG.png new file mode 100644 index 0000000..8808806 Binary files /dev/null and b/histview2/config/image/COG.png differ diff --git a/histview2/config/image/FPP.png b/histview2/config/image/FPP.png new file mode 100644 index 0000000..45a6311 Binary files /dev/null and b/histview2/config/image/FPP.png differ diff --git a/histview2/config/image/MSP.png b/histview2/config/image/MSP.png new file mode 100644 index 0000000..a529a2b Binary files /dev/null and b/histview2/config/image/MSP.png differ diff --git a/histview2/config/image/PCA.png b/histview2/config/image/PCA.png new file mode 100644 index 0000000..02c827d Binary files /dev/null and b/histview2/config/image/PCA.png differ diff --git a/histview2/config/image/PCP.png b/histview2/config/image/PCP.png new file mode 100644 index 0000000..8f453cd Binary files /dev/null and b/histview2/config/image/PCP.png differ diff --git a/histview2/config/image/RLP.png b/histview2/config/image/RLP.png new file mode 100644 index 0000000..85908a6 Binary files /dev/null and b/histview2/config/image/RLP.png differ diff --git a/histview2/config/image/ScP.png b/histview2/config/image/ScP.png new file mode 100644 index 0000000..9de7346 Binary files /dev/null and b/histview2/config/image/ScP.png differ diff --git a/histview2/config/image/SkD.png b/histview2/config/image/SkD.png new file mode 100644 index 0000000..1bca590 Binary files /dev/null and b/histview2/config/image/SkD.png differ diff --git a/histview2/config/image/StP.png b/histview2/config/image/StP.png new file mode 100644 index 0000000..0fa8473 Binary files /dev/null and b/histview2/config/image/StP.png differ diff --git a/histview2/config/tile_interface_analysis_platform.yml b/histview2/config/tile_interface_analysis_platform.yml new file mode 100644 index 0000000..584f351 --- /dev/null +++ b/histview2/config/tile_interface_analysis_platform.yml @@ -0,0 +1,111 @@ +!!omap +- sections: + - title_en: 'Time or ID Series & Distribution Fluctuation' + title_ja: '時系列/ID系列・分布変動' + tiles: + - row: 1 + column: 1 + title_en: 'FPP Full Points Plot' + title_ja: 'FPP 全数プロット' + png_path: 'FPP.png' + hover_en: 'This is "Time Series Full-point Plot" or "ID Series Full-point Plot" used as an alternative to the X-R plot in Big Data analysis (Average or Range becomes less useful as a statistic when there is too much data). It is used to see trends in data and to check anomalies and outliers.\n + It can also be used as a data downloader that extract and/or integrates data from a database.\n + There is a function called label plot (Blue labels at the top of the graph), and you can grasp relationship between the variable fluctuation and the timing of the change of the collection such as magazine, lot and part number. There is also the ability to view a list of relevant data about the work or the product by selecting [Right Click]→[Plot View] on any plot.' + hover_ja: 'Big Data分析において、X-Rプロットの代替(平均値やレンジはデータ数が多くなりすぎると統計値としての有効性が薄れます)として用いる全数データの時系列もしくはワーク・製品ID系列のプロットです。データのトレンド変化や異常値・外れ値の可視化に使用します。\n + データベースからデータ取得・紐付け統合したデータのダウンローダとしても使用できます。\n + ラベルプロットという機能(グラフ上部にある青いラベル群)があり、マガジン・ロット・品種といった纏まりの切り替わりタイミングと変数の変動との関係を把握することができます。\nまた、任意のプロットで[右クリック]→[Plot View]を選択すると、そのワークや製品に関する関連するデータを一覧として参照できる機能もあります。' + link_address: '/histview2/fpp' + - row: 1 + column: 2 + title_en: 'RLP Ridgeline Plot' + title_ja: 'RLP リッジラインプロット' + png_path: 'RLP.png' + hover_en: 'In Big Data analysis, there are so many plotting points and it is often a non-normal distribution, but it is difficult to grasp the distribution fluctuation even though outliers can be grasped in FPP (Full-points Plot). You can visualize process variations by drawing density plots for each set of data and aligning it along the time or ID series.\n + The graph plotted by points shows the increasing or decreasing trend of the distribution, indicating whether the distribution is moving downward (transitioning toward a lower value as a whole) or upward (transitioning toward a higher value). If it is straight, it means that there is little process variation. In case of continuous shift, there is a characteristic drift. And if it fluctuates rapidly, it means that it is a changing point in production line.' + hover_ja: 'Big Data分析においては打点が多く、非正規分布であることが多いため、全数プロットFPPだけでは外れ値などは把握できるものの分布変動を把握することが難しいです。\nいくつかのデータ群毎に密度曲線(Density Plot)を描画し系列に沿って並べることで、工程の変動を把握することができます。\n + 点でプロットされているグラフは、分布の増加・減少傾向を示しており、分布が下方向(全体として値が小さくなる方向に遷移)に動いているか、上方向に動いているか(値が大きくなる方向に遷移)しているか、が分かります。まっすぐな場合は工程変動があまりないということを意味しています。\n継続してずれていく場合は特性のドリフトがある、また、急激に変動した場合は何らかの工程の変化点であることを示唆します。' + link_address: '/histview2/rlp' + - title_en: 'Stratification & Pattern Overview' + title_ja: '層別・パターン俯瞰' + tiles: + - row: 1 + column: 1 + title_en: 'StP Stratified Plot' + title_ja: 'StP 層別プロット' + png_path: 'StP.png' + hover_en: 'This is ”Stratified Plot” that displays histograms by group of time zone or category.\n + It is possible to visualize the difference between the time zone trend of data (shift, day of the week, week, etc.) and category classification (line, equipment, lot, etc.). For example, it can be use to visualize differences between lines or machine.' + hover_ja: '時間帯別もしくはカテゴリ別にヒストグラム表示する層別プロットです。\n + データの時間帯群トレンド(シフト・曜日・週毎等)やカテゴリ分類(ライン・装置・ロットThis毎等)などの間差を可視化することができます。ライン間差や機差などの見える化に使用します。' + link_address: '/histview2/stp' + - row: 1 + column: 2 + title_en: 'CHM Calendar Heat Map' + title_ja: 'CHM カレンダヒートマップ' + png_path: 'CHM.png' + hover_en: 'It visualizes long-term data such as annual variations in processes and parameters that can vary depending on days of the week or shifts, etc. For example, it is possible to visualize the rate of defects or changes in production volume over a long period of time. It is possible to understand changes in specific patterns, such as a high rate of defects at the beginning of the week, or the occurrence of specific alarms soon after the lunch breaks, and so on.' + hover_ja: '工程での年間変動など長期間のデータや曜日・シフトなどで変動しうるパラメータを可視化します。\n例えば不良率や生産量の推移などを長期にわたって可視化することができ、例えば週明けが不良率が高いとか、昼休み空けに特定のアラームが発生しやすいといった、特定のパターンの変動などを把握することが可能です。' + link_address: '/histview2/chm' + - title_en: 'Multidimensional Correlation' + title_ja: '相関/多次元相関' + tiles: + - row: 1 + column: 1 + title_en: 'MSP Multi Scatter Plot' + title_ja: 'MSP 相関行列プロット' + png_path: 'MSP.png' + hover_en: 'You can check the correlation between multiple variables. You can display up to four variables in this software. If you want to understand the correlation between more variables, use PCP (Parallel Coordinate Plot) as below.' + hover_ja: '複数変数間の相関関係を確認できます。\n4変数までの表示に対応できますが、それ以上の変数間の相関を把握するには、平行座標プロットPCPをご活用ください。' + link_address: '/histview2/msp' + - row: 1 + column: 2 + title_en: 'PCP Parallel Cordinate Plot' + title_ja: 'PCP 平行座標プロット' + png_path: 'PCP.png' + hover_en: 'You can see the correlation between a large number of variables for a given target variable. Determine that the axes are color-separated on each axis (Each axis is parallel to the left and right), and that there is a variable to watch out for, or some correlation (In particular, if they are arranged in order of correlation coefficients, you can see the axis where the color-separation of interest are located at both ends. You can think of the axis with mixed colors as having a low correlation.).\n + A linear correlation is observed for axes that are well-balanced iridescent (the same color arrangement as the color bar [positive correlation] or inverted color arrangement [negative correlation]). If the colors are separated, but there is a biased color distribution, there may be a nonlinear relationship, so you need to perform some function conversion or clustering for stratification (In this case, it is a good idea to check the relationship directly between the separated variables and the target variables using scatter plot).' + hover_ja: '目的の変数に関して、大量の変数間の相関関係を確認できます。\nそれぞれの軸(各軸が左右に平行に配置されています)で色が分かれている軸が注意すべき変数、すなわち何らかの相関がある軸と判断してください(特に相関係数順に並べている場合は両端に着目すべき色が分かれた軸が見えます。色が混ざっている軸は相関が低いと考えて結構です)。\n + バランスよく虹色になっている軸では線形の相関(カラーバーと同じ色配置[正の相関]もしくは反転した色配置[負の相関])が認められます。色が分離しているものの偏った色配置になっている場合は非線形の関係が考えられますので、何らかの関数変換を行なったり、クラスタリングし層別する必要があります(分離している変数と目的変数で散布図を用いて関係を確認することをお勧めします)。' + link_address: '/histview2/pcp' + - row: 1 + column: 3 + title_en: 'ScP Scatter Plot' + title_ja: 'ScP 散布図' + png_path: 'ScP.png' + hover_en: 'You can draw a scatter plot of the relationships between quantitative variables such as measurements. In addition, it can be decomposed by category, period, and number of data, and changes in relationships can be confirmed. For example, it is effective for changing the process window.\n Also, the relationship between categorical variables such as equipment names and quantitative variables can be drawn with a violin plot, and the relationships between categorical variables can be drawn with a heat map. The relationships between variables that do not depend on the data type and their changes can be confirmed. ' + hover_ja: '測定値などの量的変数同士の関係性を散布図で描画します。さらに、カテゴリや期間、データ数で分解でき、関係性の変化を確認することができます。例えばプロセスウィンドウの変化に有効です。\nまた、設備名などのカテゴリ変数と量的変数の関係はバイオリンプロットで、カテゴリ変数同士の関係性はヒートマップで描画でき、データの型に寄らない変数同士の関係性およびその変化を確認することができます。' + link_address: '/histview2/scp' + - title_en: 'Anomaly Detection' + title_ja: '異常検出' + tiles: + - row: 1 + column: 1 + title_en: 'PCA Principal Conponent Analysis' + title_ja: 'PCA 主成分分析' + png_path: 'PCA.png' + hover_en: 'This is multivariate analysis to detect abnormal condition and to list up concerning variables, compair to the normal state data.\n + You can monitor the movement of over dozens of variables, and detect "out of the ordinary" situations.\n + (It can be used when the distribution is not split.)' + hover_ja: '多変量解析で、正常状態データを基準として、異常が発生した場合それを検出でき、その要因となる変数をリストアップすることができます。\n + 数十個といった多数の変数の動きを監視し、「いつもと違う」状況を検出することができます。\n + (分布がスプリットしていない場合に使用できます。)' + link_address: '/histview2/analyze/anomaly_detection/pca' + - title_en: 'Cause Analysis & Collocation Relationship' + title_ja: '要因分析/共起関係' + tiles: + - row: 1 + column: 1 + title_en: 'SkD Sankey Diagram' + title_ja: 'SkD サンキーダイアグラム' + png_path: 'SkD.png' + hover_en: 'Extracts highly correlated variables with respect to the target variable and plots the relationship with the strength of the correlation. The important parts of fish bones are extracted and the importance (strength of correlation is expressed by thickness) is visualized. They are calculated based on data (data-driven calculations), so you can visualize correlations without having to write fish bones by yourself.' + hover_ja: '目的の変数に関して相関の高い変数を抽出して、相関関係の強さと共にその関係をプロットします。\n魚の骨の重要な部分を抜き出して、重要度(相関の強さを太さで表現)を可視化します。データに基づいて算出(データドリブンでの算出)しますので、魚の骨を書く技術がなくても相関関係を可視化することができます。' + link_address: '/histview2/skd' + - row: 1 + column: 2 + title_en: 'COG Co-occurrence Graph' + title_ja: 'COG 共起グラフ' + png_path: 'COG.png' + hover_en: 'You can visualize co-occurrence relationships (relationships that focus on how often one phenomenon and another occur at the same time). You can visualize relationships such as simultaneous alarms that occur when certain alarms occur, or events that occur when certain conditions occur, and so on.' + hover_ja: '共起関係(ある現象と別の現象が同時に出現する頻度に着目した関係)を可視化できます。\nあるアラームが発生した際に同時に発生するアラーム、ある状態が発生した際に発生するイベントなどの関係を可視化できます。' + link_address: '/histview2/cog' diff --git a/histview2/config/tile_interface_dn7.yml b/histview2/config/tile_interface_dn7.yml new file mode 100644 index 0000000..09a1478 --- /dev/null +++ b/histview2/config/tile_interface_dn7.yml @@ -0,0 +1,69 @@ +!!omap +- sections: + - title_en: 'DN7 Digital Native QC 7 Tools' + title_ja: 'DN7 Digital Native QC 7 Tools' + tiles: + - row: 1 + column: 1 + title_en: 'FPP Full Points Plot' + title_ja: 'FPP 全数プロット' + png_path: 'FPP.png' + hover_en: 'This is "Time Series Full-point Plot" or "ID Series Full-point Plot" used as an alternative to the X-R plot in Big Data analysis (Average or Range becomes less useful as a statistic when there is too much data). It is used to see trends in data and to check anomalies and outliers.\n + It can also be used as a data downloader that extract and/or integrates data from a database.\n + There is a function called label plot (Blue labels at the top of the graph), and you can grasp relationship between the variable fluctuation and the timing of the change of the collection such as magazine, lot and part number. There is also the ability to view a list of relevant data about the work or the product by selecting [Right Click]→[Plot View] on any plot.' + hover_ja: 'Big Data分析において、X-Rプロットの代替(平均値やレンジはデータ数が多くなりすぎると統計値としての有効性が薄れます)として用いる全数データの時系列もしくはワーク・製品ID系列のプロットです。データのトレンド変化や異常値・外れ値の可視化に使用します。\n + データベースからデータ取得・紐付け統合したデータのダウンローダとしても使用できます。\n + ラベルプロットという機能(グラフ上部にある青いラベル群)があり、マガジン・ロット・品種といった纏まりの切り替わりタイミングと変数の変動との関係を把握することができます。\nまた、任意のプロットで[右クリック]→[Plot View]を選択すると、そのワークや製品に関する関連するデータを一覧として参照できる機能もあります。' + link_address: '/histview2/fpp' + - row: 1 + column: 2 + title_en: 'RLP Ridgeline Plot' + title_ja: 'RLP リッジラインプロット' + png_path: 'RLP.png' + hover_en: 'In Big Data analysis, there are so many plotting points and it is often a non-normal distribution, but it is difficult to grasp the distribution fluctuation even though outliers can be grasped in FPP (Full-points Plot). You can visualize process variations by drawing density plots for each set of data and aligning it along the time or ID series.\n + The graph plotted by points shows the increasing or decreasing trend of the distribution, indicating whether the distribution is moving downward (transitioning toward a lower value as a whole) or upward (transitioning toward a higher value). If it is straight, it means that there is little process variation. In case of continuous shift, there is a characteristic drift. And if it fluctuates rapidly, it means that it is a changing point in production line.' + hover_ja: 'Big Data分析においては打点が多く、非正規分布であることが多いため、全数プロットFPPだけでは外れ値などは把握できるものの分布変動を把握することが難しいです。\nいくつかのデータ群毎に密度曲線(Density Plot)を描画し系列に沿って並べることで、工程の変動を把握することができます。\n + 点でプロットされているグラフは、分布の増加・減少傾向を示しており、分布が下方向(全体として値が小さくなる方向に遷移)に動いているか、上方向に動いているか(値が大きくなる方向に遷移)しているか、が分かります。まっすぐな場合は工程変動があまりないということを意味しています。\n継続してずれていく場合は特性のドリフトがある、また、急激に変動した場合は何らかの工程の変化点であることを示唆します。' + link_address: '/histview2/rlp' + - row: 1 + column: 3 + title_en: 'CHM Calendar Heat Map' + title_ja: 'CHM カレンダヒートマップ' + png_path: 'CHM.png' + hover_en: 'It visualizes long-term data such as annual variations in processes and parameters that can vary depending on days of the week or shifts, etc. For example, it is possible to visualize the rate of defects or changes in production volume over a long period of time. It is possible to understand changes in specific patterns, such as a high rate of defects at the beginning of the week, or the occurrence of specific alarms soon after the lunch breaks, and so on.' + hover_ja: '工程での年間変動など長期間のデータや曜日・シフトなどで変動しうるパラメータを可視化します。\n例えば不良率や生産量の推移などを長期にわたって可視化することができ、例えば週明けが不良率が高いとか、昼休み空けに特定のアラームが発生しやすいといった、特定のパターンの変動などを把握することが可能です。' + link_address: '/histview2/chm' + - row: 2 + column: 1 + title_en: 'MSP Multi Scatter Plot' + title_ja: 'MSP 相関行列プロット' + png_path: 'MSP.png' + hover_en: 'You can check the correlation between multiple variables. You can display up to four variables in this software. If you want to understand the correlation between more variables, use PCP (Parallel Coordinate Plot) as below.' + hover_ja: '複数変数間の相関関係を確認できます。\n4変数までの表示に対応できますが、それ以上の変数間の相関を把握するには、平行座標プロットPCPをご活用ください。' + link_address: '/histview2/msp' + - row: 2 + column: 2 + title_en: 'PCP Parallel Cordinate Plot' + title_ja: 'PCP 平行座標プロット' + png_path: 'PCP.png' + hover_en: 'You can see the correlation between a large number of variables for a given target variable. Determine that the axes are color-separated on each axis (Each axis is parallel to the left and right), and that there is a variable to watch out for, or some correlation (In particular, if they are arranged in order of correlation coefficients, you can see the axis where the color-separation of interest are located at both ends. You can think of the axis with mixed colors as having a low correlation.).\n + A linear correlation is observed for axes that are well-balanced iridescent (the same color arrangement as the color bar [positive correlation] or inverted color arrangement [negative correlation]). If the colors are separated, but there is a biased color distribution, there may be a nonlinear relationship, so you need to perform some function conversion or clustering for stratification (In this case, it is a good idea to check the relationship directly between the separated variables and the target variables using scatter plot).' + hover_ja: '目的の変数に関して、大量の変数間の相関関係を確認できます。\nそれぞれの軸(各軸が左右に平行に配置されています)で色が分かれている軸が注意すべき変数、すなわち何らかの相関がある軸と判断してください(特に相関係数順に並べている場合は両端に着目すべき色が分かれた軸が見えます。色が混ざっている軸は相関が低いと考えて結構です)。\n + バランスよく虹色になっている軸では線形の相関(カラーバーと同じ色配置[正の相関]もしくは反転した色配置[負の相関])が認められます。色が分離しているものの偏った色配置になっている場合は非線形の関係が考えられますので、何らかの関数変換を行なったり、クラスタリングし層別する必要があります(分離している変数と目的変数で散布図を用いて関係を確認することをお勧めします)。' + link_address: '/histview2/pcp' + - row: 2 + column: 3 + title_en: 'SkD Sankey Diagram' + title_ja: 'SkD サンキーダイアグラム' + png_path: 'SkD.png' + hover_en: 'Extracts highly correlated variables with respect to the target variable and plots the relationship with the strength of the correlation. The important parts of fish bones are extracted and the importance (strength of correlation is expressed by thickness) is visualized. They are calculated based on data (data-driven calculations), so you can visualize correlations without having to write fish bones by yourself.' + hover_ja: '目的の変数に関して相関の高い変数を抽出して、相関関係の強さと共にその関係をプロットします。\n魚の骨の重要な部分を抜き出して、重要度(相関の強さを太さで表現)を可視化します。データに基づいて算出(データドリブンでの算出)しますので、魚の骨を書く技術がなくても相関関係を可視化することができます。' + link_address: '/histview2/skd' + - row: 2 + column: 4 + title_en: 'COG Co-occurrence Graph' + title_ja: 'COG 共起グラフ' + png_path: 'COG.png' + hover_en: 'You can visualize co-occurrence relationships (relationships that focus on how often one phenomenon and another occur at the same time). You can visualize relationships such as simultaneous alarms that occur when certain alarms occur, or events that occur when certain conditions occur, and so on.' + hover_ja: '共起関係(ある現象と別の現象が同時に出現する頻度に着目した関係)を可視化できます。\nあるアラームが発生した際に同時に発生するアラーム、ある状態が発生した際に発生するイベントなどの関係を可視化できます。' + link_address: '/histview2/cog' diff --git a/histview2/heatmap/__init__.py b/histview2/heatmap/__init__.py new file mode 100644 index 0000000..ef2f9b4 --- /dev/null +++ b/histview2/heatmap/__init__.py @@ -0,0 +1,4 @@ + +def create_module(app, **kwargs): + from .controllers import heatmap_blueprint + app.register_blueprint(heatmap_blueprint) diff --git a/histview2/heatmap/controllers.py b/histview2/heatmap/controllers.py new file mode 100644 index 0000000..7d45874 --- /dev/null +++ b/histview2/heatmap/controllers.py @@ -0,0 +1,19 @@ +import os + +from flask import Blueprint, render_template + +from histview2.common.services.form_env import get_common_config_data + +heatmap_blueprint = Blueprint( + 'heatmap', + __name__, + template_folder=os.path.join('..', 'templates', 'heatmap'), + static_folder=os.path.join('..', 'static', 'heatmap'), + url_prefix='/histview2' +) + + +@heatmap_blueprint.route('/chm') +def index(): + output_dict = get_common_config_data() + return render_template("heatmap.html", **output_dict) diff --git a/histview2/heatmap/services/__init__.py b/histview2/heatmap/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/heatmap/services/utils.py b/histview2/heatmap/services/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/multiple_scatter_plot/__init__.py b/histview2/multiple_scatter_plot/__init__.py new file mode 100644 index 0000000..7c672b1 --- /dev/null +++ b/histview2/multiple_scatter_plot/__init__.py @@ -0,0 +1,4 @@ + +def create_module(app, **kwargs): + from .controllers import multiple_scatter_plot_blueprint + app.register_blueprint(multiple_scatter_plot_blueprint) diff --git a/histview2/multiple_scatter_plot/controllers.py b/histview2/multiple_scatter_plot/controllers.py new file mode 100644 index 0000000..4406dff --- /dev/null +++ b/histview2/multiple_scatter_plot/controllers.py @@ -0,0 +1,20 @@ +import os + +from flask import Blueprint, render_template + +from histview2.common.services.form_env import get_common_config_data + +multiple_scatter_plot_blueprint = Blueprint( + 'multiple_scatter_plot', + __name__, + template_folder=os.path.join('..', 'templates', 'multiple_scatter_plot'), + static_folder=os.path.join('..', 'static', 'multiple_scatter_plot'), + # static_url_path='../static/trace_data', + url_prefix='/histview2' +) + + +@multiple_scatter_plot_blueprint.route('/msp') +def index(): + output_dict = get_common_config_data() + return render_template("multiple_scatter_plot.html", **output_dict) diff --git a/histview2/multiple_scatter_plot/services/__init__.py b/histview2/multiple_scatter_plot/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/multiple_scatter_plot/services/utils.py b/histview2/multiple_scatter_plot/services/utils.py new file mode 100644 index 0000000..89377d3 --- /dev/null +++ b/histview2/multiple_scatter_plot/services/utils.py @@ -0,0 +1,28 @@ +from histview2.common.constants import * +from histview2.common.yaml_utils import YamlConfig, DBConfigYaml, ProcConfigYaml + +def get_valid_procs(procs): + """ + Get valid process to show on selectbox 起点 + Arguments: + procs {dict} + + Returns: + dict -- valid process on 起点 + """ + proc_list = {} + filter_info = procs['filter_info'] + proc_master = procs['proc_master'] + + for key, value in filter_info.items(): + if len(filter_info[key]) > 0: + filter_time = False + for item in filter_info[key]: + if item.get('item_info', {}) \ + and item['item_info'].get('type') \ + and item['item_info']['type'] == 'datehour-range': + filter_time = True + if filter_time: + proc_list.update({key: proc_master[key]}) + + return proc_list diff --git a/histview2/parallel_plot/__init__.py b/histview2/parallel_plot/__init__.py new file mode 100644 index 0000000..b1677bc --- /dev/null +++ b/histview2/parallel_plot/__init__.py @@ -0,0 +1,4 @@ + +def create_module(app, **kwargs): + from .controllers import parallel_plot_blueprint + app.register_blueprint(parallel_plot_blueprint) diff --git a/histview2/parallel_plot/controllers.py b/histview2/parallel_plot/controllers.py new file mode 100644 index 0000000..3b5a985 --- /dev/null +++ b/histview2/parallel_plot/controllers.py @@ -0,0 +1,21 @@ +import os + +from flask import Blueprint, render_template + +from histview2.common.services.form_env import get_common_config_data + +parallel_plot_blueprint = Blueprint( + 'parallel_plot', + __name__, + template_folder=os.path.join('..', 'templates', 'parallel_plot'), + static_folder=os.path.join('..', 'static', 'parallel_plot'), + # static_url_path='../static/trace_data', + url_prefix='/histview2' +) + + +@parallel_plot_blueprint.route('/pcp') +def index(): + output_dict = get_common_config_data() + # print(kde_data) + return render_template("parallel_plot.html", **output_dict) diff --git a/histview2/parallel_plot/services/__init__.py b/histview2/parallel_plot/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/parallel_plot/services/utils.py b/histview2/parallel_plot/services/utils.py new file mode 100644 index 0000000..89377d3 --- /dev/null +++ b/histview2/parallel_plot/services/utils.py @@ -0,0 +1,28 @@ +from histview2.common.constants import * +from histview2.common.yaml_utils import YamlConfig, DBConfigYaml, ProcConfigYaml + +def get_valid_procs(procs): + """ + Get valid process to show on selectbox 起点 + Arguments: + procs {dict} + + Returns: + dict -- valid process on 起点 + """ + proc_list = {} + filter_info = procs['filter_info'] + proc_master = procs['proc_master'] + + for key, value in filter_info.items(): + if len(filter_info[key]) > 0: + filter_time = False + for item in filter_info[key]: + if item.get('item_info', {}) \ + and item['item_info'].get('type') \ + and item['item_info']['type'] == 'datehour-range': + filter_time = True + if filter_time: + proc_list.update({key: proc_master[key]}) + + return proc_list diff --git a/histview2/ridgeline_plot/__init__.py b/histview2/ridgeline_plot/__init__.py new file mode 100644 index 0000000..f8374c0 --- /dev/null +++ b/histview2/ridgeline_plot/__init__.py @@ -0,0 +1,4 @@ + +def create_module(app, **kwargs): + from histview2.ridgeline_plot.controllers import ridgeline_plot_blueprint + app.register_blueprint(ridgeline_plot_blueprint) diff --git a/histview2/ridgeline_plot/controllers.py b/histview2/ridgeline_plot/controllers.py new file mode 100644 index 0000000..2f47481 --- /dev/null +++ b/histview2/ridgeline_plot/controllers.py @@ -0,0 +1,19 @@ +import os + +from flask import Blueprint, render_template + +from histview2.common.services.form_env import get_common_config_data + +ridgeline_plot_blueprint = Blueprint( + 'ridgeline_plot', + __name__, + template_folder=os.path.join('..', 'templates', 'ridgeline_plot'), + static_folder=os.path.join('..', 'static', 'ridgeline_plot'), + url_prefix='/histview2' +) + + +@ridgeline_plot_blueprint.route('/rlp') +def ridgeline_plot(): + output_dict = get_common_config_data() + return render_template("ridgeline_plot.html", **output_dict) diff --git a/histview2/ridgeline_plot/services/__init__.py b/histview2/ridgeline_plot/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/ridgeline_plot/services/utils.py b/histview2/ridgeline_plot/services/utils.py new file mode 100644 index 0000000..422901d --- /dev/null +++ b/histview2/ridgeline_plot/services/utils.py @@ -0,0 +1,30 @@ +from histview2.common.constants import * +from histview2.common.yaml_utils import YamlConfig, DBConfigYaml, ProcConfigYaml + + +def get_valid_procs(procs): + """ + Get valid process to show on selectbox 起点 + Arguments: + procs {dict} + + Returns: + dict -- valid process on 起点 + """ + proc_list = {} + filter_info = procs['filter_info'] + proc_master = procs['proc_master'] + + for key, value in filter_info.items(): + if len(filter_info[key]) > 0: + filter_time = False + for item in filter_info[key]: + if item.get('item_info', {}) \ + and item['item_info'].get('type') \ + and item['item_info']['type'] == 'datehour-range': + filter_time = True + if filter_time: + proc_list.update({key: proc_master[key]}) + + return proc_list + diff --git a/histview2/sankey_plot/__init__.py b/histview2/sankey_plot/__init__.py new file mode 100644 index 0000000..b9d37e7 --- /dev/null +++ b/histview2/sankey_plot/__init__.py @@ -0,0 +1,4 @@ + +def create_module(app, **kwargs): + from .controllers import sankey_plot_blueprint + app.register_blueprint(sankey_plot_blueprint) diff --git a/histview2/sankey_plot/controllers.py b/histview2/sankey_plot/controllers.py new file mode 100644 index 0000000..0af0317 --- /dev/null +++ b/histview2/sankey_plot/controllers.py @@ -0,0 +1,19 @@ +import os + +from flask import Blueprint, render_template + +from histview2.common.services.form_env import get_common_config_data + +sankey_plot_blueprint = Blueprint( + 'sankey_plot', + __name__, + template_folder=os.path.join('..', 'templates', 'sankey_plot'), + static_folder=os.path.join('..', 'static', 'sankey_plot'), + url_prefix='/histview2' +) + + +@sankey_plot_blueprint.route('/skd') +def index(): + output_dict = get_common_config_data() + return render_template("sankey_plot.html", **output_dict) diff --git a/histview2/sankey_plot/services/__init__.py b/histview2/sankey_plot/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/sankey_plot/services/utils.py b/histview2/sankey_plot/services/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/scatter_plot/__init__.py b/histview2/scatter_plot/__init__.py new file mode 100644 index 0000000..51aa023 --- /dev/null +++ b/histview2/scatter_plot/__init__.py @@ -0,0 +1,4 @@ + +def create_module(app, **kwargs): + from .controllers import scatter_plot_blueprint + app.register_blueprint(scatter_plot_blueprint) diff --git a/histview2/scatter_plot/controllers.py b/histview2/scatter_plot/controllers.py new file mode 100644 index 0000000..4cc246e --- /dev/null +++ b/histview2/scatter_plot/controllers.py @@ -0,0 +1,28 @@ +import os + +from flask import Blueprint, render_template + +from histview2 import dic_yaml_config_file +from histview2.common.constants import * +from histview2.common.services.form_env import get_common_config_data + +scatter_plot_blueprint = Blueprint( + 'scatter_plot', + __name__, + template_folder=os.path.join('..', 'templates', 'scatter_plot'), + static_folder=os.path.join('..', 'static', 'scatter_plot'), + # static_url_path='../static/trace_data', + url_prefix='/histview2' +) + +# ローカルパラメータの設定 +local_params = { + "config_yaml_fname_proc": dic_yaml_config_file[YAML_CONFIG_PROC], + "config_yaml_fname_histview2": dic_yaml_config_file[YAML_CONFIG_HISTVIEW2], + "config_yaml_fname_db": dic_yaml_config_file[YAML_CONFIG_DB]} + + +@scatter_plot_blueprint.route('/scp') +def index(): + output_dict = get_common_config_data() + return render_template("scatter_plot.html", **output_dict) diff --git a/histview2/scatter_plot/services/__init__.py b/histview2/scatter_plot/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/histview2/scatter_plot/services/utils.py b/histview2/scatter_plot/services/utils.py new file mode 100644 index 0000000..89377d3 --- /dev/null +++ b/histview2/scatter_plot/services/utils.py @@ -0,0 +1,28 @@ +from histview2.common.constants import * +from histview2.common.yaml_utils import YamlConfig, DBConfigYaml, ProcConfigYaml + +def get_valid_procs(procs): + """ + Get valid process to show on selectbox 起点 + Arguments: + procs {dict} + + Returns: + dict -- valid process on 起点 + """ + proc_list = {} + filter_info = procs['filter_info'] + proc_master = procs['proc_master'] + + for key, value in filter_info.items(): + if len(filter_info[key]) > 0: + filter_time = False + for item in filter_info[key]: + if item.get('item_info', {}) \ + and item['item_info'].get('type') \ + and item['item_info']['type'] == 'datehour-range': + filter_time = True + if filter_time: + proc_list.update({key: proc_master[key]}) + + return proc_list diff --git a/histview2/script/call_r_process.py b/histview2/script/call_r_process.py new file mode 100644 index 0000000..2714fc5 --- /dev/null +++ b/histview2/script/call_r_process.py @@ -0,0 +1,14 @@ +from histview2.script.r_scripts.wrapr import wrapr_utils +from histview2.common.common_utils import get_data_path, get_wrapr_path + + +def call_r_process(): + dir_out = get_data_path() + dir_wrapr = get_wrapr_path() + dic_task = dict(func='hello_world', file='hello_world') + + try: + pipe = wrapr_utils.RPipeline(dir_wrapr, dir_out) + pipe.run({}, [dic_task]) + except Exception as e: + pass diff --git a/histview2/script/check_and_convert_yaml_files.py b/histview2/script/check_and_convert_yaml_files.py new file mode 100644 index 0000000..9432295 --- /dev/null +++ b/histview2/script/check_and_convert_yaml_files.py @@ -0,0 +1,120 @@ +import os +import shutil +from datetime import datetime + +from histview2.common.common_utils import resource_path +from histview2.common.logger import log_execution_time, log_execution +from histview2.common.yaml_utils import * + +DATA_TYPE = 'data_type' +NAME = 'name' +KEY = 'key' + + +def check_proc_id(proc_id_info, db_yaml): + # check if proc_id is old, if not, return + dbs = YamlConfig.get_node(db_yaml.dic_config, [YAML_DB], {}) or {} + + # check for old proc_id + for db_code, db_obj in dbs.items(): + proc_id_node = YamlConfig.get_node(db_obj, proc_id_info.get(KEY)) + if proc_id_node and type(proc_id_node).__name__ not in proc_id_info.get(DATA_TYPE): + return [proc_id_info.get(NAME)] + + return [] + + +def convert_proc_id_node(proc_id_info, db_yaml, proc_yaml): + # convert to new format for each key -> write to file + dbs = YamlConfig.get_node(db_yaml.dic_config, [YAML_DB], {}) or {} + for db_code, db_obj in dbs.items(): + # convert for each db + proc_id_node = YamlConfig.get_node(db_obj, proc_id_info.get(KEY)) + if proc_id_node and type(proc_id_node).__name__ not in proc_id_info.get(DATA_TYPE): + procs = YamlConfig.get_node(proc_yaml.dic_config, [YAML_PROC], {}) + for proc_code, proc_obj in procs.items(): + proc_db = YamlConfig.get_node(proc_obj, [YAML_DB], '') + if proc_db == db_code: + new_proc_id_node = { + proc_code: parse_int_value(proc_id_node) + } + universal_db_node = YamlConfig.get_node(db_obj, [YAML_UNIVERSAL_DB]) + if universal_db_node and type(universal_db_node).__name__ in ('dict', 'ordereddict'): + db_yaml.dic_config[YAML_DB][db_code][YAML_UNIVERSAL_DB][YAML_PROC_ID] = new_proc_id_node + + +def backup_current_yml(): + config_path = resource_path('histview2', 'config') + bk_folder = 'bk_' + datetime.strftime(datetime.now(), '%Y%m%d_%H%M%S') + config_bk_path = os.path.join(config_path, bk_folder) + if not os.path.exists(config_bk_path): + try: + os.mkdir(config_bk_path) + yaml_files = [f for f in os.listdir(config_path) if f.endswith('yml')] + for yaml_file in yaml_files: + shutil.copy2(os.path.join(config_path, yaml_file), config_bk_path) + except Exception: + traceback.print_exc() + + +@log_execution() +def check_and_convert_old_yaml(): + # check exist + if not os.path.exists(dic_yaml_config_file['db']): + return + + # define keys to check + proc_id_info = { + NAME: YAML_PROC_ID, + KEY: [YAML_UNIVERSAL_DB, YAML_PROC_ID], + DATA_TYPE: ['dict', 'ordereddict'] + } + + # original yaml + basic_yaml = BasicConfigYaml() + + # current version + cur_ver = str(basic_yaml.get_version()) + + # new version + new_ver = str(dic_yaml_config_file[YAML_CONFIG_VERSION]) + + # check yaml version + # TODO: unsafe: '0.10.0' >= '0.9.0' (=False) + if cur_ver >= new_ver: + return + + proc_yaml = ProcConfigYaml() + db_yaml = DBConfigYaml() + old_node_list = [] + + # check for old proc_id nodes + proc_id_node_old = check_proc_id(proc_id_info, db_yaml) + if proc_id_node_old: + old_node_list.extend(proc_id_node_old) + + # check other nodes go here + + # if there is no old node -> return + if not old_node_list: + # first release : we need this code to convert 0.0.0 to 1 for some user + # who already run convert old yaml, but did not update yaml version + # update yaml version + basic_yaml.set_version(new_ver) + basic_yaml.save_yaml() + return + + # backup yaml files just in case + backup_current_yml() + + # convert to new format + for old_node in old_node_list: + if old_node == YAML_PROC_ID: + convert_proc_id_node(proc_id_info, db_yaml, proc_yaml) + + # update back to file + db_yaml.update_yaml(db_yaml.dic_config) + + # update yaml version + basic_yaml.set_version(new_ver) + basic_yaml.save_yaml() diff --git a/histview2/script/check_r_portable.py b/histview2/script/check_r_portable.py new file mode 100644 index 0000000..69a2d4e --- /dev/null +++ b/histview2/script/check_r_portable.py @@ -0,0 +1,105 @@ +import os +import shutil +import time +import traceback + +from loguru import logger + +from histview2.common.common_utils import resource_path +from histview2.common.constants import AbsPath, R_PORTABLE, YAML_INFO, YAML_R_PATH, R_LIB_VERSION +from histview2.common.logger import log_execution +from histview2.common.yaml_utils import BasicConfigYaml + + +def copy_with_update(src, dst, symlinks=False, ignore=None): + """ Copy with update + https://stackoverflow.com/questions/1868714/ + how-do-i-copy-an-entire-directory-of-files-into-an-existing-directory-using-pyth + :param src: + :param dst: + :param symlinks: + :param ignore: + :return: + """ + if not os.path.exists(dst): + os.makedirs(dst) + + # # remove dest folders or files which src doesn't have + # for item in os.listdir(dst): + # s = os.path.join(src, item) + # d = os.path.join(dst, item) + # if not os.path.exists(s): + # try: + # if os.path.isdir(d): + # shutil.rmtree(d) + # else: + # os.remove(d) + # except: + # pass + + for item in os.listdir(src): + s = os.path.join(src, item) + d = os.path.join(dst, item) + if os.path.isdir(s): + copy_with_update(s, d, symlinks, ignore) + else: + # copy only new or newer files + if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1: + shutil.copy2(s, d) + + +def should_update_r(source_dir, dest_dir): + try: + if not os.path.exists(dest_dir): + return True + if not os.path.exists(source_dir): + return False + + src_version_file = os.path.join(source_dir, R_LIB_VERSION) + dest_version_file = os.path.join(dest_dir, R_LIB_VERSION) + if not os.path.exists(src_version_file): + return False + if not os.path.exists(dest_version_file): + return True + + with open(src_version_file, 'r') as f: + src_version = f.read() + + with open(dest_version_file, 'r') as f: + dest_version = f.read() + + if dest_version < src_version: + return True + except: + traceback.print_exc() + + return False + + +@log_execution() +def check_and_copy_r_portable(): + basic_config_yml = BasicConfigYaml() + source = basic_config_yml.get_node(basic_config_yml.dic_config, [YAML_INFO, YAML_R_PATH]) + if not source: + return + + start = time.time() + project_dir = resource_path(level=AbsPath.SHOW) + dest_r_portable_dir = os.path.join(project_dir, R_PORTABLE) + source_r_portable_dir = os.path.join(source, R_PORTABLE) + + try: + should_update = should_update_r(source_r_portable_dir, dest_r_portable_dir) + print('Should update R libraries: ', should_update) + if should_update: + print('Copying R libraries from {src} to {dest} ...'.format(src=source_r_portable_dir, + dest=dest_r_portable_dir)) + copy_with_update(source_r_portable_dir, dest_r_portable_dir) + + end = time.time() + print('DONE updating R in {sec} seconds'.format(sec=(end - start))) + logger.info('DONE updating R in {sec} seconds'.format(sec=(end - start))) + except Exception as ex: + traceback.print_exc() + end = time.time() + logger.error('ERROR updating R in {sec} seconds. Error: {err}'.format(sec=(end - start), err=ex)) diff --git a/histview2/script/convert_user_setting.py b/histview2/script/convert_user_setting.py new file mode 100644 index 0000000..a2bbbbe --- /dev/null +++ b/histview2/script/convert_user_setting.py @@ -0,0 +1,139 @@ +import json +import re +from typing import List + +from histview2.common.constants import USER_SETTING_VERSION, DataTypeEncode, EN_DASH + + +def get_setting_item_by_name(settings, key_name): + setting_items = [item for item in settings if 'name' in item and item['name'] == key_name] + return setting_items[0] if len(setting_items) > 0 else None + + +def convert_user_setting(): + """ + Convert user setting from old version + :return: None + """ + try: + print('------------CONVERT USER SETTING VERSION: START ------------') + from histview2.setting_module.models import make_session, CfgUserSetting, CfgProcessColumn + + except_key_name = ['All', ''] + apply_data_types = ['TEXT', 'INT'] + with make_session() as meta_session: + user_settings: List[CfgUserSetting] = meta_session.query(CfgUserSetting).all() + + update_settings = [] + for user_setting in user_settings: + settings = json.loads(user_setting.settings) + update_setting = user_setting # CfgUserSetting model + + if 'version' not in settings or settings['version'] < USER_SETTING_VERSION: + # find form-keys from setting + form_keys = [key for key in list(settings.keys()) if key != 'version'] + setting_forms = { + 'version': USER_SETTING_VERSION + } + for form_key in form_keys: + setting_forms[form_key] = [] + for setting_item in settings[form_key]: + update_setting_item = setting_item + if 'name' in setting_item: + # check datatype + if setting_item['name'].startswith('GET02_VALS_SELECT') and \ + setting_item['value'] not in except_key_name: + variable_id = setting_item['value'] + data_type_name = 'dataType-{}'.format(variable_id) + has_data_type = get_setting_item_by_name(settings[form_key], data_type_name) + + setting_forms[form_key].append(setting_item) + if not has_data_type: + # find sensor + variable = meta_session.query(CfgProcessColumn).get(variable_id) + # Add data type for sensor Str and Int + if variable and variable.data_type in apply_data_types: + data_type_item = { + 'id': data_type_name, + 'name': '', + 'value': DataTypeEncode[variable.data_type].value, + 'type': 'text' + } + setting_forms[form_key].append(data_type_item) + + # update autoupdateinterval + if setting_item['name'] == 'autoUpdateInterval': + # setting_forms[form_key].append({ + # 'id': 'autoUpdateInterval', + # 'name': 'autoUpdateInterval', + # 'value': '0', + # 'type': 'checkbox', + # 'checked': True if setting_item['value'] == '1' else False, + # }) + update_setting_item = { + 'id': 'autoUpdateInterval', + 'name': 'autoUpdateInterval', + 'value': '0', + 'type': 'checkbox', + 'checked': True if setting_item['value'] == '1' else False, + } + + # update showScatterPlotSelect + if setting_item['name'] == 'showScatterPlotSelect': + # setting_forms[form_key].append({ + # 'id': 'showScatterPlotSelect', + # 'name': 'showScatterPlotSelect', + # 'value': '0', + # 'type': 'checkbox', + # 'checked': True if setting_item['value'] == '1' else False, + # }) + update_setting_item = { + 'id': 'showScatterPlotSelect', + 'name': 'showScatterPlotSelect', + 'value': '0', + 'type': 'checkbox', + 'checked': True if setting_item['value'] == '1' else False, + } + + # convert datetime + # DATETIME_RANGE_PICKER + if setting_item['name'] == 'DATETIME_RANGE_PICKER': + update_setting_item['value'] = re.sub('[~]', EN_DASH, setting_item['value']) + # setting_item['value'] = re.sub(' - ', EN_DASH, setting_item['value']) + # convert category + # GET02_CATE_SELECT1: "checkbox-4163end-proc-cate-val-div-1" -> categoryLabel-4163 + setting_forms[form_key].append(update_setting_item) + update_setting.settings = json.dumps(setting_forms) + # save setting into DB here + update_settings.append(update_setting) + + print('------------CONVERT USER SETTING VERSION: END ------------') + except Exception as e: + raise e + + return user_settings + + +def convert_user_setting_url(): + dic_old_urls = { + 'fpp': ['trace_data'], + 'stp': ['categorical_plot'], + 'rlp': ['ridgeline_plot'], + 'chm': ['heatmap'], + 'msp': ['multiple_scatter_plot'], + 'scp': ['scatter_plot'], + 'pcp': ['parallel_plot'], + 'skd': ['sankey_plot'], + 'cog': ['co_occurrence'], + } + print('------------CONVERT USER SETTING URL OF OLD VERSION: START ------------') + from histview2.setting_module.models import make_session, CfgUserSetting + with make_session() as meta_session: + user_settings = meta_session.query(CfgUserSetting).order_by(CfgUserSetting.updated_at).all() + for user_setting in user_settings: + for url, old_urls in dic_old_urls.items(): + for old_url in old_urls: + user_setting.page = re.sub(old_url, url, user_setting.page) + + print('------------CONVERT USER SETTING URL OF OLD VERSION: END ------------') + return True diff --git a/histview2/script/disable_terminal_quickedit.py b/histview2/script/disable_terminal_quickedit.py new file mode 100644 index 0000000..a81bde4 --- /dev/null +++ b/histview2/script/disable_terminal_quickedit.py @@ -0,0 +1,23 @@ + +import msvcrt +import ctypes + +from histview2.common.logger import log_execution + + +@log_execution() +def disable_quickedit(): + ''' + Disable quickedit mode on Windows terminal. quickedit prevents script to + run without user pressing keys.. + ''' + try: + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + device = r'\\.\CONIN$' + with open(device, 'r') as con: + hCon = msvcrt.get_osfhandle(con.fileno()) + kernel32.SetConsoleMode(hCon, 0x0080) + except Exception as e: + print('Cannot disable QuickEdit mode! ' + str(e)) + print('.. As a consequence the script might be automatically\ + paused on Windows terminal') \ No newline at end of file diff --git a/histview2/script/generate_db_secret_key.py b/histview2/script/generate_db_secret_key.py new file mode 100644 index 0000000..c9df11b --- /dev/null +++ b/histview2/script/generate_db_secret_key.py @@ -0,0 +1,6 @@ +from cryptography.fernet import Fernet + +DB_SECRET_KEY = Fernet.generate_key() +print("////////////////////////////") +print("HERE IS YOUR NEW SECRET KEY: " + str(DB_SECRET_KEY, encoding="utf-8")) +print("////////////////////////////") diff --git a/histview2/script/hide_exe_root_folder.py b/histview2/script/hide_exe_root_folder.py new file mode 100644 index 0000000..a3ef7c7 --- /dev/null +++ b/histview2/script/hide_exe_root_folder.py @@ -0,0 +1,33 @@ +import ctypes +import os +import sys + +from histview2.common.common_utils import resource_path +from histview2.common.constants import AbsPath + + +def hide_bundle_folder(): + if not getattr(sys, 'frozen', False): + return + + file_attribute_hidden = 0x02 + current_app_folder = resource_path(level=AbsPath.HIDE) + root_folder = os.path.dirname(current_app_folder) + ret = ctypes.windll.kernel32.SetFileAttributesW(root_folder, file_attribute_hidden) + if ret: + print('attribute set to Hidden') + + +def heartbeat_bundle_folder(): + file_ext = '.temp' + current_app_folder = resource_path(level=AbsPath.HIDE) + print('current_app_folder:', current_app_folder) + current_file = f'{current_app_folder}{file_ext}' + try: + with open(current_file, 'w'): + pass + except Exception: + print('can not make .temp file') + pass + + return current_app_folder, current_file, file_ext diff --git a/histview2/script/hot_fix/fix_db_issues.py b/histview2/script/hot_fix/fix_db_issues.py new file mode 100644 index 0000000..e6b4607 --- /dev/null +++ b/histview2/script/hot_fix/fix_db_issues.py @@ -0,0 +1,58 @@ +from typing import List + +from histview2.common.common_utils import copy_file, delete_file, rename_file +from histview2.common.constants import DB_BACKUP_SUFFIX +from histview2.common.logger import log_execution + + +@log_execution() +def fix_blank_string_port(): + """ + update blank string port to None + :return: + """ + try: + print('------------HOT FIX BLANK PORT: START ------------') + from histview2.setting_module.models import make_session, CfgDataSourceDB + with make_session() as meta_session: + db_details: List[CfgDataSourceDB] = meta_session.query(CfgDataSourceDB).all() + for db_detail in db_details: + if db_detail.port is None or isinstance(db_detail.port, int): + continue + if str(db_detail.port).strip() == '': + db_detail.port = None + print('------------HOT FIX BLANK PORT: END ------------') + except Exception: + pass + + +@log_execution() +def unlock_db(db_path): + """ + unlock db + :param db_path: + :return: + """ + try: + print('------------UNLOCK DB: START ------------', db_path) + tmp = db_path + DB_BACKUP_SUFFIX + delete_file(tmp) + rename_file(db_path, tmp) + copy_file(tmp, db_path) + print('------------UNLOCK DB: END ------------') + except Exception: + print("Can not unlock db. Some processes are using database file.") + + +def reset_import_history(app): + try: + print('------------RESET IMPORT HISTORY: START ------------') + with app.app_context(): + from histview2.setting_module.models import make_session, CsvImport, FactoryImport, JobManagement + with make_session() as meta_session: + meta_session.query(CsvImport).delete() + meta_session.query(FactoryImport).delete() + meta_session.query(JobManagement).delete() + print('------------RESET IMPORT HISTORY: END ------------') + except Exception: + print("Try to reset import history , but tables are not exist") diff --git a/histview2/script/r_scripts/py2r.py b/histview2/script/r_scripts/py2r.py new file mode 100644 index 0000000..8881659 --- /dev/null +++ b/histview2/script/r_scripts/py2r.py @@ -0,0 +1,154 @@ +import os +import time +import pyper +import csv +import pickle + + +r = pyper.R(use_dict=True) +r('source("./histview2/script/r_scripts/common.r")') + +# data_size = ("1KB", "1MB", "100MB") +data_size = ("1MB",) +ip = "./instance/sample_data/42-4/in/" + + +# Calc processing time for `fn` function +def ext(): + def decorator(fn): + def wrapper(*args, **kwargs): + st = time.time() + result = fn(*args, **kwargs) + et = time.time() + 0.005 + exec_time = round((et - st) * 1000) # miliseconds + print("Function `{:s}` executed in {:f} miliseconds!".format(fn.__name__, exec_time)) + return result + return wrapper + return decorator + + +# Write query data into tsv file +def write_tsv(data, ds): + tsv_source = ip + ds + '.tsv' + with open(tsv_source, 'wt', newline='') as f: + writer = csv.writer(f, delimiter='\t') + for row in data: + writer.writerow(row) + return tsv_source + + +# Write query data into pickle file +def write_pickle(data, ds): + pkl_source = ip + ds + '.pkl' + pickle.dump(data, open(pkl_source, "wb")) + return pkl_source + + +@ext() +def p2r_pypy(data): + r.data = data + r.run("res <- py2py(data)") + res = r.get('out') + return res + + +@ext() +def p2r_pyt(data, ds): + r.data = data + r.ds = ds + r.run("res <- pyper2tsv(data, ds)") + tsv = r.get('res') + with open(tsv, newline='') as f: + reader = csv.reader(f, delimiter='\t') + res = list(reader) + return res + + +@ext() +def p2r_pypk(data, ds): + r.data = data + r.ds = ds + r.run("res <- pyper2pkl(data, ds)") + pkl = r.get('res') + return pickle.load(open(pkl, 'rb')) + + +@ext() +def p2r_tpy(data, ds): + r.tsv_source = write_tsv(data, ds) + r.run("res <- tsv2pyper(tsv_source)") + return r.get('res') + + +@ext() +def p2r_tt(data, ds): + r.tsv_source = write_tsv(data, ds) + r.ds = ds + r.run("res <- tsv2tsv(tsv_source, ds)") + ptsv = r.get('res') + with open(ptsv, newline='') as f: + reader = csv.reader(f, delimiter='\t') + res = list(reader) + return res + + +@ext() +def p2r_tpk(data, ds): + r.tsv_source = write_tsv(data, ds) + r.ds = ds + r.run("res <- tsv2pkl(tsv_source, ds)") + tpk = r.get('res') + return pickle.load(open(tpk, 'rb')) + + +@ext() +def p2r_pkpy(data, ds): + r.pkl_source = write_pickle(data, ds) + r.run("res <- pkl2pyper(pkl_source)") + pkl = r.get('res') + return pkl + + +@ext() +def p2r_pkt(data, ds): + r.pkl_source = write_pickle(data, ds) + r.ds = ds + r.run("res <- pkl2tsv(pkl_source, ds)") + ptsv = r.get('res') + with open(ptsv, newline='') as f: + reader = csv.reader(f, delimiter='\t') + res = list(reader) + return res + + +@ext() +def p2r_pkpk(data, ds): + r.pkl_source = write_pickle(data, ds) + r.ds = ds + r.run("res <- pkl2pkl(pkl_source, ds)") + pkl = r.get('res') + return pickle.load(open(pkl, 'rb')) + + +def main(): + # for ds in data_size: + ds = "1KB" + # Get data from csv/ database + with open(ip + ds + ".csv", newline='') as f: + reader = csv.reader(f) + data = list(reader) + + p2r_pypy(data) + p2r_pyt(data, ds) + p2r_pypk(data, ds) + + p2r_tpy(data, ds) + p2r_tt(data, ds) + p2r_tpk(data, ds) + + p2r_pkpy(data, ds) + p2r_pkt(data, ds) + p2r_pkpk(data, ds) + + +main() \ No newline at end of file diff --git a/histview2/script/r_scripts/wrapr/conf/pipeline_config.yml b/histview2/script/r_scripts/wrapr/conf/pipeline_config.yml new file mode 100644 index 0000000..1c7c390 --- /dev/null +++ b/histview2/script/r_scripts/wrapr/conf/pipeline_config.yml @@ -0,0 +1,109 @@ + +scaling.R: + basic: + name: "scaling" + desc: "Normalize given data to zero mean and unit variance." + func: "scaling" + is_last_func: False + params: + outputs: + - field: "x" + title: "Scaled x" + how_to_show: "table" + +heads_and_tails.R: + basic: + name: "Heads and tails" + desc: "Get heads and tails of given data" + func: "heads_and_tails" + is_last_func: False + params: + - name: "nhead" + type: "slider" + min: 10 + max: 100 + step: 10 + default: 10 + - name: "ntail" + type: "slider" + min: 10 + max: 100 + step: 10 + default: 10 + outputs: + - field: "out1" + title: "Head of data" + how_to_show: "table" + - field: "out2" + title: "Tail of data" + how_to_show: "table" + +emd.R: + basic: + name: "Earth Movers Distance (Moving Window)" + desc: "Calculate Earth Movers Distance" + func: "emd" + is_last_func: False + params: + - name: "step_size" + type: "slider" + min: 10 + max: 100 + step: 10 + default: 10 + - name: "window_size" + type: "slider" + min: 10 + max: 100 + step: 10 + default: 30 + outputs: + - field: "x" + title: "EMD" + how_to_show: "table" + +wfp.R: + basic: + name: "Waterfall Plot" + desc: "Visualize density evolve over time" + func: "wfp" + is_last_func: True + params: + - name: "step_size" + type: "slider" + min: 10 + max: 100 + step: 10 + default: 10 + - name: "window_size" + type: "slider" + min: 10 + max: 100 + step: 10 + default: 30 + outputs: + - field: "fname_out" + title: "WFP" + how_to_show: "png" + +glasso.R: + basic: + name: "Graphical LASSO (with target column)" + desc: "Apply graphical lasso and visualize partial correlation" + func: "glasso" + is_last_func: True + params: + - name: "target" + type: "selectbox" + item: "variable" + - name: "max_neighbors" + type: "slider" + min: 1 + max: 10 + step: 1 + default: 1 + outputs: + - field: "fname_out" + title: "Partial Correlations" + how_to_show: "png" + diff --git a/histview2/script/r_scripts/wrapr/wrapr_utils.py b/histview2/script/r_scripts/wrapr/wrapr_utils.py new file mode 100644 index 0000000..02250e6 --- /dev/null +++ b/histview2/script/r_scripts/wrapr/wrapr_utils.py @@ -0,0 +1,315 @@ + +import os +import glob +import pyper +import time +import unicodedata +import base64 +from contextlib import contextmanager + + +class CallR(): + # Class to use pyper.R + # It is safe to set Sys.setenv(lang="en"), and set encoding="utf-8" when loading R file + # Also it is necessary to define .libPaths() to './path/to/R-PORTABLE/library' to ensure that we load libraies from R-PORTABLE + # (I think pyper goes wrong when multibyte string is printed) + def __init__(self, r_exe=None, py_exe=None, use_reticulate=False, verbose=False): + ''' + Start R session + Inputs: + r_exe (str) path to R executable (optional) + verbose (bool) if True, prints R call + Notes: + Sys.setenv(lang="en") is to not print R message in multibyte string + ''' + print('Start R.') + self.verbose = verbose + + # start R with specified binary + if r_exe is None: + self.r_session = pyper.R() + else: + # start specified r executable and .libPaths() + self.r_session = pyper.R(RCMD=r_exe) + self.run_expr('.libPaths(c(""))') + libpath = os.path.join(os.path.dirname(os.path.dirname(r_exe)), 'library') + self.run_expr('.libPaths(c("{}"))'.format(libpath)) + + self.run_expr('options(encoding = "utf-8")') + self.run_expr('Sys.setenv(LANG="en")') + self.run_expr('Sys.setlocale("LC_CTYPE", "ja_jpn")') + + # specify Python binary and load reticulate + if use_reticulate: + if py_exe is not None: + self.run_expr('Sys.setenv(RETICULATE_PYTHON = "{}")'.format(py_exe)) + self.run_expr('library(reticulate)') + + def get_obj(self, obj: str): + # get R object + # obj (str) name of R object to get. + obj = self.r_session.get(obj) + return obj + + def load_rfile(self, fname: str): + # load .R file + # fname (str) file name to load. must be .R file. + expr = 'source("' + fname + '", encoding = "utf-8")' + self.run_expr(expr) + + def run_expr(self, expr: str): + # run R expression + # expr (str) R expression to run. + # - print() is to print R messages (default: True) + # - it is safe to replace escape to slash + # - wrap expr with e <- try() for error handling + expr = expr.replace("\\", "/") + expr = 'e <- try(' + expr + ', silent = FALSE)' + print('running R. expr={}'.format(expr)) + if self.verbose: + print(self.r_session(expr)) + else: + self.r_session(expr) + + # store error messages + self.r_session('if (class(e) == "try-error") e <- list(err = gsub("\n", "", e[1]), err_type="UnexpectedError")') + self.r_session('if (!class(e) == "list") e <- list(err = NULL, err_type = NULL)') + self.r_session('if (!"err" %in% names(e)) e <- list(err = NULL, err_type = NULL)') + self.err = self.get_obj('e') + + +class RPipeline(): + """ + Class to sequentially load & call R functions. + + In each task, results are saved in pickle file, + and file name is passed to the nekt task. + + * The reason why pickle file is used to pass data from R to Python, + is that pyper is unstable to handle large data / multibyte data. + To save data in pickle from R has about 1[sec] of overhead... + """ + + def __init__(self, dir_wrapr, dir_out=None, verbose=True, use_rds=False, use_pkl=True, use_r_portable=True): + """ + Start R process + + Inputs: + dir_wrapr (str) Path to wrapr + dir_out (str) Where to save pickle file. Can be ignored. + If any fname_* is givien, file will be saved in the same directory. + If not, file will be saved in current directory. + verbose (bool) If True, R expression runs with print() + elapse time (python) is printed + use_rds (bool) If True, .rds file will be used if possible. + use_pkl (bool) If False, final result of executed function will be returned by pyper (not by pickle). + Use with care (passing complex / multibyte data by pyper may cause errors) + use_r_portable (bool) If True, finds R.exe inside path/to/R-PORTABLE/bin . + also finds python.exe inside path/to/R-PORTABLE/python/* . + path/to/R-PORTABLE must be defined in environmental variable `R-PORTABLE`. + If False (or R-PORTABLE is not defined), R and Python executable will be automatically searched. + """ + # file name of wrapr and directory name is hard coded.. + FNAME_WRAPPER = 'wrapr.R' + DIR_FUNC = 'func' + self.dir_wrapr = dir_wrapr + self.dir_func = DIR_FUNC + self.dir_out = dir_out + self.verbose = verbose + self.use_rds = use_rds + self.use_pkl = use_pkl + self.use_r_portable = use_r_portable + + self.rportable_path = os.environ.get('R-PORTABLE') + self.dir_log = '.' if self.rportable_path is None else os.path.join(self.rportable_path, '../log') + self.dir_log = self.dir_log if os.path.exists(self.dir_log) else '.' + self.ptime_sec = {} + + with self._timer('start R process'): + # set R portable and python embeddable + # set path to python binary as environmental variable 'RETICULATE_PYTHON' for reticulate. + rpath = None + pypath = None + if self.rportable_path is not None: + rpath = os.path.join(self.rportable_path, 'bin', 'R.exe') + print('`R-PORTABLE` : {}'.format(self.rportable_path)) + if (self.use_pkl is True) and (self.rportable_path is not None): + pypath = glob.glob('{}/python/python-*/python.exe'.format(self.rportable_path))[0] + print('python binary : {}'.format(pypath)) + + # start R process and load wrapper function + self.r = CallR(rpath, pypath, self.use_pkl, self.verbose) + fname_wrapper = os.path.join(self.dir_wrapr, FNAME_WRAPPER) + self.r.load_rfile(fname_wrapper) + + def run(self, dic_data: dict, tasks: list) -> dict: + """ + Run pipeline + + Inputs: + dic_data (dict) Dictionary containing filenames of input data. + Keys: 'fname_x', 'fname_y', 'fname_newx'. + Can be ignored if a task does not need any input data. + tasks (list) List of dictionaries of each task + + Returns: + dict + + If finished successfully: + { + 'fname_out': path to the results (pickle file), + 'func': name of the function, + 'ptime_sec': {dict containing process time of each step (load/transform/save data)}, + 'err': None + } + If error occur in R process: + { + 'err': error message + } + """ + + print('Pipeline start: {}'.format([x['func'] for x in tasks])) + self.dic_data = dic_data.copy() + self.tasks = [x.copy() for x in tasks] + self.res = [] + + for (t, task) in enumerate(tasks): + print('Task: {}'.format(task['func'])) + + with self._timer(task['func']): + with self._timer('{}: load source file'.format(task['func'])): + fname = os.path.basename(task['file']) + fpath = glob.glob('{}/{}/*/{}'.format(self.dir_wrapr, self.dir_func, fname))[0] + self.r.load_rfile(fpath) + if self.r.err['err'] is not None: + print('R ERROR: message={}'.format(self.r.err)) + return(self.r.err) + + with self._timer('{}: run R expr'.format(task['func'])): + expr = self._gen_r_expr(t, task) + self.r.run_expr(expr) + if self.r.err['err'] is not None: + print('R ERROR: message={}'.format(self.r.err)) + return(self.r.err) + res = self.r.get_obj('res') + res = self._decode_all_base64(res) + self.res.append(res) + + # return info of the last task + return self.res[-1] + + def _gen_r_expr(self, t, task): + # are we going to save results as .rds file? + if self._check_rds_available(t): + task.update({'type_out': 'rds'}) + + # final results are saved in pickle? + if self.use_pkl is False and (t + 1) is len(self.tasks): + task.update({'type_out': 'list'}) + + # create expression (e.g. res <- wrapr('fname_x=hoge1.tsv', 'fname_y=hoge2.tsv', ..., 'dir_out="."')) + if t is 0: + # first task takes tsv or no fnames + dic_inputs = self.dic_data.copy() + dic_inputs.update(task) + else: + # from second task, use pickle file of previous task + dic_inputs = {'fname_prev': self.res[-1]['fname_out']} + dic_inputs.update(task) + dic_inputs.update({'dir_out': self.dir_out}) + dic_inputs.update({'dir_log': self.dir_log}) + str_params = self._gen_param_string(dic_inputs.keys(), dic_inputs.values()) + expr = 'res <- wrapr({})'.format(str_params) + return expr + + def _check_rds_available(self, t): + # check that current task can save output as .rds file. + # .rds is used to save results and pass to the next process, + # If + # - self.use_rds is True + # - current task is not the last one + # - current task is R func and the next is R too + res = False + if self.use_rds is True and (t+1) < len(self.tasks): + is_this_task_r = '.R' in self.tasks[t]['file'] + is_next_task_r = '.R' in self.tasks[t+1]['file'] + if is_this_task_r and is_next_task_r: + res = True + return res + + def _gen_param_string(self, keys, values): + # generate single string which denotes parameters to pass to wrapr() function. + # e.g. 'func=process2, 'dir_out=".", nhead=5' + + def _concat_param_without_quotes(k, v): + # {k}=v: for numbers and functions + return str(k) + '=' + str(v) + + def _concat_param_with_quotes(k, v): + # {k}="{v}": for strings + return str(k) + '=' + "'" + str(v) + "'" + + list_params = [] + for k, v in zip(keys, values): + if v is not None: + # generate paramter string + if k == 'func' or type(v) is not str: + list_params.append(_concat_param_without_quotes(k, v)) + else: + # base64 encoding (if necessary) + v = self._enc_base64_if_multibyte(v) + list_params.append(_concat_param_with_quotes(k, v)) + + str_params = ', '.join(list_params) + return str_params + + def _enc_base64_if_multibyte(self, x): + # encode with base64 if multibyte character is detected + # x: str, int, float + + def _detect_multibyte_str(x): + len_text = 0 + for c in x: + j = unicodedata.east_asian_width(c) + if j in 'FWA': + len_text += 2 + else: + len_text += 1 + + if len_text > len(x): + return True + else: + return False + + if _detect_multibyte_str(x): + x_enc = base64.b64encode(str(x).encode('utf8')) + x_enc = 'base64_' + str(x_enc.decode()) + print(x_enc) + return x_enc + else: + return x + + def _decode_all_base64(self, obj): + # decode base64 if 'base64_' is detected in character + # obj: any python object + + def _dec_base64_if_multibyte(x): + if isinstance(x, str): + if 'base64_' in x: + x = x.replace('base64_', '') + x_dec = base64.b64decode(x).decode('utf8') + return x_dec + return x + for key, value in obj.items(): + obj[key] = _dec_base64_if_multibyte(value) + return obj + + @contextmanager + def _timer(self, name): + t0 = time.time() + if self.verbose: + print('[{}] start'.format(name)) + yield + ptime_sec = time.time() - t0 + self.ptime_sec[name] = ptime_sec + if self.verbose: + print('[{}] done in {:.2f} s'.format(name, ptime_sec)) diff --git a/histview2/setting_module/__init__.py b/histview2/setting_module/__init__.py new file mode 100644 index 0000000..de9dca7 --- /dev/null +++ b/histview2/setting_module/__init__.py @@ -0,0 +1,3 @@ +def create_module(app, **kwargs): + from .controllers import setting_module_blueprint + app.register_blueprint(setting_module_blueprint) diff --git a/histview2/setting_module/controllers.py b/histview2/setting_module/controllers.py new file mode 100644 index 0000000..17d253c --- /dev/null +++ b/histview2/setting_module/controllers.py @@ -0,0 +1,156 @@ +import os +from json import dumps + +from flask import Blueprint, render_template +from flask_babel import gettext as _, get_locale + +from histview2.common.common_utils import get_wrapr_path, get_files, get_error_trace_path, get_about_md_file, \ + get_terms_of_use_md_file +from histview2.common.constants import * +from histview2.common.services import http_content +from histview2.common.yaml_utils import YamlConfig, BasicConfigYaml +from histview2.setting_module.forms import DataSourceForm +from histview2.setting_module.models import CfgConstant, CfgDataSource +from histview2.setting_module.services.about import markdown_to_html +from histview2.setting_module.services.background_process import get_background_jobs_service +from histview2.setting_module.services.process_config import convert2serialize +from histview2.setting_module.services.process_config import get_all_process, get_all_process_no_nested + +# socketio = web_socketio[SOCKETIO] + +setting_module_blueprint = Blueprint( + 'setting_module', + __name__, + template_folder=os.path.join('..', 'templates', 'setting_module'), + static_folder=os.path.join('..', 'static', 'setting_module'), + static_url_path=os.path.join(os.sep, 'static', 'setting_module'), + url_prefix='/histview2' +) + + +@setting_module_blueprint.route('/config') +def config_screen(): + data_sources = CfgDataSource.get_all() + data_source_forms = [DataSourceForm(obj=ds) for ds in data_sources] + + all_datasource = [convert2serialize(ds) for ds in data_sources] + import_err_dir = get_error_trace_path().replace('\\', '/') + + # ローカルパラメータの設定 + basic_config_yaml = BasicConfigYaml() + basic_config = basic_config_yaml.dic_config + output_dict = { + 'title': YamlConfig.get_node(basic_config, ['info', 'title']), + 'plant': YamlConfig.get_node(basic_config, ['info', 'plant']), + 'factory': YamlConfig.get_node(basic_config, ['info', 'factory']), + 'line_group': YamlConfig.get_node(basic_config, ['info', 'line-group']), + 'port_no': YamlConfig.get_node(basic_config, ['info', 'port-no']), + 'version': YamlConfig.get_node(basic_config, ['info', 'version']), + } + + # get polling frequency + polling_frequency = CfgConstant.get_value_by_type_first(CfgConstantType.POLLING_FREQUENCY.name) + if polling_frequency is None: + # set default polling freq. + polling_frequency = DEFAULT_POLLING_FREQ + CfgConstant.create_or_update_by_type(const_type=CfgConstantType.POLLING_FREQUENCY.name, + const_value=polling_frequency) + + all_procs = get_all_process() + processes = get_all_process_no_nested() + procs = [(proc.get('id'), proc.get('name')) for proc in processes] + + output_dict.update({ + 'page_title': _('Application Configuration'), + 'proc_list': all_procs, + 'procs': procs, + 'import_err_dir': import_err_dir, + 'polling_frequency': int(polling_frequency), + 'data_sources': data_source_forms, + 'all_datasource': dumps(all_datasource) + # 'ds_tables': ds_tables + }) + + # get R ETL wrap functions + wrap_path = get_wrapr_path() + func_etl_path = os.path.join(wrap_path, 'func', 'etl') + try: + etl_scripts = get_files(directory=func_etl_path, depth_from=1, depth_to=1, file_name_only=True) or [] + except Exception: + etl_scripts = [] + output_dict.update({'etl_scripts': etl_scripts}) + + return render_template("config.html", **output_dict) + + +@setting_module_blueprint.route('/config/filter') +def filter_config(): + basic_config_yaml = BasicConfigYaml() + basic_config = basic_config_yaml.dic_config + processes = get_all_process() + output_dict = { + "title": YamlConfig.get_node(basic_config, ['info', 'title']), + "version": YamlConfig.get_node(basic_config, ['info', 'version']), + 'procs': processes, + } + return render_template("filter_config.html", **output_dict) + + +@setting_module_blueprint.route('/config/master_cfg') +def master_cfg(): + basic_config_yaml = BasicConfigYaml() + basic_config = basic_config_yaml.dic_config + processes = get_all_process() + output_dict = { + "title": YamlConfig.get_node(basic_config, ['info', 'title']), + 'procs': processes, + } + return render_template("master_config.html", **output_dict) + + +@setting_module_blueprint.route('/config/job', methods=['GET']) +def backgound_process(): + basic_config_yaml = BasicConfigYaml() + output_dict = { + 'title': YamlConfig.get_node(basic_config_yaml.dic_config, ['info', 'title']), + "page_title": _("Job List"), + } + jobs = get_background_jobs_service() + jobs = dumps(jobs, ensure_ascii=False, default=http_content.json_serial) + output_dict['jobs'] = jobs + + # return render_template("background_job.html", **output_dict, async_mode=socketio.async_mode) + return render_template("background_job.html", **output_dict) + + +@setting_module_blueprint.route('/about', methods=['GET']) +def about(): + """ + about page + """ + markdown_file_path = get_about_md_file() + css, html = markdown_to_html(markdown_file_path) + return render_template("about.html", css=css, content=html) + + +@setting_module_blueprint.route('/terms_of_use', methods=['GET']) +def term_of_use(): + """ + term of use page + """ + current_locale = get_locale() + markdown_file_path = get_terms_of_use_md_file(current_locale) + css, html = markdown_to_html(markdown_file_path) + return render_template("terms_of_use.html", css=css, content=html) + + +@setting_module_blueprint.route('/config/master') +def master_config(): + basic_config_yaml = BasicConfigYaml() + basic_config = basic_config_yaml.dic_config + processes = get_all_process() + output_dict = { + "title": YamlConfig.get_node(basic_config, ['info', 'title']), + 'procs': processes, + } + return render_template("master_cfg.html", **output_dict) diff --git a/histview2/setting_module/forms.py b/histview2/setting_module/forms.py new file mode 100644 index 0000000..18c1ad8 --- /dev/null +++ b/histview2/setting_module/forms.py @@ -0,0 +1,90 @@ +from flask_wtf import FlaskForm +from wtforms import IntegerField, StringField, BooleanField, FormField, FieldList + + +class DataSourceDbForm(FlaskForm): + id = IntegerField('dataSourceId') + + host = StringField() + port = IntegerField() + dbname = StringField() + schema = StringField() + username = StringField() + password = StringField() + hashed = BooleanField() + use_os_timezone = BooleanField() + + +class DataSourceCsvForm(FlaskForm): + id = IntegerField('dataSourceId') + directory = StringField() + skip_head = IntegerField() + skip_tail = IntegerField() + delimiter = StringField() + etl_func = StringField() + + +class DataSourceForm(FlaskForm): + id = IntegerField() + name = StringField() + type = StringField() + comment = StringField() + order = IntegerField() + # db_detail = FormField(DataSourceDbForm) + # csv_detail = FormField(DataSourceCsvForm) + + +class UniversalDBSchema(FlaskForm): + column_names = FieldList(StringField()) + + +class DataSourceCsvUpdateSchema(FlaskForm): + name = StringField('master-name') + delimiter = StringField() + universal_db = FormField(UniversalDBSchema) + + +class Test1(FlaskForm): + db_201123111328889 = FormField(DataSourceCsvUpdateSchema) + + +class ProcessColumnsForm(FlaskForm): + id = IntegerField() + process_id = IntegerField() + column_name = StringField() + english_name = StringField() + name = StringField() + data_type = StringField() + operator = StringField() + coef = StringField() + column_type = IntegerField() + is_serial_no = BooleanField() + is_get_date = BooleanField() + is_auto_increment = BooleanField() + order = IntegerField() + + +class ProcessCfgForm(FlaskForm): + id = IntegerField() + name = StringField('proc_id') + data_source_id = StringField() + table_name = StringField() + comment = StringField() + order = IntegerField() + columns = FieldList(FormField(ProcessColumnsForm)) + + +# class DataSourceDbSchema(ma.SQLAlchemyAutoSchema): +# class Meta: +# model = CfgDataSourceDB +# +# +# class DataSourceCsvSchema(ma.SQLAlchemyAutoSchema): +# class Meta: +# model = CfgDataSourceCSV +# +# +# class DataSourceSchema(ma.SQLAlchemyAutoSchema): +# class Meta: +# model = CfgDataSource +# diff --git a/histview2/setting_module/models.py b/histview2/setting_module/models.py new file mode 100644 index 0000000..aaf54b1 --- /dev/null +++ b/histview2/setting_module/models.py @@ -0,0 +1,1284 @@ +import json +from contextlib import contextmanager +from typing import Union, List, Dict + +from flask import g +from sqlalchemy import func, and_, or_, event, Index +from sqlalchemy.inspection import inspect +from sqlalchemy.orm import load_only + +from histview2 import Session +from histview2 import db +from histview2.common.common_utils import get_current_timestamp, dict_deep_merge +from histview2.common.constants import JobStatus, FlaskGKey, CsvDelimiter, DataType, CfgConstantType, \ + EFA_HEADER_FLAG, DiskUsageStatus, DEFAULT_WARNING_DISK_USAGE, DEFAULT_ERROR_DISK_USAGE +from histview2.common.constants import RelationShip +from histview2.common.cryptography_utils import decrypt_pwd +from histview2.common.memoize import set_all_cache_expired +from histview2.common.services.normalization import model_normalize +from histview2.common.trace_data_log import Location, ReturnCode, LogLevel +from histview2.setting_module.forms import DataSourceCsvForm, ProcessCfgForm + + +@contextmanager +def make_session(): + try: + session = g.setdefault(FlaskGKey.APP_DB_SESSION, Session()) + except Exception: + # for unit test + session = Session() + + try: + yield session + session.commit() + except Exception as e: + session.rollback() + raise e + + +class JobManagement(db.Model): # TODO change to new modal and edit job + __bind_key__ = 'app_metadata' + __tablename__ = 't_job_management' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + + job_type = db.Column(db.Text()) + db_code = db.Column(db.Text()) + db_name = db.Column(db.Text()) + process_id = db.Column(db.Integer()) + process_name = db.Column(db.Text()) + + start_tm = db.Column(db.Text(), default=get_current_timestamp) + end_tm = db.Column(db.Text()) + status = db.Column(db.Text()) + done_percent = db.Column(db.Float(), default=0) + duration = db.Column(db.Float(), default=0) + error_msg = db.Column(db.Text()) + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + csv_imports = db.relationship('CsvImport', lazy='dynamic') + gen_global = db.relationship('GenGlobalId', lazy='dynamic') + + @classmethod + def check_new_jobs(cls, from_job_id, target_job_types): + out = cls.query.options(load_only(cls.id)) + return out.filter(cls.id > from_job_id).filter(cls.job_type.in_(target_job_types)).first() + + @classmethod + def get_last_job_id_by_jobtype(cls, job_type): + out = cls.query.options(load_only(cls.id)) + return out.filter(cls.job_type == job_type).order_by(cls.id.desc()).first() + + @classmethod + def get_last_job_of_process(cls, proc_id, job_type): + out = cls.query.options(load_only(cls.id)) + return out.filter(cls.process_id == proc_id).filter(cls.job_type == job_type).order_by(cls.id.desc()).first() + + @classmethod + def get_error_jobs(cls, job_id): + infos = cls.query.filter(cls.id == job_id).filter(cls.status != str(JobStatus.DONE)) + return infos.all() + + +class CsvImport(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 't_csv_import' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + job_id = db.Column(db.Integer(), db.ForeignKey('t_job_management.id'), index=True) + + process_id = db.Column(db.Integer()) + file_name = db.Column(db.Text()) + + start_tm = db.Column(db.Text(), default=get_current_timestamp) + end_tm = db.Column(db.Text()) + imported_row = db.Column(db.Integer(), default=0) + status = db.Column(db.Text()) + error_msg = db.Column(db.Text()) + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + @classmethod + def get_last_job_id(cls, process_id): + max_job = cls.query.filter(cls.process_id == process_id).with_entities(func.max(cls.job_id).label('job_id')) + return max_job + + @classmethod + def get_last_fatal_import(cls, process_id): + max_job = cls.get_last_job_id(process_id).subquery() + csv_imports = cls.query.filter(cls.job_id == max_job.c.job_id, + cls.status.in_([JobStatus.FATAL.name, JobStatus.PROCESSING.name])) + csv_imports = csv_imports.order_by(cls.id).all() + + return csv_imports + + @classmethod + def get_by_job_id(cls, job_id): + csv_imports = cls.query.filter(cls.job_id == job_id) + return csv_imports.all() + + @classmethod + def get_error_jobs(cls, job_id): + csv_imports = cls.query.filter(cls.job_id == job_id).filter(cls.status != str(JobStatus.DONE)) + return csv_imports.all() + + @classmethod + def get_latest_done_files(cls, process_id): + csv_files = cls.query.filter(cls.process_id == process_id, + cls.status.in_([JobStatus.DONE.name, JobStatus.FAILED.name])) + + csv_files = csv_files.with_entities(cls.file_name, + func.max(cls.start_tm).label(cls.start_tm.key), + func.max(cls.imported_row).label(cls.imported_row.key)) + + csv_files = csv_files.group_by(cls.file_name).all() + + return csv_files + + +Index(f'ix_t_csv_import_process_id_status_file_name', CsvImport.process_id, CsvImport.status, CsvImport.file_name) + + +# TODO: remove +class GenGlobalId(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 't_gen_global_id' + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + job_id = db.Column(db.Integer(), db.ForeignKey('t_job_management.id'), index=True) + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + +class ProcLink(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 't_proc_link' + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + job_id = db.Column(db.Integer(), db.ForeignKey('t_job_management.id'), index=True) + + process_id = db.Column(db.Integer()) + target_process_id = db.Column(db.Integer()) + matched_count = db.Column(db.Integer(), default=0) + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + @classmethod + def delete_all(cls): + """delete all records + """ + # cls.query.delete() + with make_session() as meta_session: + meta_session.query(cls).delete() + + @classmethod + def calc_proc_link(cls): + with make_session() as meta_session: + output = meta_session.query(cls.process_id, cls.target_process_id, + func.sum(cls.matched_count).label(cls.matched_count.key)) + output = output.group_by(cls.process_id, cls.target_process_id).all() + + return output + + +class CfgConstant(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_constant' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + type = db.Column(db.Text()) + name = db.Column(db.Text()) + value = db.Column(db.Text()) + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp, onupdate=get_current_timestamp) + + @classmethod + def get_value_by_type_first(cls, type, parse_val=None): + output = cls.query.options(load_only(cls.value)).filter(cls.type == type).first() + if not output: + return None + + return output.value if not parse_val else parse_val(output.value) + + @classmethod + def get_value_by_type_name(cls, type, name, parse_val=None): + output = cls.query.options(load_only(cls.value)).filter(cls.type == type, cls.name == name).first() + if not output: + return None + + return output.value if not parse_val else parse_val(output.value) + + @classmethod + def create_or_update_by_type(cls, const_type=None, const_value=0, const_name=None): + with make_session() as meta_session: + constant = None + if const_type: + constant = meta_session.query(cls).filter(cls.type == const_type) + + if const_name: + constant = constant.filter(cls.name == const_name) + + constant = constant.first() + + if not constant: + constant = cls(type=const_type, value=const_value, name=const_name) + meta_session.add(constant) + else: + constant.value = const_value + + @classmethod + def create_or_merge_by_type(cls, const_type=None, const_name=None, const_value=0): + with make_session() as meta_session: + constant = meta_session.query(cls).filter(cls.type == const_type, cls.name == const_name).first() + if not constant: + constant = cls(type=const_type, name=const_name, value=json.dumps(const_value)) + meta_session.add(constant) + else: + # merge new order to old orders + dic_value = json.loads(constant.value) + dic_latest_orders = dict_deep_merge(const_value, dic_value) + constant.value = json.dumps(dic_latest_orders) + + @classmethod + def get_efa_header_flag(cls, data_source_id): + efa_header_flag = cls.query.filter( + cls.type == CfgConstantType.EFA_HEADER_EXISTS.name, + cls.name == data_source_id + ).first() + + if efa_header_flag and efa_header_flag.value and efa_header_flag.value == EFA_HEADER_FLAG: + return True + return False + + @classmethod + def get_warning_disk_usage(cls) -> int: + return cls.get_value_by_type_name(CfgConstantType.DISK_USAGE_CONFIG.name, DiskUsageStatus.Warning.name, + parse_val=int) + + @classmethod + def get_error_disk_usage(cls) -> int: + return cls.get_value_by_type_name(CfgConstantType.DISK_USAGE_CONFIG.name, DiskUsageStatus.Full.name, + parse_val=int) + + @classmethod + def initialize_disk_usage_limit(cls): + """ + Sets default disk usage limit constants. + - Warning: 80% (No terminate jobs) + - Error: 90% (Terminate jobs) + + :return: + """ + constants_type = CfgConstantType.DISK_USAGE_CONFIG.name + warning_percent = cls.get_warning_disk_usage() + if not warning_percent: # insert of not existing + warning_percent = DEFAULT_WARNING_DISK_USAGE + cls.create_or_update_by_type(constants_type, warning_percent, + const_name=DiskUsageStatus.Warning.name) + + error_percent = cls.get_error_disk_usage() + if not error_percent: # insert of not existing + error_percent = DEFAULT_ERROR_DISK_USAGE + + cls.create_or_update_by_type(constants_type, error_percent, + const_name=DiskUsageStatus.Full.name) + + +class CfgDataSource(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_data_source' + __table_args__ = {'sqlite_autoincrement': True} + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + name = db.Column(db.Text()) + type = db.Column(db.Text()) + comment = db.Column(db.Text()) + order = db.Column(db.Integer()) + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp, onupdate=get_current_timestamp) + db_detail = db.relationship("CfgDataSourceDB", lazy='subquery', backref="cfg_data_source", uselist=False, + cascade='all') + csv_detail = db.relationship("CfgDataSourceCSV", lazy='subquery', backref="cfg_data_source", uselist=False, + cascade='all') + processes = db.relationship('CfgProcess', lazy="dynamic", cascade='all') + + @classmethod + def delete(cls, meta_session, id): + meta_session.query.filter(cls.id == id).delete() + + @classmethod + def get_all(cls): + all_ds = cls.query.order_by(cls.order).all() + for ds in all_ds: + db_detail: CfgDataSourceDB = ds.db_detail + if db_detail and db_detail.hashed: + db_detail.password = decrypt_pwd(db_detail.password) + return all_ds + + @classmethod + def get_all_db_source(cls): + all_ds = cls.query.filter(cls.type != "CSV").order_by(cls.order).all() + for ds in all_ds: + db_detail: CfgDataSourceDB = ds.db_detail + if db_detail and db_detail.hashed: + db_detail.password = decrypt_pwd(db_detail.password) + return all_ds + + @classmethod + def get_ds(cls, ds_id): + ds = cls.query.get(ds_id) + db_detail: CfgDataSourceDB = ds.db_detail + if db_detail and db_detail.hashed: + db_detail.password = decrypt_pwd(db_detail.password) + return ds + + @classmethod + def update_order(cls, meta_session, data_source_id, order): + meta_session.query(cls).filter(cls.id == data_source_id).update({cls.order: order}) + + # @classmethod + # def get_detail(cls, id): + # ds = cls.query.get(id) + # if ds.type == DBType.CSV.value: + # return ds.csv_detail + # + # return ds.db_detail + + # @classmethod + # def get_or_create(cls, meta_session, id): + # rec = meta_session.query.get(id) + # if not rec: + # rec = cls() + # + # return rec + + +class CfgDataSourceDB(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_data_source_db' + __table_args__ = {'sqlite_autoincrement': True} + id = db.Column(db.Integer(), db.ForeignKey('cfg_data_source.id', ondelete="CASCADE"), primary_key=True) + host = db.Column(db.Text()) + port = db.Column(db.Integer()) + dbname = db.Column(db.Text()) + schema = db.Column(db.Text()) + username = db.Column(db.Text()) + password = db.Column(db.Text()) + hashed = db.Column(db.Boolean(), default=False) + use_os_timezone = db.Column(db.Boolean(), default=False) + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp, onupdate=get_current_timestamp) + + # @classmethod + # def save(cls, form: DataSourceDbForm): + # if form.id: + # row = cls() + # else: + # row = cls.query.filter(cls.id == form.id) + # + # # create dataSource ins + # # TODO pwd encrypt + # form.populate_obj(row) + # + # return row + + @classmethod + def delete(cls, id): + cls.query.filter(cls.id == id).delete() + + +class CfgCsvColumn(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_csv_column' + __table_args__ = {'sqlite_autoincrement': True} + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + data_source_id = db.Column(db.Integer(), db.ForeignKey('cfg_data_source_csv.id', ondelete="CASCADE")) + column_name = db.Column(db.Text()) + data_type = db.Column(db.Text()) + order = db.Column(db.Integer()) + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + +class CfgProcessColumn(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_process_column' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + process_id = db.Column(db.Integer(), db.ForeignKey('cfg_process.id', ondelete="CASCADE")) + column_name = db.Column(db.Text()) + english_name = db.Column(db.Text()) + name = db.Column(db.Text()) + data_type = db.Column(db.Text()) + operator = db.Column(db.Text()) + coef = db.Column(db.Text()) + column_type = db.Column(db.Integer()) + is_serial_no = db.Column(db.Boolean(), default=False) + is_get_date = db.Column(db.Boolean(), default=False) + is_auto_increment = db.Column(db.Boolean(), default=False) + order = db.Column(db.Integer()) + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + # TODO trace key, cfg_filter: may not needed + # visualizations = db.relationship('CfgVisualization', lazy='dynamic', backref="cfg_process_column", cascade="all") + + def as_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + @classmethod + def get_by_col_name(cls, proc_id, col_name): + return cls.query.filter(cls.process_id == proc_id, cls.column_name == col_name).first() + + @classmethod + def get_by_data_type(cls, proc_id, data_type: DataType): + return cls.query.filter(cls.process_id == proc_id, cls.data_type == data_type.name).all() + + @classmethod + def get_by_ids(cls, ids): + return cls.query.filter(cls.id.in_(ids)).all() + + @classmethod + def get_serials(cls, proc_id): + return cls.query.filter(cls.process_id == proc_id, cls.is_serial_no == 1).all() + + @classmethod + def get_all_columns(cls, proc_id): + return cls.query.filter(cls.process_id == proc_id).all() + + +class CfgProcess(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_process' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + name = db.Column(db.Text()) + data_source_id = db.Column(db.Integer(), db.ForeignKey('cfg_data_source.id', ondelete="CASCADE")) + table_name = db.Column(db.Text()) + comment = db.Column(db.Text()) + + order = db.Column(db.Integer()) + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + # TODO check fetch all + columns: List[CfgProcessColumn] = db.relationship('CfgProcessColumn', lazy='joined', backref="cfg_process", + cascade="all") + traces = db.relationship('CfgTrace', lazy='dynamic', foreign_keys='CfgTrace.self_process_id', backref="cfg_process", + cascade="all") + filters = db.relationship('CfgFilter', lazy='dynamic', backref="cfg_process", cascade="all") + visualizations = db.relationship('CfgVisualization', lazy='dynamic', backref="cfg_process", cascade="all") + data_source = db.relationship('CfgDataSource', lazy='select') + + def get_date_col(self, column_name_only=True): + """ + get date column + :param column_name_only: + :return: + """ + cols = [col for col in self.columns if col.is_get_date] + if cols: + if column_name_only: + return cols[0].column_name + + return cols[0] + + return None + + def get_auto_increment_col(self, column_name_only=True): + """ + get auto increment column + :param column_name_only: + :return: + """ + cols = [col for col in self.columns if col.is_auto_increment] + if cols: + if column_name_only: + return cols[0].column_name + + return cols[0] + + return None + + def get_auto_increment_col_else_get_date(self, column_name_only=True): + """ + get auto increment column + :param column_name_only: + :return: + """ + return self.get_auto_increment_col(column_name_only) or self.get_date_col(column_name_only) + + def get_serials(self, column_name_only=True): + if column_name_only: + cols = [col.column_name for col in self.columns if col.is_serial_no] + else: + cols = [col for col in self.columns if col.is_serial_no] + + return cols + + def get_order_cols(self, column_name_only=True): + cols = [col for col in self.columns if col.order or col.is_serial_no] + if column_name_only: + cols = [col.column_name for col in cols] + + return cols + + def get_cols(self, col_ids=()): + cols = [col for col in self.columns if col.id in col_ids] + return cols + + def get_cols_by_data_type(self, data_type: DataType, column_name_only=True): + """ + get date column + :param data_type: + :param column_name_only: + :return: + """ + if column_name_only: + cols = [col.column_name for col in self.columns if col.data_type == data_type.name] + else: + cols = [col for col in self.columns if col.data_type == data_type.name] + + return cols + + @classmethod + def get_all(cls): + return cls.query.order_by(cls.order).all() + + @classmethod + def get_all_ids(cls): + return cls.query.options(load_only(cls.id)).all() + + @classmethod + def get_all_order_by_id(cls): + return cls.query.order_by(cls.id).all() + + @classmethod + def get_procs(cls, ids): + return cls.query.filter(cls.id.in_(ids)) + + @classmethod + def as_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + @classmethod + def save(cls, meta_session, form: ProcessCfgForm): + if not form.id.data: + row = cls() + meta_session.add(row) + else: + row = meta_session.query(cls).get(form.id.data) + # row.columns = [ProcessColumnsForm] + # for column in columns: + # columObj = CfgProcessColumn(**column) + # meta_session.add(columObj) + # form.populate_obj(row) + meta_session.commit() + return row + + @classmethod + def delete(cls, proc_id): + # TODO refactor + with make_session() as meta_session: + proc = meta_session.query(cls).get(proc_id) + if not proc: + return False + + meta_session.delete(proc) + + # delete traces manually + meta_session.query(CfgTrace).filter( + or_(CfgTrace.self_process_id == proc_id, CfgTrace.target_process_id == proc_id) + ).delete() + + # delete linking prediction manually + meta_session.query(ProcLink).filter( + or_(ProcLink.process_id == proc_id, ProcLink.target_process_id == proc_id) + ).delete() + + meta_session.commit() + + return True + + @classmethod + def update_order(cls, meta_session, process_id, order): + meta_session.query(cls).filter(cls.id == process_id).update({cls.order: order}) + + +class CfgTraceKey(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_trace_key' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + trace_id = db.Column(db.Integer(), db.ForeignKey('cfg_trace.id', ondelete="CASCADE")) + # TODO confirm PO delete + self_column_id = db.Column(db.Integer(), db.ForeignKey('cfg_process_column.id', ondelete="CASCADE")) + self_column_substr_from = db.Column(db.Integer()) + self_column_substr_to = db.Column(db.Integer()) + + target_column_id = db.Column(db.Integer(), db.ForeignKey('cfg_process_column.id', ondelete="CASCADE")) + target_column_substr_from = db.Column(db.Integer()) + target_column_substr_to = db.Column(db.Integer()) + + order = db.Column(db.Integer()) + + self_column = db.relationship('CfgProcessColumn', foreign_keys=[self_column_id], lazy='joined') + target_column = db.relationship('CfgProcessColumn', foreign_keys=[target_column_id], lazy='joined') + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + def as_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + +class CfgTrace(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_trace' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + self_process_id = db.Column(db.Integer(), db.ForeignKey('cfg_process.id', ondelete="CASCADE")) + target_process_id = db.Column(db.Integer(), db.ForeignKey('cfg_process.id', ondelete="CASCADE")) + is_trace_backward = db.Column(db.Boolean(), default=False) + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + trace_keys: List[CfgTraceKey] = db.relationship('CfgTraceKey', lazy='joined', backref="cfg_trace", cascade="all") + + self_process = db.relationship('CfgProcess', foreign_keys=[self_process_id], lazy='joined') + target_process = db.relationship('CfgProcess', foreign_keys=[target_process_id], lazy='joined') + + @classmethod + def get_all(cls): + return cls.query.all() + + def as_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + @classmethod + def get_cols_between_two(cls, proc_id1, proc_id2): + trace = cls.query.filter(or_( + and_(cls.self_process_id == proc_id1, cls.target_process_id == proc_id2), + and_(cls.self_process_id == proc_id2, cls.target_process_id == proc_id1) + )).first() + + cols = set() + if trace: + [cols.update([key.self_column_id, key.target_column_id]) for key in trace.trace_keys] + return cols + + +class CfgFilterDetail(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_filter_detail' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + filter_id = db.Column(db.Integer(), db.ForeignKey('cfg_filter.id', ondelete="CASCADE")) + parent_detail_id = db.Column(db.Integer(), db.ForeignKey(id, ondelete="CASCADE")) + name = db.Column(db.Text()) + filter_condition = db.Column(db.Text()) + filter_function = db.Column(db.Text()) + filter_from_pos = db.Column(db.Integer()) + + order = db.Column(db.Integer()) + + parent = db.relationship('CfgFilterDetail', lazy='joined', backref="cfg_children", remote_side=[id], uselist=False) + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + def update_by_dict(self, **kwargs): + for key, value in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) + + @classmethod + def get_filters(cls, ids): + return cls.query.filter(cls.id.in_(ids)) + + def __eq__(self, other): + return (isinstance(other, self.__class__) and getattr(other, self.id.key, None) and self.id and + getattr(other, self.id.key, None) == self.id) + + def __hash__(self): + return hash(str(self.id)) + + +def get_or_create(db_session, model, **kwargs): + instance = db_session.query(model).filter_by(**kwargs).first() + if instance: + return instance + else: + instance = model(**kwargs) + db_session.add(instance) + db_session.commit() + return instance + + +class CfgFilter(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_filter' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + process_id = db.Column(db.Integer(), db.ForeignKey('cfg_process.id', ondelete="CASCADE")) + name = db.Column(db.Text()) + column_id = db.Column(db.Integer(), db.ForeignKey('cfg_process_column.id', ondelete="CASCADE")) # TODO confirm PO + filter_type = db.Column(db.Text()) + parent_id = db.Column(db.Integer(), db.ForeignKey(id, ondelete="CASCADE"), + nullable=True) # TODO check if needed to self ref + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + parent = db.relationship('CfgFilter', lazy='joined', backref="cfg_children", remote_side=[id], uselist=False) + column = db.relationship('CfgProcessColumn', lazy='joined', backref="cfg_filters", uselist=False) + filter_details = db.relationship('CfgFilterDetail', lazy='joined', backref="cfg_filter", cascade="all") + + @classmethod + def delete_by_id(cls, meta_session, filter_id): + cfg_filter = meta_session.query(cls).get(filter_id) + if cfg_filter: + meta_session.delete(cfg_filter) + + @classmethod + def get_filters(cls, ids): + return cls.query.filter(cls.id.in_(ids)) + + @classmethod + def get_filter_by_col_id(cls, column_id): + return cls.query.filter(cls.column_id == column_id).first() + + @classmethod + def get_by_proc_n_col_ids(cls, proc_ids, column_ids): + return cls.query.filter(cls.process_id.in_(proc_ids), cls.column_id.in_(column_ids)).all() + + +class CfgVisualization(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_visualization' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + process_id = db.Column(db.Integer(), db.ForeignKey('cfg_process.id', ondelete="CASCADE")) + # TODO confirm PO + control_column_id = db.Column(db.Integer(), db.ForeignKey('cfg_process_column.id', ondelete="CASCADE")) + filter_column_id = db.Column(db.Integer(), db.ForeignKey('cfg_process_column.id', ondelete="CASCADE"), + nullable=True) + # filter_column_id = db.Column(db.Integer(), nullable=True) + filter_value = db.Column(db.Text()) + is_from_data = db.Column(db.Boolean(), default=False) + filter_detail_id = db.Column(db.Integer(), db.ForeignKey('cfg_filter_detail.id', ondelete="CASCADE"), nullable=True) + + ucl = db.Column(db.Float()) + lcl = db.Column(db.Float()) + upcl = db.Column(db.Float()) + lpcl = db.Column(db.Float()) + ymax = db.Column(db.Float()) + ymin = db.Column(db.Float()) + + # TODO check default value, null is OK + act_from = db.Column(db.Text()) + act_to = db.Column(db.Text()) + + order = db.Column(db.Integer()) + + control_column = db.relationship('CfgProcessColumn', foreign_keys=[control_column_id], lazy='joined') + filter_column = db.relationship('CfgProcessColumn', foreign_keys=[filter_column_id], lazy='joined') + filter_detail = db.relationship('CfgFilterDetail', lazy='joined') + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + deleted_at = db.Column(db.Text()) + + @classmethod + def get_filter_ids(cls): + return db.session.query(cls.filter_detail_id).filter(cls.filter_detail_id > 0) + + @classmethod + def get_by_control_n_filter_detail_ids(cls, col_id, filter_detail_ids, start_tm, end_tm): + return cls.query.filter( + and_(cls.control_column_id == col_id, cls.filter_detail_id.in_(filter_detail_ids), )).filter( + or_(cls.act_from.is_(None), cls.act_from < end_tm, cls.act_from == '')).filter( + or_(cls.act_to.is_(None), cls.act_to > start_tm, cls.act_to == '')).order_by(cls.act_from.desc()).all() + + @classmethod + def get_sensor_default_chart_info(cls, col_id, start_tm, end_tm): + # TODO: not deleted, ... + return cls.query.filter(cls.control_column_id == col_id) \ + .filter(and_(cls.filter_detail_id.is_(None))) \ + .filter(or_(cls.act_from.is_(None), cls.act_from < end_tm, cls.act_from == '')) \ + .filter(or_(cls.act_to.is_(None), cls.act_to > start_tm, cls.act_to == '')) \ + .order_by(cls.act_from.desc()).all() + + @classmethod + def get_by_control_n_filter_col_id(cls, col_id, filter_col_id, start_tm, end_tm): + # TODO: not deleted, ... + return cls.query.filter( + and_(cls.control_column_id == col_id, + cls.filter_column_id == filter_col_id, + cls.filter_value.is_(None), + cls.filter_detail_id.is_(None), + )) \ + .filter(or_(cls.act_from.is_(None), cls.act_from < end_tm, cls.act_from == '')) \ + .filter(or_(cls.act_to.is_(None), cls.act_to > start_tm, cls.act_to == '')) \ + .order_by(cls.act_from.desc()).all() + + @classmethod + def get_all_by_control_n_filter_col_id(cls, col_id, filter_col_id, start_tm, end_tm): + # TODO: not deleted, ... + return cls.query.filter( + and_(cls.control_column_id == col_id, + cls.filter_column_id == filter_col_id, + )) \ + .filter(or_(cls.act_from.is_(None), cls.act_from < end_tm, cls.act_from == '')) \ + .filter(or_(cls.act_to.is_(None), cls.act_to > start_tm, cls.act_to == '')) \ + .order_by(cls.act_from.desc()).all() + + @classmethod + def get_by_filter_detail_id(cls, col_id, filter_detail_id, start_tm, end_tm): + # TODO: not deleted, ... + return cls.query.filter(and_(cls.control_column_id == col_id, cls.filter_detail_id == filter_detail_id)) \ + .filter(or_(cls.act_from.is_(None), cls.act_from < end_tm, cls.act_from == '')) \ + .filter(or_(cls.act_to.is_(None), cls.act_to > start_tm, cls.act_to == '')) \ + .order_by(cls.act_from.desc()).all() + + +class DataTraceLog(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 't_data_trace_log' + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + date_time = db.Column(db.Text(), default=get_current_timestamp) + dataset_id = db.Column(db.Text()) + event_type = db.Column(db.Text()) + event_action = db.Column(db.Text()) + target = db.Column(db.Text()) + exe_time = db.Column(db.Integer()) + data_size = db.Column(db.Integer()) + rows = db.Column(db.Integer()) + cols = db.Column(db.Integer()) + dumpfile = db.Column(db.Text()) + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + @classmethod + def get_max_id(cls): + out = cls.query.options(load_only(cls.id)).order_by(cls.id.desc()).first() + if out: + return out.id + else: + return 0 + + @classmethod + def get_dataset_id(cls, dataset_id, action): + return cls.query.filter(cls.dataset_id == dataset_id, cls.event_action == action).all() + + +class AbnormalTraceLog(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 't_abnormal_trace_log' + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + date_time = db.Column(db.Text(), default=get_current_timestamp) + dataset_id = db.Column(db.Integer(), autoincrement=True) + log_level = db.Column(db.Text(), default=LogLevel.ERROR.value) + event_type = db.Column(db.Text()) + event_action = db.Column(db.Text()) + location = db.Column(db.Text(), default=Location.PYTHON.value) + return_code = db.Column(db.Text(), default=ReturnCode.UNKNOWN_ERR.value) + message = db.Column(db.Text()) + dumpfile = db.Column(db.Text()) + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + +class FactoryImport(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 't_factory_import' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + job_id = db.Column(db.Integer(), db.ForeignKey('t_job_management.id'), index=True) + + process_id = db.Column(db.Integer()) + import_type = db.Column(db.Text()) + import_from = db.Column(db.Text()) + import_to = db.Column(db.Text()) + + start_tm = db.Column(db.Text(), default=get_current_timestamp) + end_tm = db.Column(db.Text()) + imported_row = db.Column(db.Integer(), default=0) + + status = db.Column(db.Text()) + error_msg = db.Column(db.Text()) + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp) + + @classmethod + def get_last_import(cls, process_id, import_type, is_first_id=False): + data = cls.query.filter(cls.process_id == process_id) + data = data.filter(cls.import_type == import_type) + data = data.filter(cls.status.in_([str(JobStatus.FAILED), str(JobStatus.DONE)])) + data = data.order_by(cls.job_id.desc()) + if is_first_id: + data = data.order_by(cls.id) + else: + data = data.order_by(cls.id.desc()) + + data = data.first() + + return data + + @classmethod + def get_first_import(cls, process_id, import_type): + data = cls.query.filter(cls.process_id == process_id) + data = data.filter(cls.import_type == import_type) + data = data.filter(cls.status.in_([str(JobStatus.FAILED), str(JobStatus.DONE)])) + data = data.order_by(cls.id).first() + + return data + + @classmethod + def get_error_jobs(cls, job_id): + infos = cls.query.filter(cls.job_id == job_id).filter(cls.status != str(JobStatus.DONE)) + return infos.all() + + +class AppLog(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 't_app_log' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + ip = db.Column(db.Text()) + action = db.Column(db.Text()) + description = db.Column(db.Text()) + created_at = db.Column(db.Text(), default=get_current_timestamp) + + +def insert_or_update_config(meta_session, data: Union[Dict, db.Model], + key_names: Union[List, str] = None, model: db.Model = None, + parent_obj: db.Model = None, parent_relation_key=None, + parent_relation_type=None, exclude_columns=None): + """ + + :param exclude_columns: + :param meta_session: + :param data: + :param key_names: + :param model: + :param parent_obj: + :param parent_relation_key: + :param parent_relation_type: + :return: + """ + excludes = ['created_at', 'updated_at'] + if exclude_columns: + excludes += exclude_columns + + rec = None + + # get model + if not model: + model = data.__class__ + + # default primary key is id + # get primary keys + primary_keys = [key.name for key in inspect(model).primary_key] + + if not key_names: + key_names = primary_keys + + # convert to list + if isinstance(key_names, str): + key_names = [key_names] + + # query condition by keys + if isinstance(data, db.Model): + dict_key = {key: getattr(data, key) for key in key_names} + else: + dict_key = {key: data[key] for key in key_names} + + # query by key_names + if dict_key: + rec = meta_session.query(model).filter_by(**dict_key).first() + + # create new record + if not rec: + rec = model() + if not parent_obj: + meta_session.add(rec) + elif parent_relation_type is RelationShip.MANY: + objs = getattr(parent_obj, parent_relation_key) + if objs is None: + setattr(parent_obj, parent_relation_key, [rec]) + else: + objs.append(rec) + else: + setattr(parent_obj, parent_relation_key, rec) + + if isinstance(data, db.Model): + dict_data = {key: getattr(data, key) for key in data.__table__.columns.keys()} + else: + dict_data = data + + for key, val in dict_data.items(): + # primary keys + if key in primary_keys and not val: + continue + + # ignore non-data fields + if key in excludes: + continue + + # check if valid columns + if key not in model.__table__.columns: + continue + + # avoid update None to primary key_names + if key in key_names and not val: + continue + + setattr(rec, key, val) + + return rec + + +# def update_record(rec, data, model, primary_keys, excludes=('created_at', 'updated_at'), key_names=[]): +# if isinstance(data, db.Model): +# dict_data = {key: getattr(data, key) for key in data.__table__.columns.keys()} +# else: +# dict_data = data +# +# for key, val in dict_data.items(): +# # primary keys +# if key in primary_keys and not val: +# continue +# +# # ignore non-data fields +# if key in excludes: +# continue +# +# # check if valid columns +# if key not in model.__table__.columns: +# continue +# +# # avoid update None to primary key_names +# if key in key_names and not val: +# continue +# +# setattr(rec, key, val) +# +# return rec + + +def crud_config(meta_session, data: List[Union[Dict, db.Model]], parent_key_names: Union[List, str] = None, + key_names: Union[List, str] = None, model: db.Model = None, + parent_obj: db.Model = None, parent_relation_key=None, parent_relation_type=RelationShip.MANY): + """ + + :param meta_session: + :param data: + :param parent_key_names: + :param key_names: + :param model: + :param parent_obj: + :param parent_relation_key: + :param parent_relation_type: + :return: + """ + # get model + if not model: + model = data[0].__class__ + + # convert to list + if isinstance(parent_key_names, str): + parent_key_names = [parent_key_names] + + # get primary keys + if not key_names: + key_names = [key.name for key in inspect(model).primary_key] + + if isinstance(key_names, str): + key_names = [key_names] + + key_names = parent_key_names + key_names + + # query condition by keys + if parent_key_names: + # query by key_names + if not data: + current_recs = [] + if parent_relation_key: # assume that relation key was set in model + current_recs = getattr(parent_obj, parent_relation_key) + else: + if isinstance(data[0], db.Model): + dict_key = {key: getattr(data[0], key) for key in parent_key_names} + else: + dict_key = {key: data[0][key] for key in parent_key_names} + + current_recs = meta_session.query(model).filter_by(**dict_key).all() + else: + # query all + current_recs = meta_session.query(model).all() + + # insert or update data + set_active_keys = set() + + # # container + # if parent_obj and parent_relation_key: + # setattr(parent_obj, parent_relation_key, [] if parent_relation_type else None) + + for row in data: + if parent_obj and parent_relation_key: + rec = insert_or_update_config(meta_session, row, key_names, + model=model, + parent_obj=parent_obj, + parent_relation_key=parent_relation_key, + parent_relation_type=parent_relation_type) + else: + rec = insert_or_update_config(meta_session, row, key_names, model=model) + + key = tuple(getattr(rec, key) for key in key_names) + set_active_keys.add(key) + + # delete data + for current_rec in current_recs: + key = tuple(getattr(current_rec, key) for key in key_names) + if key in set_active_keys: + continue + + meta_session.delete(current_rec) + + return True + + +class CfgDataSourceCSV(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_data_source_csv' + __table_args__ = {'sqlite_autoincrement': True} + id = db.Column(db.Integer(), db.ForeignKey('cfg_data_source.id', ondelete="CASCADE"), primary_key=True) + directory = db.Column(db.Text()) + skip_head = db.Column(db.Integer(), default=0) + skip_tail = db.Column(db.Integer(), default=0) + delimiter = db.Column(db.Text(), default=CsvDelimiter.CSV.name) + etl_func = db.Column(db.Text()) + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp, onupdate=get_current_timestamp) + # TODO check fetch all + csv_columns: List[CfgCsvColumn] = db.relationship('CfgCsvColumn', backref="cfg_data_source_csv", lazy='subquery', + cascade="all") + + def get_column_names_with_sorted(self, key=CfgCsvColumn.id.key): + """ + get column names that sorted by key + :param key: + :return: + """ + self.csv_columns.sort(key=lambda csv_column: getattr(csv_column, key)) + return [col.column_name for col in self.csv_columns] + + @classmethod + def save(cls, form: DataSourceCsvForm): + if form.id: + row = cls() + else: + row = cls.query.filter(cls.id == form.id) + + # create dataSource ins + form.populate_obj(row) + + return row + + @classmethod + def delete(cls, id): + cls.query.filter(cls.id == id).delete() + + +class CfgUserSetting(db.Model): + __bind_key__ = 'app_metadata' + __tablename__ = 'cfg_user_setting' + __table_args__ = {'sqlite_autoincrement': True} + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + key = db.Column(db.Text()) # TODO use page_name/page_url + title for now + title = db.Column(db.Text()) + page = db.Column(db.Text()) + created_by = db.Column(db.Text()) + priority = db.Column(db.Integer()) + use_current_time = db.Column(db.Boolean()) + description = db.Column(db.Text()) + share_info = db.Column(db.Boolean()) + settings = db.Column(db.Text()) + + created_at = db.Column(db.Text(), default=get_current_timestamp) + updated_at = db.Column(db.Text(), default=get_current_timestamp, onupdate=get_current_timestamp) + + @classmethod + def get_all(cls): + data = cls.query.options( + load_only(cls.id, cls.key, cls.title, cls.page, cls.created_by, cls.priority, cls.use_current_time, + cls.description, cls.share_info, cls.created_at, cls.updated_at)) + data = data.order_by(cls.priority.desc(), cls.updated_at.desc()).all() + return data + + @classmethod + def delete_by_id(cls, meta_session, setting_id): + user_setting = meta_session.query(cls).get(setting_id) + if user_setting: + meta_session.delete(user_setting) + + @classmethod + def get_by_id(cls, setting_id): + return cls.query.get(setting_id) + + @classmethod + def get_top(cls, page): + return cls.query.filter(cls.page == page).order_by(cls.priority.desc(), cls.updated_at.desc()).first() + + @classmethod + def get_by_title(cls, title): + return cls.query.filter(cls.title == title).order_by(cls.priority.desc(), cls.created_at.desc()).all() + + +def get_models(): + all_sub_classes = db.Model.__subclasses__() + return tuple([_class for _class in all_sub_classes if hasattr(_class, '__tablename__')]) + + +def make_f(model): + @event.listens_for(model, 'before_insert') + def before_insert(mapper, connection, target): + model_normalize(target) + + @event.listens_for(model, 'before_update') + def before_update(mapper, connection, target): + model_normalize(target) + if isinstance(target, (CfgProcess, CfgProcessColumn, CfgFilter, CfgFilterDetail, CfgTrace, CfgTraceKey, + CfgVisualization)): + set_all_cache_expired() + + +def add_listen_event(): + for model in get_models(): + make_f(model) + + +# GUI normalization +add_listen_event() diff --git a/histview2/setting_module/schemas.py b/histview2/setting_module/schemas.py new file mode 100644 index 0000000..951bcb3 --- /dev/null +++ b/histview2/setting_module/schemas.py @@ -0,0 +1,170 @@ +from marshmallow import post_load, fields +from marshmallow_sqlalchemy.fields import Nested + +from histview2 import ma +from histview2.setting_module.models import CfgDataSourceDB, CfgDataSourceCSV, CfgDataSource, CfgProcess, \ + CfgCsvColumn, CfgTrace, CfgTraceKey, CfgProcessColumn, CfgFilter, CfgFilterDetail, CfgVisualization, CfgUserSetting + + +class CsvColumnSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgCsvColumn + include_fk = True + unknown = True + partial = True + + id = fields.Integer(required=False) + data_source_id = fields.Integer(required=False) + + @post_load + def make_obj(self, data, **kwargs): + return CfgCsvColumn(**data) + + +class DataSourceDbSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgDataSourceDB + include_fk = True + unknown = True + + id = fields.Integer(required=False) + + @post_load + def make_obj(self, data, **kwargs): + return CfgDataSourceDB(**data) + + +class DataSourceCsvSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgDataSourceCSV + include_fk = True + unknown = True + required = False + + id = fields.Integer(required=False) + csv_columns = Nested(CsvColumnSchema, many=True) + + @post_load + def make_obj(self, data, **kwargs): + return CfgDataSourceCSV(**data) + + +class DataSourceSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgDataSource + unknown = True + + id = fields.Integer(required=False, allow_none=True) + csv_detail = Nested(DataSourceCsvSchema) + db_detail = Nested(DataSourceDbSchema) + + @post_load + def make_obj(self, data, **kwargs): + return CfgDataSource(**data) + + +class TraceKeySchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgTraceKey + include_fk = True + + +class TraceSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgTrace + include_fk = True + + trace_keys = Nested(TraceKeySchema, many=True) + + +class FilterDetailSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgFilterDetail + include_fk = True + + +class FilterSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgFilter + include_fk = True + + filter_details = Nested(FilterDetailSchema, many=True) + + +class VisualizationSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgVisualization + include_fk = True + + id = fields.Integer(required=False, allow_none=True) + filter_detail_id = fields.Integer(required=False, allow_none=True) + + @post_load + def make_obj(self, data, **kwargs): + return CfgVisualization(**data) + + +class ProcessColumnSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgProcessColumn + + @post_load + def make_obj(self, data, **kwargs): + return CfgProcessColumn(**data) + + +class ProcessSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgProcess + include_fk = True + include_relationships = True + exclude = ("visualizations",) # re-open the params if used + + id = fields.Integer(required=False, allow_none=True) + columns = Nested(ProcessColumnSchema, many=True) + traces = Nested(TraceSchema, many=True) + filters = Nested(FilterSchema, many=True) + data_source = Nested(lambda: DataSourceSchema(only=("id", "name"))) + + +class ProcessFullSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgProcess + include_fk = True + include_relationships = True + + id = fields.Integer(required=False, allow_none=True) + columns = Nested(ProcessColumnSchema, many=True) + traces = Nested(TraceSchema, many=True) + filters = Nested(FilterSchema, many=True) + data_source = Nested(DataSourceSchema) + visualizations = Nested(VisualizationSchema, many=True) + + +class ProcessVisualizationSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgProcess + include_fk = True + include_relationships = True + exclude = ("data_source", "traces") + + columns = Nested(ProcessColumnSchema, many=True) + visualizations = Nested(VisualizationSchema, many=True) + filters = Nested(FilterSchema, many=True) + + +class ProcessOnlySchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgProcess + exclude = ("visualizations", "columns", "traces", "filters") # re-open the params if used + + +class CfgUserSettingSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = CfgUserSetting + + id = fields.Integer(required=False, allow_none=True) + + @post_load + def make_obj(self, data, **kwargs): + return CfgUserSetting(**data) diff --git a/histview2/setting_module/services/about.py b/histview2/setting_module/services/about.py new file mode 100644 index 0000000..f0e8dc1 --- /dev/null +++ b/histview2/setting_module/services/about.py @@ -0,0 +1,37 @@ +import re + +import markdown2 +from flask import Markup + + +def markdown_to_html(markdown_file_path): + extras = ['tables', 'header-ids', 'footnotes', 'code-color', + 'link-pattern', 'markdown-in-html', 'numbering', 'wiki-tables'] + + css, html = split_css_html(markdown_file_path) + html = markdown2.markdown(html, extras=extras) + return Markup(css), Markup(html) + + +def split_css_html(about_fpath): + print('about_fpath', about_fpath) + regex_css = r'\]*>.*\<\/style[ ]*>' + + with open(about_fpath, 'r', encoding='utf-8') as f: + md = f.read() + + css = re.search(regex_css, md, re.DOTALL) + if css: + css = add_about_class(css[0]) + else: + css = '' + + html = re.sub(r'(\]*>)', '', html) + + return css, html + + +def add_about_class(css): + output = re.sub(r'(.*{)', '.about \\1', css, re.DOTALL) + return output diff --git a/histview2/setting_module/services/background_process.py b/histview2/setting_module/services/background_process.py new file mode 100644 index 0000000..40f736b --- /dev/null +++ b/histview2/setting_module/services/background_process.py @@ -0,0 +1,453 @@ +import datetime as dt +import math +import re +import traceback + +from flask_babel import gettext as _ +from sqlalchemy import desc + +from histview2.common.common_utils import DATE_FORMAT_STR, DATE_FORMAT_STR_FACTORY_DB, convert_time +from histview2.common.constants import * +from histview2.common.logger import logger, log_execution_time +from histview2.common.scheduler import JobType, dic_running_job +from histview2.common.services.sse import background_announcer, AnnounceEvent +from histview2.common.timezone_utils import choose_utc_convert_func +from histview2.setting_module.models import * +from histview2.setting_module.models import JobManagement, CsvImport, FactoryImport +from histview2.common.disk_usage import get_disk_capacity_once + +JOB_ID = 'job_id' +JOB_NAME = 'job_name' +JOB_TYPE = 'job_type' +DB_CODE = 'db_code' +PROC_CODE = 'proc_code' +PROC_ID = 'proc_id' +DB_MASTER_NAME = 'db_master_name' +DONE_PERCENT = 'done_percent' +START_TM = 'start_tm' +END_TM = 'end_tm' +DURATION = 'duration' +STATUS = 'status' +PROCESS_MASTER_NAME = 'process_master_name' +DETAIL = 'detail' +DATA_TYPE_ERR = 'data_type_error' + + +@log_execution_time() +def get_background_jobs_service(): + """ + Get background jobs from JobManagement table + """ + + dic_running_job_keys = set(dic_running_job.keys()) + days_ago = dt.datetime.utcnow() - dt.timedelta(days=1) + jobs_from_db = JobManagement.query.filter(JobManagement.start_tm > days_ago.strftime(DATE_FORMAT_STR)).order_by( + desc(JobManagement.id)).all() + + jobs = {} + for job in jobs_from_db: + # check and force dangling jobs to be FAILED + if job.job_type in (JobType.CSV_IMPORT.name, JobType.FACTORY_IMPORT.name, JobType.FACTORY_PAST_IMPORT.name): + job_running_id = {f'{job.job_type}_{job.process_id}'} + elif job.job_type == JobType.GEN_GLOBAL.name: + job_running_id = {f'_{job.job_type}', f'{job.job_type}'} + else: + job_running_id = {job.job_type} + + # status is PROCESSING but actually the job is not running -> forced to be FAILED + if not (job_running_id & dic_running_job_keys) and job.status == JobStatus.PROCESSING.name: + job.status = JobStatus.FAILED.name + job.error_msg = FORCED_TO_BE_FAILED + + # get job information and send to UI + if job.job_type: + job_name = _(job.job_type) + else: + job_name = job.id + + jobs[job.id] = { + JOB_ID: job.id, + JOB_NAME: job_name, + JOB_TYPE: job.job_type or '', + DB_CODE: job.db_code or '', + DB_MASTER_NAME: job.db_name or '', + DONE_PERCENT: job.done_percent or 0, + START_TM: job.start_tm, + END_TM: job.end_tm or '', + DURATION: round(job.duration, 2), + STATUS: str(job.status), + PROCESS_MASTER_NAME: job.process_name or '', + DETAIL: '' + } + + return jobs + + +def send_processing_info(generator_func, job_type: JobType, db_code=None, process_id=None, process_name=None, + after_success_func=None, is_check_disk=True): + """ send percent, status to client + + Arguments: + job_type {JobType} -- [description] + generator_func {[type]} -- [description] + """ + # add new job + start_tm = dt.datetime.utcnow() + with make_session() as meta_session: + job = JobManagement() + job.job_type = job_type.name + job.db_code = db_code + + # datasource_idの代わりに、t_job_managementテーブルにdatasource_nameが保存される。 + if job.db_code is not None: + data_source: CfgDataSource = CfgDataSource.query.get(job.db_code) + job.db_name = data_source.name + + job.process_id = process_id + + # Yamlの代わりに、データベースからデータを取得する。 + job.process_name = None + if process_name: + data_process: CfgProcess = CfgProcess.query.get(job.process_id) + job.process_name = data_process.name + + job.status = str(JobStatus.PROCESSING) + meta_session.add(job) + meta_session.commit() + + # processing info + dic_res = {job.id: { + JOB_ID: job.id, + JOB_NAME: job.job_type or job.id or '', + JOB_TYPE: job.job_type, + DB_CODE: job.db_code or '', + PROC_CODE: process_name or '', + PROC_ID: job.process_id or process_id or '', + DB_MASTER_NAME: job.db_name or '', + DONE_PERCENT: job.done_percent, + START_TM: job.start_tm, + END_TM: job.end_tm, + DURATION: round(job.duration, 2), + STATUS: str(job.status), + PROCESS_MASTER_NAME: job.process_name or '', + DETAIL: '' + }} + + # time variable ( use for set start time in csv import) + anchor_tm = get_current_timestamp() + prev_job_info = None + notify_data_type_error_flg = True + while True: + try: + if is_check_disk: + disk_capacity = get_disk_capacity_once(_job_id=job.id) + if disk_capacity: + background_announcer.announce(disk_capacity.to_dict(), + AnnounceEvent.DISK_USAGE.name) + if disk_capacity.disk_status == DiskUsageStatus.Full: + raise disk_capacity + + job_info = next(generator_func) + + # update job details ( csv_import , gen global...) + if isinstance(job_info, int): + # job percent update + job.done_percent = job_info + elif isinstance(job_info, JobInfo): + prev_job_info = job_info + # job percent update + job.done_percent = job_info.percent + # update job details (csv_import...) + if job_type is JobType.CSV_IMPORT: + detail_rec = insert_csv_import_info(job, anchor_tm, job_info) + if detail_rec: + # update new status + meta_session.add(detail_rec) + meta_session.commit() + anchor_tm = get_current_timestamp() + + # empty files + if job_info.empty_files: + background_announcer.announce(job_info.empty_files, AnnounceEvent.EMPTY_FILE.name) + + elif job_type in (JobType.FACTORY_IMPORT, JobType.FACTORY_PAST_IMPORT): + detail_rec = insert_factory_import_info(job, anchor_tm, job_info) + if detail_rec: + meta_session.add(detail_rec) + meta_session.commit() + anchor_tm = get_current_timestamp() + + # job err msg + update_job_management_status_n_error(job, job_info) + else: + if job_type is JobType.GEN_GLOBAL: + _, dic_cycle_ids, dic_edge_cnt = job_info + save_proc_link_history(meta_session, job.id, dic_cycle_ids, dic_edge_cnt) + meta_session.commit() + + except StopIteration: + update_job_management(job) + + # emit successful import data + if prev_job_info and prev_job_info.has_record: + # call gen global id job + if after_success_func: + proc_link_publish_flg = job_type in (JobType.FACTORY_IMPORT, JobType.CSV_IMPORT) + after_success_func(proc_link_publish_flg) + + # stop while loop + break + except Exception as e: + # update job status + db.session.rollback() + update_job_management(job, str(e)) + logger.exception(str(e)) + traceback.print_exc() + break + finally: + # notify if data type error greater than 100 + if notify_data_type_error_flg and prev_job_info and prev_job_info.data_type_error_cnt > 100: + background_announcer.announce(job.db_name, AnnounceEvent.DATA_TYPE_ERR.name) + dic_res[job.id][DATA_TYPE_ERR] = True + notify_data_type_error_flg = False + + # emit info + dic_res[job.id][DONE_PERCENT] = job.done_percent + dic_res[job.id][END_TM] = job.end_tm + dic_res[job.id][DURATION] = round((dt.datetime.utcnow() - start_tm).total_seconds(), 2) + background_announcer.announce(dic_res, AnnounceEvent.JOB_RUN.name) + + dic_res[job.id][STATUS] = str(job.status) + background_announcer.announce(dic_res, AnnounceEvent.JOB_RUN.name) + + +def update_job_management(job, err=None): + """ update job status + + Arguments: + job {[type]} -- [description] + done_percent {[type]} -- [description] + """ + + if not err and not job.error_msg and ( + job.done_percent == 100 or job.status in (JobStatus.PROCESSING.name, JobStatus.DONE.name)): + job.status = JobStatus.DONE.name + job.done_percent = 100 + job.error_msg = None + else: + job.status = job.status or JobStatus.FAILED.name + job.error_msg = err or job.error_msg or 'An unidentified error has occurred' + + job.duration = round( + (dt.datetime.utcnow() - dt.datetime.strptime(job.start_tm, DATE_FORMAT_STR)).total_seconds(), 2) + job.end_tm = get_current_timestamp() + + +def insert_csv_import_info(job, start_tm, dic_detail): + """ insert csv import information + + Arguments: + job {[type]} -- [description] + file_name {[type]} -- [description] + imported_row {[type]} -- [description] + error_msg {[type]} -- [description] + """ + + csv_import_mana = CsvImport() + csv_import_mana.job_id = job.id + csv_import_mana.process_id = job.process_id + csv_import_mana.status = str(dic_detail.status) + csv_import_mana.file_name = dic_detail.target + csv_import_mana.imported_row = dic_detail.committed_count + csv_import_mana.error_msg = dic_detail.err_msg + csv_import_mana.start_tm = job.start_tm or start_tm + csv_import_mana.end_tm = job.end_tm or get_current_timestamp() + + return csv_import_mana + + +def insert_factory_import_info(job, start_tm, dic_detail): + """ insert csv import information + + Arguments: + job {[type]} -- [description] + file_name {[type]} -- [description] + imported_row {[type]} -- [description] + error_msg {[type]} -- [description] + """ + + import_frm = format_factory_date_to_meta_data(dic_detail.first_cycle_time, dic_detail.auto_increment_col_timezone) + import_to = format_factory_date_to_meta_data(dic_detail.last_cycle_time, dic_detail.auto_increment_col_timezone) + + fac_import_mana = FactoryImport() + fac_import_mana.job_id = job.id + fac_import_mana.process_id = job.process_id + fac_import_mana.import_type = job.job_type + fac_import_mana.import_from = import_frm + fac_import_mana.import_to = import_to + fac_import_mana.status = str(dic_detail.status) + fac_import_mana.imported_row = dic_detail.committed_count + fac_import_mana.error_msg = dic_detail.err_msg + fac_import_mana.start_tm = job.start_tm or start_tm + fac_import_mana.end_tm = job.end_tm or get_current_timestamp() + + return fac_import_mana + + +class JobInfo: + auto_increment_col_timezone: bool + percent: int + status: JobStatus + row_count: int + committed_count: int + has_record: bool + exception: Exception + empty_files: List[str] + + def __init__(self): + self.target = None + self.percent = 0 + self.status = JobStatus.PROCESSING + self.row_count = 0 + self.first_cycle_time = None + self.last_cycle_time = None + self.auto_increment_col_timezone = False + self.empty_files = None + + # private (per chunk in one csv file) + self._exception = None + self._committed_count = 0 + self._err_msg = None + self.start_tm = None + self.end_tm = None + + # 累計(Cumulative for all files in a job) + self.has_record = False + self.has_error = None + self.data_type_error_cnt = 0 + + @property + def committed_count(self): + return self._committed_count + + @committed_count.setter + def committed_count(self, val: int): + if val: + self.has_record = True + + self._committed_count = val + + @property + def err_msg(self): + return self._err_msg + + @err_msg.setter + def err_msg(self, val: str): + if val: + self.has_error = True + + self._err_msg = val or None + + @property + def exception(self): + return self._exception + + @exception.setter + def exception(self, val: Exception): + if val: + self.has_error = True + + self._exception = val or None + + def calc_percent(self, row_count, total): + percent = row_count * 100 / total + percent = math.floor(percent) + if percent >= 100: + percent = 99 + + self.percent = percent + + +def format_factory_date_to_meta_data(date_val, is_tz_col): + if is_tz_col: + convert_utc_func, _ = choose_utc_convert_func(date_val) + date_val = convert_utc_func(date_val) + date_val = date_val.replace('T', ' ') + regex_str = r"(\.)(\d{3})(\d{3})" + date_val = re.sub(regex_str, f'\\1\\2', date_val) + else: + date_val = convert_time(date_val, format_str=DATE_FORMAT_STR_FACTORY_DB, only_milisecond=True) + + return date_val + + +@log_execution_time() +def get_job_detail_service(job_id): + """ + Get all job details of a job + :param job_id: + :return: + """ + job = db.session.query(JobManagement).filter(JobManagement.id == job_id).first() + job_details_as_dict = {} + if job: + if job.job_type == JobType.CSV_IMPORT.name: + job_details = CsvImport.get_error_jobs(job_id=job_id) + else: + job_details = FactoryImport.get_error_jobs(job_id=job_id) + + if job.error_msg: + job_details = [job] + job_details + + for job_detail in job_details: + job_details_as_dict[job_detail.id] = row2dict(job_detail) + + return job_details_as_dict + + +@log_execution_time() +def row2dict(row): + """ + Convert SQLAlchemy returned object to dictionary + :param row: + :return: + """ + object_as_dict = {} + for column in row.__table__.columns: + object_as_dict[column.name] = str(getattr(row, column.name)) + + return object_as_dict + + +def save_proc_link_history(meta_session, job_id, dic_cycle_ids, dic_edge_cnt): + for (start_proc_id, end_proc_id), matched_cnt in dic_edge_cnt.items(): + proc_link_hist = ProcLink() + proc_link_hist.job_id = job_id + proc_link_hist.process_id = start_proc_id + proc_link_hist.target_process_id = end_proc_id + proc_link_hist.matched_count = matched_cnt + meta_session.add(proc_link_hist) + + # save process matched count to db + for proc_id, cycle_ids in dic_cycle_ids.items(): + proc_link_hist = ProcLink() + proc_link_hist.job_id = job_id + proc_link_hist.process_id = proc_id + proc_link_hist.matched_count = len(cycle_ids) + meta_session.add(proc_link_hist) + + return meta_session + + +def update_job_management_status_n_error(job, job_info: JobInfo): + if not job.status or job_info.status.value > JobStatus[job.status].value: + job.status = job_info.status.name + + if job_info.err_msg: + if job.error_msg: + job.error_msg += job_info.err_msg + else: + job.error_msg = job_info.err_msg + + # reset job info + job_info.err_msg = None diff --git a/histview2/setting_module/services/process_config.py b/histview2/setting_module/services/process_config.py new file mode 100644 index 0000000..1ad4122 --- /dev/null +++ b/histview2/setting_module/services/process_config.py @@ -0,0 +1,139 @@ +from functools import lru_cache + +from histview2.common.constants import DBType, ProcessCfgConst +from histview2.common.pydn.dblib.db_proxy import DbProxy +from histview2.common.services.jp_to_romaji_utils import to_romaji +from histview2.setting_module.models import CfgProcess, make_session, CfgDataSource, crud_config, CfgProcessColumn, \ + insert_or_update_config, CfgFilter, CfgVisualization +from histview2.setting_module.schemas import ProcessSchema, ProcessOnlySchema, ProcessColumnSchema, FilterSchema, \ + ProcessVisualizationSchema + + +def get_all_process(): + process = CfgProcess.get_all() or [] + return process + + +def get_all_process_no_nested(): + process_only_schema = ProcessOnlySchema(many=True) + processes = CfgProcess.get_all() or [] + return process_only_schema.dump(processes, many=True) + + +def get_process_cfg(proc_id): + process_schema = ProcessSchema() + process = CfgProcess.query.get(proc_id) or {} + return process_schema.dump(process) + + +def get_process_columns(proc_id): + proc_col_schema = ProcessColumnSchema(many=True) + columns = CfgProcessColumn.query.filter(CfgProcessColumn.process_id == proc_id).all() or [] + return proc_col_schema.dump(columns) + + +def get_process_filters(proc_id): + proc_filter_schema = FilterSchema(many=True) + filters = CfgFilter.query.filter(CfgFilter.process_id == proc_id).all() or [] + return proc_filter_schema.dump(filters) + + +def get_process_visualizations(proc_id): + proc_vis_schema = ProcessVisualizationSchema() + process = CfgProcess.query.get(proc_id) or {} + return proc_vis_schema.dump(process) + + +def get_all_visualizations(): + return list(set([cfg.filter_detail_id for cfg in CfgVisualization.get_filter_ids()])) + + +# def get_all_process_cfg_with_nested(): +# process_schema = ProcessSchema(many=True) +# processes = CfgProcess.get_all() or [] +# return process_schema.dump(processes, many=True) + + +def create_or_update_process_cfg(proc_data): + with make_session() as meta_session: + # save process config + process = insert_or_update_config(meta_session=meta_session, data=proc_data, + key_names=CfgProcess.id.key, model=CfgProcess) + meta_session.commit() + + # create column alchemy object + assign process id + columns = proc_data[ProcessCfgConst.PROC_COLUMNS.value] + for proc_column in columns: + proc_column.process_id = process.id + # transform english name + proc_column.english_name = to_romaji(proc_column.english_name) + + # save columns + crud_config(meta_session=meta_session, + data=columns, + parent_key_names=CfgProcessColumn.process_id.key, + key_names=CfgProcessColumn.column_name.key, + model=CfgProcessColumn) + + return process + + +def query_database_tables(db_id): + with make_session() as mss: + data_source = mss.query(CfgDataSource).get(db_id) + if not data_source: + return None + + output = dict(ds_type=data_source.type, tables=[]) + # return None if CSV + if data_source.type.lower() == DBType.CSV.name.lower(): + return output + + updated_at = data_source.db_detail.updated_at + output['tables'] = get_list_tables_and_views(data_source.id, updated_at) + + return output + + +@lru_cache(maxsize=20) +def get_list_tables_and_views(data_source_id, updated_at=None): + # updated_at only for cache + print('database config updated_at:', updated_at, ', so cache can not be used') + with DbProxy(data_source_id) as db_instance: + tables = db_instance.list_tables_and_views() + + return tables + + +def get_ds_tables(ds_id): + try: + tables = query_database_tables(ds_id) + return tables['tables'] or [] + except Exception: + return [] + + +def get_datasource_and_tables(data_sources): + tables = [] + ds_objects = { + 'list_ds': [convert2serialize(ds) for ds in data_sources], + 'tables': tables + } + return ds_objects + + +def convert2serialize(obj): + if isinstance(obj, dict): + return {k: convert2serialize(v) for k, v in obj.items()} + elif hasattr(obj, "_ast"): + return convert2serialize(obj._ast()) + elif not isinstance(obj, str) and hasattr(obj, "__iter__"): + return [convert2serialize(v) for v in obj] + elif hasattr(obj, "__dict__"): + return { + k: convert2serialize(v) + for k, v in obj.__dict__.items() + if not callable(v) and not k.startswith('_') + } + else: + return obj diff --git a/histview2/setting_module/services/trace_config.py b/histview2/setting_module/services/trace_config.py new file mode 100644 index 0000000..6295afd --- /dev/null +++ b/histview2/setting_module/services/trace_config.py @@ -0,0 +1,55 @@ +from histview2 import Session +from histview2.common.logger import log_execution_time +from histview2.setting_module.models import CfgProcess, CfgTrace, CfgTraceKey, make_session +from histview2.setting_module.schemas import ProcessSchema + + +@log_execution_time() +def get_all_processes_traces_info(): + procs = CfgProcess.get_all() or [] + return dump_json(procs) + + +@log_execution_time() +def dump_json(procs): + proc_schema = ProcessSchema(many=True) + proc_jsons = proc_schema.dump(procs, many=True) or {} + return proc_jsons + + +def save_trace_config_to_db(traces): + # sch = TraceConfigSchema(many=True) + # sch.loads(params) # TODO load to schema + with make_session() as meta_session: + # delete all old edges + meta_session.query(CfgTrace).delete() + meta_session.query(CfgTraceKey).delete() # TODO check cascade + + for trace in traces: + cfg_trace = gen_cfg_trace(trace) + meta_session.add(cfg_trace) + + +def gen_cfg_trace(trace): + self_col_ids = trace.get('self_col') + target_col_ids = trace.get('target_col') + self_substrs = trace.get('self_substr') + target_substrs = trace.get('target_substr') + trace_keys = [] + for idx, self_col_id in enumerate(self_col_ids): + target_col_id = target_col_ids[idx] + self_sub_from, self_sub_to = self_substrs[idx] or (None, None) + target_sub_from, target_sub_to = target_substrs[idx] or (None, None) + trace_key = CfgTraceKey(self_column_id=self_col_id, + self_column_substr_from=self_sub_from, + self_column_substr_to=self_sub_to, + target_column_id=target_col_id, + target_column_substr_from=target_sub_from, + target_column_substr_to=target_sub_to) + trace_keys.append(trace_key) + self_process_id = trace.get('from') + target_process_id = trace.get('to') + cfg_trace = CfgTrace(self_process_id=self_process_id, target_process_id=target_process_id, + trace_keys=trace_keys) + + return cfg_trace diff --git a/histview2/static/analyze/css/anomaly_detection.css b/histview2/static/analyze/css/anomaly_detection.css new file mode 100644 index 0000000..7e41f0a --- /dev/null +++ b/histview2/static/analyze/css/anomaly_detection.css @@ -0,0 +1,218 @@ +#plot-cards { + background-color: #222; + border: none; + padding: 0; + margin-top: 1rem; +} + +#plot-cards .row { + margin: 0; +} +#plot-cards .col { + padding: 2px; +} + +.plot-boxs .plotbox-header { + border-bottom: 1px solid #444; +} + +#plot-cards .plot-boxs { + border: 1px solid #444; + border-radius: 4px; +} + + +.plotbox-header h5 { + line-height: 35px; + margin: 0 0 0 5px; + display: inline-block; +} + +.plot-tabs { + height: 4vh; +} + +div#scatter-plot { + padding: 0; +} + +.nav-right { + float: right; +} + +.nav-item { + font-size: 1vw; +} + +.plot-tabs .nav-link { + padding: 0.5rem 0.5rem !important; + height: 4.1vh; /* 0.1 to make borders ovelapped */ +} + +.table-dark th, .table-dark td { + padding: 0.1rem 0.25rem !important; + background-color: #222; + border-top: none; + border-bottom: 1px solid #444; + border-right: 1px solid #444; + vertical-align: middle; +} +/* , .dataTables_scrollBody */ +#dp-tbl { + overflow: scroll !important; + height: 20vw; +} +.dataTables_scrollBody::-webkit-scrollbar-track, +#dp-tbl::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); + background-color: #444; +} + +.dataTables_scrollBody::-webkit-scrollbar, +#dp-tbl::-webkit-scrollbar { + width: 15px; + height: 15px; + background-color: #444; +} + +.dataTables_scrollBody::-webkit-scrollbar-thumb, +#dp-tbl::-webkit-scrollbar-thumb { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); + background-color: #222222; +} + +.dataTables_scrollBody::-webkit-scrollbar-corner, +#dp-tbl::-webkit-scrollbar-corner { + background-color: #444; +} + +.dataTables_scroll { + border: solid 0.5px #444; +} + +table.dataTable thead th{ + border: 0.5px solid #444 !important; +} + +.search-box { + padding: 0 !important; +} + +#dp-body tr td:nth-child(2) { + text-align: right; +} + +.plot-container .plotly { + width: 100%; +} + +.plot-responsive { + max-width: 30vw !important; + height: 20vw !important; +} + +#plot-cards .col-sm-4 { + padding: 5px; +} + +.spliter { + margin-bottom: 0 !important; +} + +.fcard { + border: 1px solid #444; + background-color: #303030; + background-clip: border-box; + border-radius: 0.25rem; +} + +.fcard .card-body { + border-bottom: 2px dotted #444; +} + +.fcard .sensor-list { + border: none !important; +} + +table.dataTable tbody tr, +table.dataTable.stripe tbody tr.odd, +table.dataTable.display tbody tr.odd, +table.dataTable.display tbody .sorting_1 { + background-color: #222222 !important; +} + +table.dataTable.hover tbody tr:hover, +table.dataTable.display tbody tr:hover { + background-color: #303030 !important; +} + +table.dataTable.row-border tbody th, +table.dataTable.row-border tbody td, +table.dataTable.display tbody th, +table.dataTable.display tbody td, +table.dataTable.cell-border tbody th, +table.dataTable.cell-border tbody td { + border-top: none !important; + border-bottom: 1px solid #303030 !important; + white-space: nowrap; +} + +/* centralize checkbox */ +.checkbox-col { + text-align: center !important; +} + +#pcaConditionTbl tr td:nth-child(5){ + text-align: left !important; + font-family: 'Consolas', 'Courier New', 'Courier', 'Monaco', 'monospace'; +} + +.cond-proc { + padding: 1rem !important; + border: 1px solid #444; + position: relative; +} + +.confirm-btn { + margin-right: 10px; + padding-left: 20px; + padding-right: 20px; +} + +.tooltip-pca { + position: relative; +} + +.tooltip-pca .tooltip-pca-text { + visibility: hidden; + width: 150px; + background-color: rgba(8, 8, 8, .8); + color: #fff; + text-align: left; + border-radius: 6px; + border: 2px solid #444444; + padding: 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + /* Position the tooltip */ + position: fixed; + z-index: 9999; + top: 10%; + left: 82%; + +} + +.tooltip-pca:hover .tooltip-pca-text { + visibility: visible; +} + +/* hide search bar */ +.dataTables_filter{ + display: none; +} + +h6 { + margin: 0.3rem; +} diff --git a/histview2/static/analyze/css/toastr.css b/histview2/static/analyze/css/toastr.css new file mode 100644 index 0000000..49fe789 --- /dev/null +++ b/histview2/static/analyze/css/toastr.css @@ -0,0 +1,200 @@ +.toast-title { + font-weight: bold; +} +.toast-message { + -ms-word-wrap: break-word; + word-wrap: break-word; +} +.toast-message a, +.toast-message label { + color: #ffffff; +} +.toast-message a:hover { + color: #cccccc; + text-decoration: none; +} +.toast-close-button { + position: relative; + right: -0.3em; + top: -0.3em; + float: right; + font-size: 20px; + font-weight: bold; + color: #ffffff; + -webkit-text-shadow: 0 1px 0 #ffffff; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); +} +.toast-close-button:hover, +.toast-close-button:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); +} +/*Additional properties for button version + iOS requires the button element instead of an anchor tag. + If you want the anchor version, it requires `href="#"`.*/ +button.toast-close-button { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.toast-top-center { + top: 0; + right: 0; + width: 100%; +} +.toast-bottom-center { + bottom: 0; + right: 0; + width: 100%; +} +.toast-top-full-width { + top: 0; + right: 0; + width: 100%; +} +.toast-bottom-full-width { + bottom: 0; + right: 0; + width: 100%; +} +.toast-top-left { + top: 12px; + left: 12px; +} +.toast-top-right { + top: 12px; + right: 12px; +} +.toast-bottom-right { + right: 12px; + bottom: 12px; +} +.toast-bottom-left { + bottom: 12px; + left: 12px; +} +#toast-container { + position: fixed; + z-index: 999999; + pointer-events: none; + /*overrides*/ +} +#toast-container * { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +#toast-container > div { + position: relative; + pointer-events: auto; + overflow: hidden; + margin: 0 0 6px; + padding: 15px 15px 15px 50px; + width: 300px; + -moz-border-radius: 3px 3px 3px 3px; + -webkit-border-radius: 3px 3px 3px 3px; + border-radius: 3px 3px 3px 3px; + background-position: 15px center; + background-repeat: no-repeat; + -moz-box-shadow: 0 0 12px #999999; + -webkit-box-shadow: 0 0 12px #999999; + box-shadow: 0 0 12px #999999; + color: #ffffff; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); +} +#toast-container > :hover { + -moz-box-shadow: 0 0 12px #000000; + -webkit-box-shadow: 0 0 12px #000000; + box-shadow: 0 0 12px #000000; + opacity: 1; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + filter: alpha(opacity=100); + cursor: pointer; +} +#toast-container > .toast-info { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important; +} +#toast-container > .toast-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; +} +#toast-container > .toast-success { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important; +} +#toast-container > .toast-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important; +} +#toast-container.toast-top-center > div, +#toast-container.toast-bottom-center > div { + width: 300px; + margin-left: auto; + margin-right: auto; +} +#toast-container.toast-top-full-width > div, +#toast-container.toast-bottom-full-width > div { + width: 96%; + margin-left: auto; + margin-right: auto; +} +.toast { + background-color: #030303; +} +.toast-success { + background-color: #51a351; +} +.toast-error { + background-color: #bd362f; +} +.toast-info { + background-color: #2f96b4; +} +.toast-warning { + background-color: #f89406; +} +.toast-progress { + position: absolute; + left: 0; + bottom: 0; + height: 4px; + background-color: #000000; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); +} +/*Responsive Design*/ +@media all and (max-width: 240px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 11em; + } + #toast-container .toast-close-button { + right: -0.2em; + top: -0.2em; + } +} +@media all and (min-width: 241px) and (max-width: 480px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 18em; + } + #toast-container .toast-close-button { + right: -0.2em; + top: -0.2em; + } +} +@media all and (min-width: 481px) and (max-width: 768px) { + #toast-container > div { + padding: 15px 15px 15px 50px; + width: 25em; + } +} diff --git a/histview2/static/analyze/js/generateJson.js b/histview2/static/analyze/js/generateJson.js new file mode 100644 index 0000000..7c48b78 --- /dev/null +++ b/histview2/static/analyze/js/generateJson.js @@ -0,0 +1,1270 @@ +const generateCircles = (json) => { + if (!json) return {}; + + // sigma + const dataSigmaX = getNode(json, ['circles', 'Sigma', 'x']) || []; + const dataSigmaY = getNode(json, ['circles', 'Sigma', 'y']) || []; + const dataSigmaText = dataSigmaX.map( + (ele, idx) => `border: Sigma
xvar: ${ele}
yvar: ${dataSigmaY[idx]}`, + ) || []; + + // 2sigma + const data2SigmaX = getNode(json, ['circles', '2Sigma', 'x']) || []; + const data2SigmaY = getNode(json, ['circles', '2Sigma', 'y']) || []; + const data2SigmaText = data2SigmaX.map( + (ele, idx) => `border: 2Sigma
xvar: ${ele}
yvar: ${data2SigmaY[idx]}`, + ) || []; + + // 3sigma + const data3SigmaX = getNode(json, ['circles', '3Sigma', 'x']) || []; + const data3SigmaY = getNode(json, ['circles', '3Sigma', 'y']) || []; + const data3SigmaText = data3SigmaX.map( + (ele, idx) => `border: 3Sigma
xvar: ${ele}
yvar: ${data3SigmaY[idx]}`, + ) || []; + + // Range + const dataRangeX = getNode(json, ['circles', 'Range', 'x']) || []; + const dataRangeY = getNode(json, ['circles', 'Range', 'y']) || []; + const dataRangeText = dataRangeX.map( + (ele, idx) => `border: Range
xvar: ${ele}
yvar: ${dataRangeY[idx]}`, + ) || []; + + // Parcentile 0.85 + const dataParcentileX = getNode(json, ['circles', 'Percentile85', 'x']) || []; + const dataParcentileY = getNode(json, ['circles', 'Percentile85', 'y']) || []; + const dataParcentileText = dataParcentileX.map( + (ele, idx) => `border: Percentile 0.85
xvar: ${ele}
yvar: ${dataParcentileY[idx]}`, + ) || []; + + // axis label + const axislab = getNode(json, ['axislab']) || ['PC1', 'PC2']; + + return { + dataSigmaX, + dataSigmaY, + dataSigmaText, + data2SigmaX, + data2SigmaY, + data2SigmaText, + data3SigmaX, + data3SigmaY, + data3SigmaText, + dataRangeX, + dataRangeY, + dataRangeText, + dataParcentileX, + dataParcentileY, + dataParcentileText, + axislab, + }; +}; + + +const generateXTrainScatter = (json) => { + if (!json) return {}; + + // scatter + const dataScatterX = getNode(json, ['scatter', 'x']) || []; + const dataScatterY = getNode(json, ['scatter', 'y']) || []; + const dataScatterText = dataScatterX.map((ele, idx) => `'xvar: ${ele}
yvar: ${dataScatterY[idx]}'`) || []; + + const { + dataSigmaX, + dataSigmaY, + dataSigmaText, + data2SigmaX, + data2SigmaY, + data2SigmaText, + data3SigmaX, + data3SigmaY, + data3SigmaText, + dataRangeX, + dataRangeY, + dataRangeText, + dataParcentileX, + dataParcentileY, + dataParcentileText, + axislab, + } = generateCircles(json); + + return { + data: [{ + x: dataScatterX, + y: dataScatterY, + text: dataScatterText, + type: 'scatter', + mode: 'markers', + marker: { + autocolorscale: false, + color: 'white', + opacity: 0.5, + size: 3.02362204724409, + symbol: 'circle', + line: { + width: 1.88976377952756, + color: 'rgba(255,255,255,1)', + }, + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: dataSigmaX, + y: dataSigmaY, + text: dataSigmaText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(204,229,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: 'Sigma', + legendgroup: 'Sigma', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: data2SigmaX, + y: data2SigmaY, + text: data2SigmaText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(153,204,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: '2Sigma', + legendgroup: '2Sigma', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: data3SigmaX, + y: data3SigmaY, + text: data3SigmaText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(102,178,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: '3Sigma', + legendgroup: '3Sigma', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: dataRangeX, + y: dataRangeY, + text: dataRangeText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(30,144,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: 'Range', + legendgroup: 'Range', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: dataParcentileX, + y: dataParcentileY, + text: dataParcentileText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(255,52,179,1)', + dash: 'solid', + }, + hoveron: 'points', + name: 'Parcentile 0.85', + legendgroup: 'Parcentile 0.85', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: [0, 0], + y: [-3.51416158802218, 3.51416158802218], + text: 'xintercept: 0', + type: 'scatter', + mode: 'lines', + line: { + width: 1.88976377952756, + color: 'rgba(64,64,64,1)', + dash: 'solid', + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: [-3.5156396748279, 3.50907246263227], + y: [0, 0], + text: 'yintercept: 0', + type: 'scatter', + mode: 'lines', + line: { + width: 1.88976377952756, + color: 'rgba(64,64,64,1)', + dash: 'solid', + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, + ], + layout: { + margin: { + t: 24.8556800885568, + r: 6.6417600664176, + b: 36.086896360869, + l: 33.8729763387298, + }, + plot_bgcolor: '#222222', + paper_bgcolor: '#222222', + font: { + color: 'rgba(0,0,0,1)', + family: '', + size: 13.2835201328352, + }, + xaxis: { + domain: [0, 1], + automargin: true, + type: 'linear', + autorange: true, + range: [-3.5156396748279, 3.50907246263227], + tickmode: 'array', + ticktext: ['-2', '0', '2'], + tickvals: [-2, 4.44089209850063e-16, 2], + categoryorder: 'array', + categoryarray: ['-2', '0', '2'], + nticks: null, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: 0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: '#444444', + gridwidth: 0, + zeroline: false, + anchor: 'y', + title: { + text: axislab[0], + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 14, + }, + }, + scaleanchor: 'y', + scaleratio: 1, + hoverformat: '.2f', + }, + yaxis: { + domain: [0, 1], + automargin: true, + type: 'linear', + autorange: true, + range: [-3.51416158802218, 3.51416158802218], + tickmode: 'array', + ticktext: ['-2', '0', '2'], + tickvals: [-2, 0, 2], + categoryorder: 'array', + categoryarray: ['-2', '0', '2'], + nticks: null, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: 0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: '#444444', + gridwidth: 0, + zeroline: false, + anchor: 'x', + title: { + text: axislab[1], + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 14, + }, + }, + scaleratio: 1, + hoverformat: '.2f', + }, + shapes: [{ + type: 'rect', + fillcolor: null, + line: { + color: null, + width: 0, + linetype: [], + }, + yref: 'paper', + xref: 'paper', + x0: 0, + x1: 1, + y0: 0, + y1: 1, + }, + ], + showlegend: true, + legend: { + bgcolor: 'transparent', + bordercolor: 'transparent', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 11, + }, + xanchor: 'right', + tracegroupgap: 3, + }, + annotations: [{ + text: 'Guide', + x: 1.02, + y: 1, + showarrow: false, + ax: 0, + ay: 0, + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + xref: 'paper', + yref: 'paper', + textangle: 0, + xanchor: 'left', + yanchor: 'bottom', + legendTitle: true, + }, + ], + hovermode: 'closest', + barmode: 'relative', + autosize: true, + }, + config: { + doubleClick: 'reset', + showSendToCloud: false, + }, + source: 'A', + attrs: { + '2f6447552f24': { + x: {}, + y: {}, + type: 'scatter', + }, + '2f646ad6734': { + x: {}, + y: {}, + colour: {}, + 'x.1': {}, + 'y.1': {}, + }, + '2f64404a41b1': { + xintercept: {}, + }, + '2f64610e1c0': { + yintercept: {}, + }, + }, + cur_data: '2f6447552f24', + visdat: { + '2f6447552f24': ['function (y) ', 'x'], + '2f646ad6734': ['function (y) ', 'x'], + '2f64404a41b1': ['function (y) ', 'x'], + '2f64610e1c0': ['function (y) ', 'x'], + }, + highlight: { + on: 'plotly_click', + persistent: false, + dynamic: false, + selectize: false, + opacityDim: 0.2, + selected: { + opacity: 1, + }, + debounce: 0, + }, + shinyEvents: ['plotly_hover', 'plotly_click', 'plotly_selected', 'plotly_relayout', 'plotly_brushed', 'plotly_brushing', 'plotly_clickannotation', 'plotly_doubleclick', 'plotly_deselect', 'plotly_afterplot', 'plotly_sunburstclick'], + base_url: 'https://plot.ly', + }; +}; + +const generateXTestScatter = (json, jsonTrain) => { + if (!json) return {}; + + // scatter + const dataScatterX = getNode(json, ['scatter', 'x']) || []; + const dataScatterY = getNode(json, ['scatter', 'y']) || []; + const dataScatterText = dataScatterX.map((ele, idx) => `'xvar: ${ele}
yvar: ${dataScatterY[idx]}'`) || []; + + const { + dataSigmaX, + dataSigmaY, + dataSigmaText, + data2SigmaX, + data2SigmaY, + data2SigmaText, + data3SigmaX, + data3SigmaY, + data3SigmaText, + dataRangeX, + dataRangeY, + dataRangeText, + dataParcentileX, + dataParcentileY, + dataParcentileText, + axislab, + } = generateCircles(jsonTrain); + + + return { + data: [{ + x: dataScatterX, + y: dataScatterY, + text: dataScatterText, + type: 'scatter', + mode: 'markers', + marker: { + autocolorscale: false, + color: 'rgba(255,165,0,1)', + opacity: 0.5, + size: 3.02362204724409, + symbol: 'square', + line: { + width: 1.88976377952756, + color: 'rgba(255,165,0,1)', + }, + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: dataSigmaX, + y: dataSigmaY, + text: dataSigmaText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(204,229,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: 'Sigma', + legendgroup: 'Sigma', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: data2SigmaX, + y: data2SigmaY, + text: data2SigmaText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(153,204,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: '2Sigma', + legendgroup: '2Sigma', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: data3SigmaX, + y: data3SigmaY, + text: data3SigmaText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(102,178,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: '3Sigma', + legendgroup: '3Sigma', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: dataRangeX, + y: dataRangeY, + text: dataRangeText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(30,144,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: 'Range', + legendgroup: 'Range', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: dataParcentileX, + y: dataParcentileY, + text: dataParcentileText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(255,52,179,1)', + dash: 'solid', + }, + hoveron: 'points', + name: 'Parcentile 0.85', + legendgroup: 'Parcentile 0.85', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: [0, 0], + y: [-3.94029493306541, 3.94029493306541], + text: 'xintercept: 0', + type: 'scatter', + mode: 'lines', + line: { + width: 1.88976377952756, + color: 'rgba(64,64,64,1)', + dash: 'solid', + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: [-3.94195225524749, 3.9345886914811], + y: [0, 0], + text: 'yintercept: 0', + type: 'scatter', + mode: 'lines', + line: { + width: 1.88976377952756, + color: 'rgba(64,64,64,1)', + dash: 'solid', + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, + ], + layout: { + margin: { + t: 24.8556800885568, + r: 6.6417600664176, + b: 36.086896360869, + l: 33.8729763387298, + }, + plot_bgcolor: 'rgba(0,0,0,1)', + paper_bgcolor: 'rgba(26,26,26,1)', + font: { + color: 'rgba(0,0,0,1)', + family: '', + size: 13.2835201328352, + }, + xaxis: { + domain: [0, 1], + automargin: true, + type: 'linear', + autorange: true, + tickmode: 'array', + ticktext: ['-2', '0', '2'], + tickvals: [-2, 0, 2], + categoryorder: 'array', + categoryarray: ['-2', '0', '2'], + nticks: null, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: null, + gridwidth: 0, + zeroline: false, + anchor: 'y', + title: { + text: axislab[0], + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + scaleanchor: 'y', + scaleratio: 1, + hoverformat: '.2f', + }, + yaxis: { + domain: [0, 1], + automargin: true, + type: 'linear', + autorange: true, + tickmode: 'array', + ticktext: ['-2', '0', '2'], + tickvals: [-2, 0, 2], + categoryorder: 'array', + categoryarray: ['-2', '0', '2'], + nticks: null, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: null, + gridwidth: 0, + zeroline: false, + anchor: 'x', + title: { + text: axislab[1], + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + scaleanchor: 'x', + scaleratio: 1, + hoverformat: '.2f', + }, + shapes: [{ + type: 'rect', + fillcolor: null, + line: { + color: null, + width: 0, + linetype: [], + }, + yref: 'paper', + xref: 'paper', + x0: 0, + x1: 1, + y0: 0, + y1: 1, + }, + ], + showlegend: false, + legend: { + bgcolor: 'rgba(26,26,26,1)', + bordercolor: 'transparent', + borderwidth: 1.71796707229778, + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + }, + hovermode: 'closest', + barmode: 'relative', + }, + config: { + doubleClick: 'reset', + showSendToCloud: false, + }, + source: 'A', + attrs: { + '274714c1e60': { + x: {}, + y: {}, + type: 'scatter', + }, + '2746e4841e1': { + x: {}, + y: {}, + colour: {}, + 'x.1': {}, + 'y.1': {}, + }, + 27471696011: { + xintercept: {}, + }, + 27436716639: { + yintercept: {}, + }, + }, + cur_data: '274714c1e60', + visdat: { + '274714c1e60': ['function (y) ', 'x'], + '2746e4841e1': ['function (y) ', 'x'], + 27471696011: ['function (y) ', 'x'], + 27436716639: ['function (y) ', 'x'], + }, + highlight: { + on: 'plotly_click', + persistent: false, + dynamic: false, + selectize: false, + opacityDim: 0.2, + selected: { + opacity: 1, + }, + debounce: 0, + }, + shinyEvents: ['plotly_hover', 'plotly_click', 'plotly_selected', 'plotly_relayout', 'plotly_brushed', + 'plotly_brushing', 'plotly_clickannotation', 'plotly_doubleclick', 'plotly_deselect', + 'plotly_afterplot', 'plotly_sunburstclick'], + base_url: 'https://plot.ly', + }; +}; + +const generateBiplot = (json, jsonTrain, sampleNo = null) => { + if (!json) return {}; + + // scatter + const dataX = getNode(json, ['x']) || []; + const dataY = getNode(json, ['y']) || []; + + // vector labels + const angle = getNode(json, ['angle']) || []; + const hjust = getNode(json, ['hjust']) || []; + const varname = getNode(json, ['varname']) || []; + const sensorHoverText = dataX.map( + (ele, i) => `xvar: ${ele}
yvar: ${dataY[i]}
varname: ${varname[i]}
angle: ${angle[i]}
hjust: ${hjust[i]}`, + ) || []; + + const vectorX = []; + dataX.forEach((x, i) => { + if (i > 0) vectorX.push(null); + vectorX.push(0); + vectorX.push(x); + }); + + const vectorText = []; + const vectorY = []; + dataY.forEach((x, i) => { + if (i > 0) { + vectorY.push(null); + vectorText.push(null); + } + vectorY.push(0); + vectorY.push(x); + vectorText.push(`x: 0
y: 0
xvar: ${dataX[i]}
yvar: ${dataY[i]}`); + vectorText.push(`x: 0
y: 0
xvar: ${dataX[i]}
yvar: ${dataY[i]}`); + }); + + // clickedPoint + const clickedPoint = getNode(json, ['clicked_point']) || { x: [null], y: [null] }; + + const { + dataSigmaX, + dataSigmaY, + dataSigmaText, + data2SigmaX, + data2SigmaY, + data2SigmaText, + data3SigmaX, + data3SigmaY, + data3SigmaText, + dataRangeX, + dataRangeY, + dataRangeText, + dataParcentileX, + dataParcentileY, + dataParcentileText, + axislab, + } = generateCircles(jsonTrain); + + return { + data: [{ + x: dataSigmaX, + y: dataSigmaY, + text: dataSigmaText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(204,229,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: 'Sigma', + legendgroup: 'Sigma', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: data2SigmaX, + y: data2SigmaY, + text: data2SigmaText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(153,204,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: '2Sigma', + legendgroup: '2Sigma', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: data3SigmaX, + y: data3SigmaY, + text: data3SigmaText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(102,178,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: '3Sigma', + legendgroup: '3Sigma', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: dataRangeX, + y: dataRangeY, + text: dataRangeText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(30,144,255,1)', + dash: 'solid', + }, + hoveron: 'points', + name: 'Range', + legendgroup: 'Range', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: dataParcentileX, + y: dataParcentileY, + text: dataParcentileText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.13385826771654, + color: 'rgba(255,52,179,1)', + dash: 'solid', + }, + hoveron: 'points', + name: 'Parcentile 0.85', + legendgroup: 'Parcentile 0.85', + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: vectorX, + y: vectorY, + text: vectorText, + type: 'scatter', + mode: 'lines', + line: { + width: 1.88976377952756, + color: 'rgba(255,192,203,1)', + dash: 'solid', + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: dataX.map(x => x * 2), + y: dataY.map(y => y * 2), + text: varname, + hovertext: sensorHoverText, + textfont: { + size: 11.3385826771654, + color: 'rgba(255,255,255,1)', + }, + type: 'scatter', + mode: 'text', + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: clickedPoint.x, + y: clickedPoint.y, + text: `xvar: ${clickedPoint.x[0]}
yvar: ${clickedPoint.y[0]}`, + type: 'scatter', + mode: 'markers', + marker: { + autocolorscale: false, + color: 'rgba(255,165,0,1)', + opacity: 0.5, + size: 11.3385826771654, + symbol: 'square', + line: { + width: 1.88976377952756, + color: 'rgba(255,165,0,1)', + }, + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: [0, 0], + y: [-4.07856844526106, 6.84403868917397], + text: 'xintercept: 0', + type: 'scatter', + mode: 'lines', + line: { + width: 1.88976377952756, + color: 'rgba(64,64,64,1)', + dash: 'solid', + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, { + x: [-5.82714314845274, 5.83842812409785], + y: [0, 0], + text: 'yintercept: 0', + type: 'scatter', + mode: 'lines', + line: { + width: 1.88976377952756, + color: 'rgba(64,64,64,1)', + dash: 'solid', + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }, + ], + layout: { + margin: { + t: 38.139200221392, + r: 6.6417600664176, + b: 36.086896360869, + l: 33.8729763387298, + }, + plot_bgcolor: 'rgba(0,0,0,1)', + paper_bgcolor: 'rgba(26,26,26,1)', + font: { + color: 'rgba(0,0,0,1)', + family: '', + size: 13.2835201328352, + }, + title: { + text: `Biplot with Sample No. ${sampleNo || 1}`, + font: { + color: 'rgba(204,229,255,1)', + family: '', + size: 13.2835201328352, + }, + x: 0, + xref: 'paper', + }, + xaxis: { + domain: [0, 1], + automargin: true, + type: 'linear', + autorange: true, + tickmode: 'array', + ticktext: ['-3', '0', '3'], + tickvals: [-3, 0, 3], + categoryorder: 'array', + categoryarray: ['-3', '0', '3'], + nticks: null, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: null, + gridwidth: 0, + zeroline: false, + anchor: 'y', + title: { + text: axislab[0], + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + scaleanchor: 'y', + scaleratio: 1, + hoverformat: '.2f', + }, + yaxis: { + domain: [0, 1], + automargin: true, + type: 'linear', + autorange: true, + tickmode: 'array', + ticktext: ['-4', '-2', '0', '2', '4', '6'], + tickvals: [-4, -2, 0, 2, 4, 6], + categoryorder: 'array', + categoryarray: ['-4', '-2', '0', '2', '4', '6'], + nticks: null, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: null, + gridwidth: 0, + zeroline: false, + anchor: 'x', + title: { + text: axislab[1], + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + scaleanchor: 'x', + scaleratio: 1, + hoverformat: '.2f', + }, + shapes: [{ + type: 'rect', + fillcolor: null, + line: { + color: null, + width: 0, + linetype: [], + }, + yref: 'paper', + xref: 'paper', + x0: 0, + x1: 1, + y0: 0, + y1: 1, + }, + ], + showlegend: true, + legend: { + bgcolor: 'rgba(26,26,26,1)', + bordercolor: 'transparent', + borderwidth: 1.71796707229778, + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + y: 0.940944881889764, + }, + annotations: [{ + text: 'Guide', + x: 1.02, + y: 1, + showarrow: false, + ax: 0, + ay: 0, + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + xref: 'paper', + yref: 'paper', + textangle: -0, + xanchor: 'left', + yanchor: 'bottom', + legendTitle: true, + }, + ], + hovermode: 'closest', + barmode: 'relative', + autosize: true, + }, + config: { + doubleClick: 'reset', + showSendToCloud: false, + }, + source: 'A', + attrs: { + '27439d70d': { + x: {}, + y: {}, + colour: {}, + type: 'scatter', + }, + '2742d6549e2': { + x: {}, + y: {}, + xend: {}, + yend: {}, + }, + '27465c06157': { + x: {}, + y: {}, + label: {}, + angle: {}, + hjust: {}, + }, + '2743c634b8': { + x: {}, + y: {}, + }, + '2745b764c1c': { + xintercept: {}, + }, + '27461e19c8': { + yintercept: {}, + }, + }, + cur_data: '27439d70d', + visdat: { + '27439d70d': ['function (y) ', 'x'], + '2742d6549e2': ['function (y) ', 'x'], + '27465c06157': ['function (y) ', 'x'], + '2743c634b8': ['function (y) ', 'x'], + '2745b764c1c': ['function (y) ', 'x'], + '27461e19c8': ['function (y) ', 'x'], + }, + highlight: { + on: 'plotly_click', + persistent: false, + dynamic: false, + selectize: false, + opacityDim: 0.2, + selected: { + opacity: 1, + }, + debounce: 0, + }, + shinyEvents: ['plotly_hover', 'plotly_click', 'plotly_selected', 'plotly_relayout', 'plotly_brushed', 'plotly_brushing', 'plotly_clickannotation', 'plotly_doubleclick', 'plotly_deselect', 'plotly_afterplot', 'plotly_sunburstclick'], + base_url: 'https://plot.ly', + }; +}; diff --git a/histview2/static/analyze/js/hotelling_biplot.js b/histview2/static/analyze/js/hotelling_biplot.js new file mode 100644 index 0000000..d6b43a5 --- /dev/null +++ b/histview2/static/analyze/js/hotelling_biplot.js @@ -0,0 +1,48 @@ +/* eslint-disable no-undef */ +const drawPCABiplotChart = (json, chartConfig = {}, sizeOfData = null) => { + if (!json) return; + + const startTime = performance.now(); + + const figure = json; + figure.layout.autosize = true; + figure.layout.plot_bgcolor = '#222222'; + figure.layout.paper_bgcolor = '#222222'; + figure.layout.xaxis.gridcolor = '#444444'; + figure.layout.yaxis.gridcolor = '#444444'; + + figure.layout.legend = { + bgcolor: 'transparent', + bordercolor: 'transparent', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 11, + }, + xanchor: 'right', + tracegroupgap: 3, + }; + + delete figure.layout.yaxis.scaleanchor; + Plotly.newPlot('pcaBiplotChart', figure.data, figure.layout, { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }); + + // send plotting time + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; diff --git a/histview2/static/analyze/js/hotelling_common.js b/histview2/static/analyze/js/hotelling_common.js new file mode 100644 index 0000000..f9f1553 --- /dev/null +++ b/histview2/static/analyze/js/hotelling_common.js @@ -0,0 +1,567 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable guard-for-in */ +/* eslint-disable no-unused-vars */ + +// dont rename. must use formElements because some common functions +// are called from component.js. These functions use a formElements variable. +// TODO: fix this. +const formElements = { + NO_FILTER: 'NO_FILTER', +}; + +const eles = { + qContributionChart: '#qContributionChart', + t2ContributionChart: '#t2ContributionChart', + pcaBiplotChart: '#pcaBiplotChart', + recordInfoTable: '#dp-body', + formID: '#traceDataForm', + radioDefaultInterval: $('#radioDefaultInterval'), + radioRecentInterval: $('#radioRecentInterval'), + autoUpdateInterval: $('#autoUpdateInterval'), + traceTimeOptions: $('input:radio[name="traceTime"]'), + endProcItems: '#end-proc-row .end-proc', + endProcSelectedItem: '#end-proc-row select', + condProcReg: /cond_proc/g, + NO_FILTER: 'NO_FILTER', + msgModal: '#msgModal', + msgContent: '#msgContent', + msgConfirmBtn: '#msgConfirmBtn', + selectAll: '#selectAll', + recentTimeIntervalInput: 'input[name="recentTimeInterval"]', + btnAddCondProc: '#btn-add-cond-proc', + pcaConditionTbl: '#pcaConditionTbl', + spinner: '#spinner', + pcaConditionTblAllRows: '#pcaConditionTbl>tbody>tr', +}; + +const showLoading = (divElement = null) => { + if (!divElement) return; + + const currentContentHeight = divElement.height(); + const loadingHTML = ` +
+
+ Loading... +
+
`; + divElement.html(loadingHTML); +}; + +const hideLoading = (divElement = null) => { + if (!divElement) return; + divElement.html(''); +}; + + +// A template to visualize clicked data point +const clickedPointTemplate = (xVal, yVal) => ({ + x: [xVal], + y: [yVal], + text: [`xvar: ${xVal}
yvar: ${yVal}`], + type: 'scatter', + mode: 'markers', + marker: { + autocolorscale: false, + color: 'red', + opacity: 0.8, + size: 8, + symbol: 'square', + }, + hoveron: 'points', + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + isSelectedPoint: true, +}); + +// update Chart from x and y +const addDataPointFromXY = (elementId, orginalDataLen, x, y) => { + const clickedPointTrace = clickedPointTemplate(x, y); + if (clickedPointTrace) { + const chartElement = document.getElementById(elementId); + const dataLength = chartElement.data.length; + if (dataLength > orginalDataLen) { + Plotly.deleteTraces(elementId, dataLength - 1); + } + Plotly.addTraces(elementId, clickedPointTrace, chartElement.data.length); + } +}; + + +/* + This function is to update clicked data point to timeseries chart: Q & T2 + Args: + elementId: id of chart + dataPoint: data of clicked point got from click event + isStartingChart: to identify if chart in the elementId is origin of the click +*/ +const updateTimeSeries = (elementId = null, dataPoint = {}, isStartingChart = true) => { + // update Chart + const tsElement = document.getElementById(elementId); + const tsOriginalDataLength = 2; + if (isStartingChart) { + // use x, y directly if the chart originates the click + const { x, y } = dataPoint.points[0]; + addDataPointFromXY(elementId, tsOriginalDataLength, x, y); + } else if (!isStartingChart) { + // find x, y from sampleNo and clickedDataIndex if the chart doesn't originate the click + const { data } = tsElement; + const { sampleNo, clickedDataIndex } = dataPoint.points[0]; + + for (let idx = 0; idx < data.length; idx++) { + const trace = data[idx]; + if ((trace.name === 'test')) { // clicked point is in test data + const dataX = trace.x[clickedDataIndex]; + const dataY = trace.y[clickedDataIndex]; + addDataPointFromXY(elementId, tsOriginalDataLength, dataX, dataY); + break; + } + } + } +}; + +/* + This function is to update clicked data point to timeseries chart: Q & T2 + Args: + elementId: id of chart + dataPoint: data of clicked point got from click event + isStartingChart: to identify if chart in the elementId is origin of the click +*/ +const updateScatter = (elementId = null, dataPoint = {}, isStartingChart = true, jsonDtTest = {}) => { + // update Chart + const scatterElement = document.getElementById(elementId); + const scatterOrginalDataLength = 6; + if (isStartingChart) { + // use x, y directly if the chart originates the click + const { x, y } = dataPoint.points[0]; + addDataPointFromXY(elementId, scatterOrginalDataLength, x, y); + } else if (!isStartingChart) { + // find x, y from sampleNo and clickedDataIndex if the chart doesn't originate the click + const { data } = scatterElement; + const { sampleNo, clickedDataIndex } = dataPoint.points[0]; + + let isReplaced = false; + for (let idx = 0; idx < data.length; idx++) { + const trace = data[idx]; + if (trace.name === 'clickedPoint') { + const dataX = jsonDtTest.data[0].x[clickedDataIndex]; + const dataY = jsonDtTest.data[0].y[clickedDataIndex]; + addDataPointFromXY(elementId, scatterOrginalDataLength, dataX, dataY); + isReplaced = true; + break; + } + } + if (!isReplaced) { + const dataX = jsonDtTest.data[0].x[clickedDataIndex]; + const dataY = jsonDtTest.data[0].y[clickedDataIndex]; + addDataPointFromXY(elementId, scatterOrginalDataLength, dataX, dataY); + } + } +}; + +const updateRecordInfo = (dataInfos = {}, sampleNo = 0) => { + $('#spID').text(sampleNo); + const tableInfoEl = '#dp-body'; + const graphElement = $(tableInfoEl); + showLoading(graphElement); + let content = ` + + ${i18n.processName} + ${i18n.shownName} + ${i18n.value} + + `; + const re = /\d+\_[1,2]$/; + const reGetDate = /\d+\_[2]$/; + for (const dataInfo of dataInfos) { + let [proc, col_name, col_val, col_attr] = dataInfo; + let bgColorStyle = ''; + if (re.test(col_attr)) { + bgColorStyle = 'style="color:#56B1F7;"'; + } + if (reGetDate.test(col_attr)) { + col_val = moment(col_val).format(DATE_FORMAT_WITHOUT_TZ); + } + content += ` + < tr > + ${proc} + ${col_name} + ${col_val} + `; + } + hideLoading(graphElement); + $(tableInfoEl).html(content); +}; + +/* + Common function to broadcast click event. + dataPoint: data which is got from the click event + startingChart: id of chart which originates the click event +*/ +const broadcastClickEvent = (dataPoint, startingChart, jsonPCAScoreTest = {}) => { + // Update time series + updateTimeSeries(elementId = 'timeSeriesT2', dataPoint, startingChart === 'timeSeriesT2'); + updateTimeSeries(elementId = 'timeSeriesQ', dataPoint, startingChart === 'timeSeriesQ'); + + // Update Xtest scatter + updateScatter(elementId = 'xTest', dataPoint, startingChart === 'xTest', jsonDtTest = jsonPCAScoreTest); + + // Call backend to get jsons for Qcont + T2cont + BiPlot + record info + const formData = collectInputAsFormData(); + const { sampleNo } = dataPoint.points[0]; + formData.set('sampleNo', sampleNo); + getPCAPlotsFromBackend(formData, clickOnChart = true, sampleNo); + + // switch to record table + $('[href="#table-info"]').tab('show'); +}; + +const contributionChartLayout = (objData, type = 't2', sampleNo = null, + chartConfig = {}, shortName=null) => { + const getShortNameVar = (varName) => { + const shortKey = Object.keys(shortName).filter(keyName => keyName.includes(varName)) || null; + if (shortKey) { + return shortName[shortKey]; + } + return ''; +}; + const textVar = objData.Ratio.map((v, k) => getShortNameVar(objData.Var[k])).reverse(); + const layout = { + margin: { + t: 38.139200221392, + r: 6.6417600664176, + b: 36.086896360869, + l: 76.3802407638024, + }, + plot_bgcolor: '#222222', + paper_bgcolor: '#222222', + font: { + color: 'rgba(0,0,0,1)', + family: '', + size: 13.2835201328352, + }, + title: { + text: `${type.toUpperCase()} Contribution of Sample No. ${sampleNo || 1}`, + font: { + color: 'rgba(204,229,255,1)', + family: '', + size: 13.2835201328352, + }, + x: 0, + xref: 'paper', + }, + xaxis: { + domain: [ + 0, + 1, + ], + automargin: true, + type: 'linear', + autorange: false, + range: [ + -0.0486537209559121, + 1.05 * Math.max(...objData.Ratio.map(x => Math.abs(x))), + ], + tickmode: 'array', + categoryorder: 'array', + nticks: null, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -30, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: '#444444', + gridwidth: 0, + zeroline: false, + anchor: 'y', + title: { + text: type === 't2' ? '|Ratio|' : 'Ratio', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + hoverformat: '.2f', + }, + yaxis: { + domain: [ + 0, + 1, + ], + automargin: true, + type: 'linear', + autorange: true, + tickmode: 'array', + ticktext: textVar, + tickvals: textVar.map((v, i) => i + 1), + categoryorder: 'array', + categoryarray: textVar, + nticks: null, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: '#444444', + gridwidth: 0, + zeroline: false, + anchor: 'x', + title: { + text: 'Item Name (Variables)', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + hoverformat: '.2f', + }, + shapes: [ + { + type: 'rect', + fillcolor: null, + line: { + color: null, + width: 0, + linetype: [], + }, + yref: 'paper', + xref: 'paper', + x0: 0, + x1: 1, + y0: 0, + y1: 1, + }, + ], + showlegend: false, + legend: { + bgcolor: '#222222', + bordercolor: 'transparent', + borderwidth: 1.71796707229778, + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + }, + hovermode: 'closest', + barmode: 'relative', + autosize: true, + }; + return layout; +}; + +const genContributionChartData = (objData, type = 't2', dpInfo=null) => { + const colorScale = { + t2: [ + [0, '#51A5E1'], + [0.0526315789473684, '#4B9ACB'], + [0.105263157894737, '#458EB5'], + [0.157894736842105, '#3F83A0'], + [0.210526315789474, '#39788B'], + [0.263157894736842, '#336D77'], + [0.315789473684211, '#2C6263'], + [0.368421052631579, '#245850'], + [0.421052631578947, '#1C4D3D'], + [0.473684210526316, '#13432B'], + [0.526315789473684, '#195337'], + [0.578947368421053, '#1F6343'], + [0.631578947368421, '#267450'], + [0.684210526315789, '#2C865D'], + [0.736842105263158, '#33986A'], + [0.789473684210526, '#3AAA78'], + [0.842105263157895, '#40BD86'], + [0.894736842105263, '#48D094'], + [0.947368421052632, '#4FE3A2'], + [1, '#56F7B1'], + ], + q: [ + [0, '#132B43'], + [0.0526315789473684, '#16314B'], + [0.105263157894737, '#193754'], + [0.157894736842105, '#1D3E5C'], + [0.210526315789474, '#204465'], + [0.263157894736842, '#234B6E'], + [0.315789473684211, '#275277'], + [0.368421052631579, '#2A5980'], + [0.421052631578947, '#2E608A'], + [0.473684210526316, '#316793'], + [0.526315789473684, '#356E9D'], + [0.578947368421053, '#3875A6'], + [0.631578947368421, '#3C7CB0'], + [0.684210526315789, '#3F83BA'], + [0.736842105263158, '#438BC4'], + [0.789473684210526, '#4792CE'], + [0.842105263157895, '#4B9AD8'], + [0.894736842105263, '#4EA2E2'], + [0.947368421052632, '#52A9ED'], + [1, '#56B1F7'], + ], + }; + const convertRange = (ranger) => { + const X = Math.max(...ranger); + const M = Math.min(...ranger); + const absX = Math.abs(X); + const absM = Math.abs(M); + const t = colorScale[type].length - 1; + const binVolume = (x) => { + if (type === 't2') { + return 2 * x / t; + } + if (absX !== absM) { + return (absX - absM) / t; + } + return 2 * absX / t; + }; + return absM >= absX ? { + min: M, + max: -1 * M, + binVol: binVolume(absM), + } : { + min: type === 't2' ? -1 * X : absM, + max: type === 't2' ? X : absX, + binVol: binVolume(absX), + }; + }; + const cr = convertRange(objData.Ratio); + const markerColor = (i) => { + const r = cr.binVol !== 0 ? Math.round((i - cr.min) / cr.binVol) : 0; + return colorScale[type][r][1]; + }; + + const rRanger = () => { + const r = [cr.min, cr.max]; + const s = (cr.max - cr.min) / 4; + let subRange = [...Array(3).keys()].map(x => cr.min + ((x + 1) * s)); + subRange = subRange.concat(r); + const tVals = Array.from(new Set(subRange.sort((a, b) => a - b))); + const ratioConvertor = (ranger) => { + const minR = Math.min(...ranger); + const maxR = Math.max(...ranger); + const sR = (maxR - minR) / 100; + return ranger.map(i => 0.01 * (i - minR) / sR); + }; + return { + ticktext: tVals.map(x => x.toFixed(1)), + tickvals: ratioConvertor(tVals), + }; + }; + const getFullNameVar = (varName) => { + let distributionName = ''; + let procName = ''; + let colName = ''; + if (dpInfo) { + const rowInfo = dpInfo.filter(row => varName.toLowerCase() === row[1]); + if (rowInfo.length) { + [[procName, colName]] = rowInfo; + distributionName = `${procName}-${colName}
`; + } + } + return distributionName; + }; + const retData = objData.Ratio.map((v, k) => { + const varFullName = getFullNameVar(objData.Var[k]); + return { + orientation: 'h', + width: 0.9, + base: 0, + x: [ + Math.abs(v), + ], + y: [ + objData.Ratio.length - k, + ], + hovertext: `${varFullName}reorder(Var, abs(Ratio)): ${objData.Var[k]}
abs(Ratio): ${ + applySignificantDigit(Math.abs(v)) + }
Ratio: ${ + applySignificantDigit(v) + }`, + type: 'bar', + marker: { + autocolorscale: false, + color: markerColor(v), + line: { + width: 1.88976377952756, + color: 'transparent', + }, + }, + showlegend: false, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }; + }); + + const ratioChart = { + x: [ + 1, + ], + y: [ + 0, + ], + name: '99_d31a3926dd854cda0f79a49b4456c467', + type: 'scatter', + mode: 'markers', + opacity: 0, + hoverinfo: 'skip', + showlegend: false, + marker: { + color: [ + 0, + 1, + ], + colorscale: colorScale[type], + colorbar: { + bgcolor: 'transparent', + bordercolor: 'transparent', + borderwidth: 1.71796707229778, + thickness: 10, + title: 'Ratio', + titlefont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + tickmode: 'array', + ticktext: rRanger().ticktext, + tickvals: rRanger().tickvals, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + ticklen: 2, + len: 0.5, + }, + }, + xaxis: 'x', + yaxis: 'y', + frame: null, + }; + retData.push(ratioChart); + return retData; +}; diff --git a/histview2/static/analyze/js/hotelling_q_contribution.js b/histview2/static/analyze/js/hotelling_q_contribution.js new file mode 100644 index 0000000..55f7523 --- /dev/null +++ b/histview2/static/analyze/js/hotelling_q_contribution.js @@ -0,0 +1,81 @@ +/* eslint-disable no-undef */ +const drawQContributionChart = (json, chartConfig = {}, sizeOfData = null) => { + if (!json) return; + + const startTime = performance.now(); + + const figure = json; + figure.layout.autosize = true; + figure.layout.plot_bgcolor = '#222222'; + figure.layout.paper_bgcolor = '#222222'; + figure.layout.xaxis.gridcolor = '#444444'; + figure.layout.yaxis.gridcolor = '#444444'; + figure.layout.xaxis.tickangle = -30; + figure.layout.legend = { + bgcolor: '#222222', + }; + delete figure.layout.yaxis.scaleanchor; + + const len = figure.data.length; + if (getNode(figure.data[len - 1], ['marker', 'colorbar'])) { + figure.data[len - 1].marker.colorbar.bgcolor = 'transparent'; + figure.data[len - 1].marker.colorbar.thickness = 10; + } + + // const tickTexts = figure.layout.yaxis.ticktext; + // figure.layout.yaxis.ticktext = tickTexts.map(v => (v.length > 10 ? `${v.substring(0, 9)}...` : v)); + Plotly.newPlot('qContributionChart', figure.data, figure.layout, { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }); + + + // send plotting time event + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; + +const drawQContributionChartFromObj = (objData, sampleNo = null, chartConfig = {}, + sizeOfData = null, dpInfo=null, + shortName= null) => { + if (!objData) return; + const startTime = performance.now(); + + Plotly.newPlot('qContributionChart', + genContributionChartData(objData, 'q', dpInfo), + contributionChartLayout(objData, 'q', sampleNo, chartConfig, shortName), { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }); + + + // send plotting time event + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; diff --git a/histview2/static/analyze/js/hotelling_scatters.js b/histview2/static/analyze/js/hotelling_scatters.js new file mode 100644 index 0000000..efed835 --- /dev/null +++ b/histview2/static/analyze/js/hotelling_scatters.js @@ -0,0 +1,136 @@ +/* eslint-disable no-undef */ +/* eslint-disable no-unused-vars */ + +const drawXTrainScatter = (json, chartConfig = {}, sizeOfData = null) => { + if (!json) return; + + const startTime = performance.now(); + + const figure = json; + + // customize jsonDtTrain + figure.data[0].marker.color = 'white'; + figure.layout.plot_bgcolor = '#222222'; + figure.layout.paper_bgcolor = '#222222'; + figure.layout.legend = { + bgcolor: 'transparent', + bordercolor: 'transparent', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 11, + }, + xanchor: 'right', + tracegroupgap: 3, + }; + figure.layout.autosize = true; + figure.layout.xaxis.gridcolor = '#444444'; + figure.layout.yaxis.gridcolor = '#444444'; + figure.layout.xaxis.title.font = { color: 'rgba(255,255,255,1)', family: '', size: 14 }; + figure.layout.yaxis.title.font = { color: 'rgba(255,255,255,1)', family: '', size: 14 }; + figure.layout.xaxis.tickfont.color = 'rgba(255,255,255,1)'; + figure.layout.yaxis.tickfont.color = 'rgba(255,255,255,1)'; + figure.layout.xaxis.autorange = true; + figure.layout.yaxis.autorange = true; + figure.layout.annotations[0].font.color = 'rgba(255,255,255,1)'; + delete figure.layout.yaxis.scaleanchor; + // plot config + const plotConfig = { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }; + + // plot xtrain data + Plotly.newPlot('xTrain', figure.data, figure.layout, plotConfig); + + // send plotting time + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; + +const drawXTestScatter = (json, chartConfig = {}, sizeOfData = null) => { + if (!json) return; + + const startTime = performance.now(); + + const figure = json; + + // customize jsonDtTest + figure.layout.plot_bgcolor = '#222222'; + figure.layout.paper_bgcolor = '#222222'; + figure.layout.legend = { + bgcolor: 'transparent', + bordercolor: 'transparent', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 11, + }, + y: -0.1, + tracegroupgap: 3, + orientation: 'h', + }; + delete figure.layout.yaxis.scaleanchor; + figure.layout.xaxis.gridcolor = '#444444'; + figure.layout.yaxis.gridcolor = '#444444'; + delete figure.layout.xaxis.title; + delete figure.layout.yaxis.title; + figure.layout.xaxis.autorange = true; + figure.layout.yaxis.autorange = true; + figure.layout.autosize = true; + // figure.layout.annotations[0].font.color = 'rgba(255,255,255,1)'; + figure.layout.xaxis.tickfont.color = 'rgba(255,255,255,1)'; + figure.layout.yaxis.tickfont.color = 'rgba(255,255,255,1)'; + + // plot config + const plotConfig = { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }; + + // plot xtest data + const xTestElement = document.getElementById('xTest'); + + Plotly.newPlot('xTest', figure.data, figure.layout, plotConfig); + + // xtest click event + xTestElement.on('plotly_click', (dataPoint) => { + // to spread sampleNo to other charts + const { pointIndex } = dataPoint.points[0]; + dataPoint.points[0].sampleNo = pointIndex + 1; + dataPoint.points[0].clickedDataIndex = pointIndex; + + // to broadcast click event to other charts + broadcastClickEvent(dataPoint, startingChart = 'xTest', jsonPCAScoreTest = json); + }); + + // send plotting time + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; diff --git a/histview2/static/analyze/js/hotelling_t2_contribution.js b/histview2/static/analyze/js/hotelling_t2_contribution.js new file mode 100644 index 0000000..d17b4e2 --- /dev/null +++ b/histview2/static/analyze/js/hotelling_t2_contribution.js @@ -0,0 +1,78 @@ +/* eslint-disable no-undef */ +const drawT2ContributionChart = (json, chartConfig = {}, sizeOfData = null) => { + if (!json) return; + + const startTime = performance.now(); + + const figure = json; + + figure.layout.plot_bgcolor = '#222222'; + figure.layout.paper_bgcolor = '#222222'; + figure.layout.xaxis.gridcolor = '#444444'; + figure.layout.yaxis.gridcolor = '#444444'; + figure.layout.xaxis.tickangle = -30; + figure.layout.autosize = true; + figure.layout.legend = { + bgcolor: '#222222', + }; + delete figure.layout.yaxis.scaleanchor; + + const len = figure.data.length; + if (getNode(figure.data[len - 1], ['marker', 'colorbar'])) { + figure.data[len - 1].marker.colorbar.bgcolor = 'transparent'; + figure.data[len - 1].marker.colorbar.thickness = 10; + } + + Plotly.newPlot('t2ContributionChart', figure.data, figure.layout, { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }); + + + // send plotting time event + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; + +const drawT2ContributionChartFromObj = (objData, sampleNo = null, chartConfig = {}, + sizeOfData = null, dpInfo=null, + shortName = null) => { + if (!objData) return; + const startTime = performance.now(); + + Plotly.newPlot('t2ContributionChart', + genContributionChartData(objData, 't2', dpInfo), + contributionChartLayout(objData, 't2', sampleNo, chartConfig, shortName), { + responsive: true, + ...genPlotlyIconSettings(), + }); + + + // send plotting time event + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; diff --git a/histview2/static/analyze/js/hotelling_timeseries.js b/histview2/static/analyze/js/hotelling_timeseries.js new file mode 100644 index 0000000..704e418 --- /dev/null +++ b/histview2/static/analyze/js/hotelling_timeseries.js @@ -0,0 +1,587 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-undef */ + +const drawShapes = (x = null, display = true) => { + const plots = document.querySelectorAll('#timeSeriesT2, #timeSeriesQ'); + const layoutUpdates = {}; + if (display) { + layoutUpdates.shapes = [ + { + type: 'line', + yref: 'paper', + x0: x, + y0: -1, + x1: x, + y1: 1, + line: { + color: 'rgb(255, 0, 0)', + width: 1, + dash: 'solid', + }, + layer: 'above', + }, + ]; + } else { + layoutUpdates.shapes = []; + } + plots.forEach((plot) => { + Plotly.update(plot, {}, layoutUpdates); + }); +}; + +const drawTimeSeriesT2Chart = (json, jsonDtTest = {}, chartConfig = {}, sizeOfData = null) => { + if (!json) return; + + const startTime = performance.now(); + + const figure = json; + + figure.layout.plot_bgcolor = '#222222'; + figure.layout.paper_bgcolor = '#222222'; + figure.layout.xaxis.gridcolor = '#444444'; + figure.layout.yaxis.gridcolor = '#444444'; + figure.layout.legend = { + bgcolor: 'transparent', + bordercolor: 'transparent', + borderwidth: 0.1, + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 12, + }, + y: 0.95, + xanchor: 'left', + orientation: 'h', + traceorder: 'reversed', + }; + figure.layout.autosize = true; + delete figure.layout.yaxis.scaleanchor; + // plot config + const plotConfig = { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }; + + + const timeSeriesT2Element = document.getElementById('timeSeriesT2'); + + Plotly.newPlot('timeSeriesT2', figure.data, figure.layout, plotConfig); + + // xtest click event + timeSeriesT2Element.on('plotly_click', (dataPoint) => { + // to spread sampleNo and clickedDataIndexto other charts + const sampleNo = dataPoint.points[0].x; + dataPoint.points[0].sampleNo = sampleNo; + + if (sampleNo > 0) { + // clicked on Xtest + dataPoint.points[0].clickedDataIndex = sampleNo - 1; + } else { + return; // support test data only + // dataPoint.points[0].clickedDataIndex = sampleNo - 1; + } + + // to broadcast click event to other charts + broadcastClickEvent(dataPoint, startingChart = 'timeSeriesT2', jsonPCAScoreTest = jsonDtTest); + }); + + // Add hover event for t2 & q plots + timeSeriesT2Element.on('plotly_hover', (data) => { + drawShapes(data.points[0].x); + }).on('plotly_unhover', (data) => { + drawShapes(null, false); + }); + // send plotting time + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; + +const drawTimeSeriesQChart = (json, jsonDtTest = {}, chartConfig = {}, sizeOfData = null) => { + if (!json) return; + + const startTime = performance.now(); + + const figure = json; + + figure.layout.plot_bgcolor = '#222222'; + figure.layout.paper_bgcolor = '#222222'; + figure.layout.xaxis.gridcolor = '#444444'; + figure.layout.yaxis.gridcolor = '#444444'; + figure.layout.yaxis.tickmode = 'linear'; + figure.layout.legend = { + bgcolor: 'transparent', + bordercolor: 'transparent', + borderwidth: 0.1, + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 12, + }, + y: 0.95, + xanchor: 'left', + orientation: 'h', + traceorder: 'reversed', + }; + figure.layout.autosize = true; + delete figure.layout.yaxis.scaleanchor; + // plot config + const plotConfig = { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }; + + const timeSeriesQElement = document.getElementById('timeSeriesQ'); + + Plotly.newPlot('timeSeriesQ', figure.data, figure.layout, plotConfig); + + // xtest click event + timeSeriesQElement.on('plotly_click', (dataPoint) => { + // to spread sampleNo and clickedDataIndexto other charts + const sampleNo = dataPoint.points[0].x; + dataPoint.points[0].sampleNo = sampleNo; + + if (sampleNo > 0) { + // clicked on Xtest + dataPoint.points[0].clickedDataIndex = sampleNo - 1; + } else { + return; // support test data only + // dataPoint.points[0].clickedDataIndex = sampleNo - 1; + } + + // to broadcast click event to other charts + broadcastClickEvent(dataPoint, startingChart = 'timeSeriesQ', jsonPCAScoreTest = jsonDtTest); + }); + // Add hover event for t2 & q plots + timeSeriesQElement.on('plotly_hover', (data) => { + drawShapes(data.points[0].x); + }).on('plotly_unhover', (data) => { + drawShapes(null, false); + }); + // send plotting time + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; + +const genTimeSeriesData = (data, type = 'test', label = 'T2_statics') => { + retData = { + x: [], + y: [], + text: [], + type: 'scatter', + mode: 'lines', + line: { + width: 1.88976377952756, + color: '', + dash: 'solid', + }, + hoveron: 'points', + name: type, + legendgroup: type, + showlegend: true, + xaxis: 'x', + yaxis: 'y', + hoverinfo: 'text', + frame: null, + }; + retData.text = data.map((v, k) => { + const n = (type === 'test') ? (k + 1) : (k + 1 - data.length); + retData.line.color = (type === 'test') ? 'rgba(255,165,0,1)' : 'rgba(255,255,255,1)'; + retData.x.push(n); + retData.y.push(v); + return `sample_no: ${n}
${label}: ${v}`; + }); + return retData; +}; + +const drawTimeSeriesT2ChartFromObj = (objData, jsonDtTest = {}, chartConfig = {}, sizeOfData = null) => { + if (!objData) return; + + // const chartConfig.width = $('#xTest').width(); + const startTime = performance.now(); + + const t2TrainTimeSeries = genTimeSeriesData(objData.train, 'train'); + const t2TestTimeSeries = genTimeSeriesData(objData.test); + const t2TimeSeriesData = [t2TrainTimeSeries, t2TestTimeSeries]; + const t2TimeSeriesLayout = { + margin: { + t: 24.8556800885568, + r: 6.6417600664176, + b: 36.086896360869, + l: 28.5595682855957, + }, + plot_bgcolor: '#222222', + paper_bgcolor: '#222222', + font: { + color: 'rgba(0,0,0,1)', + family: '', + size: 13.2835201328352, + }, + xaxis: { + domain: [ + 0, + 1, + ], + automargin: true, + type: 'linear', + autorange: true, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: '#444444', + gridwidth: 0, + zeroline: false, + anchor: 'y', + title: { + text: 'Sample No.', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + hoverformat: '.2f', + }, + yaxis: { + domain: [ + 0, + 1, + ], + automargin: true, + type: 'linear', + autorange: true, + tickmode: 'auto', + nticks: 5, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: '#444444', + gridwidth: 0, + zeroline: false, + anchor: 'x', + title: { + text: 'T2 Statics', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + hoverformat: '.2f', + }, + shapes: [ + { + type: 'rect', + fillcolor: null, + line: { + color: null, + width: 0, + linetype: [], + }, + yref: 'paper', + xref: 'paper', + x0: 0, + x1: 1, + y0: 0, + y1: 1, + }, + ], + showlegend: true, + legend: { + bgcolor: 'transparent', + bordercolor: 'transparent', + borderwidth: 0.1, + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + x: 0, + xanchor: 'left', + y: 1, + }, + hovermode: 'closest', + barmode: 'relative', + autosize: true, + }; + const plotConfig = { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }; + const timeSeriesT2Element = document.getElementById('timeSeriesT2'); + + Plotly.newPlot(timeSeriesT2Element, t2TimeSeriesData, t2TimeSeriesLayout, plotConfig); + + // xtest click event + timeSeriesT2Element.on('plotly_click', (dataPoint) => { + // to spread sampleNo and clickedDataIndexto other charts + const sampleNo = dataPoint.points[0].x; + dataPoint.points[0].sampleNo = sampleNo; + + if (sampleNo > 0) { + // clicked on Xtest + dataPoint.points[0].clickedDataIndex = sampleNo - 1; + } else { + return; // support test data only + // dataPoint.points[0].clickedDataIndex = sampleNo - 1; + } + + // to broadcast click event to other charts + broadcastClickEvent(dataPoint, startingChart = 'timeSeriesT2', jsonPCAScoreTest = jsonDtTest); + }); + + // Add hover event for t2 & q plots + timeSeriesT2Element.on('plotly_hover', (data) => { + drawShapes(data.points[0].x); + }).on('plotly_unhover', (data) => { + drawShapes(null, false); + }); + // send plotting time + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; + +const drawTimeSeriesQChartFromObj = (objData, jsonDtTest = {}, chartConfig = {}, sizeOfData = null) => { + if (!objData) return; + + const startTime = performance.now(); + + const qTrainTimeSeries = genTimeSeriesData(objData.SPE, 'train', 'Q_statics'); + const qTestTimeSeries = genTimeSeriesData(objData.test, 'test', 'Q_statics'); + const qTimeSeriesData = [qTrainTimeSeries, qTestTimeSeries]; + const qTimeSeriesLayout = { + margin: { + t: 24.8556800885568, + r: 6.6417600664176, + b: 36.086896360869, + l: 28.5595682855957, + }, + plot_bgcolor: '#222222', + paper_bgcolor: '#222222', + font: { + color: 'rgba(0,0,0,1)', + family: '', + size: 13.2835201328352, + }, + xaxis: { + domain: [ + 0, + 1, + ], + automargin: true, + type: 'linear', + autorange: true, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: '#444444', + gridwidth: 0, + zeroline: false, + anchor: 'y', + title: { + text: 'Sample No.', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + hoverformat: '.2f', + }, + yaxis: { + domain: [ + 0, + 1, + ], + automargin: true, + type: 'linear', + autorange: true, + tickmode: 'auto', + nticks: 5, + ticks: 'outside', + tickcolor: 'rgba(51,51,51,1)', + ticklen: 3.3208800332088, + tickwidth: 0.301898184837164, + showticklabels: true, + tickfont: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + tickangle: -0, + showline: false, + linecolor: null, + linewidth: 0, + showgrid: false, + gridcolor: '#444444', + gridwidth: 0, + zeroline: false, + anchor: 'x', + title: { + text: 'Q Statics', + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 13.2835201328352, + }, + }, + hoverformat: '.2f', + }, + shapes: [ + { + type: 'rect', + fillcolor: null, + line: { + color: null, + width: 0, + linetype: [], + }, + yref: 'paper', + xref: 'paper', + x0: 0, + x1: 1, + y0: 0, + y1: 1, + }, + ], + showlegend: true, + legend: { + bgcolor: 'transparent', + bordercolor: 'transparent', + borderwidth: 0.1, + font: { + color: 'rgba(255,255,255,1)', + family: '', + size: 10.6268161062682, + }, + x: 0, + xanchor: 'left', + y: 1, + }, + hovermode: 'closest', + barmode: 'relative', + autosize: true, + }; + const plotConfig = { + ...genPlotlyIconSettings(), + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }; + const timeSeriesQElement = document.getElementById('timeSeriesQ'); + + Plotly.newPlot(timeSeriesQElement, qTimeSeriesData, qTimeSeriesLayout, plotConfig); + + // xtest click event + timeSeriesQElement.on('plotly_click', (dataPoint) => { + // to spread sampleNo and clickedDataIndexto other charts + const sampleNo = dataPoint.points[0].x; + dataPoint.points[0].sampleNo = sampleNo; + + if (sampleNo > 0) { + // clicked on Xtest + dataPoint.points[0].clickedDataIndex = sampleNo - 1; + } else { + return; // support test data only + // dataPoint.points[0].clickedDataIndex = sampleNo - 1; + } + + // to broadcast click event to other charts + broadcastClickEvent(dataPoint, startingChart = 'timeSeriesQ', jsonPCAScoreTest = jsonDtTest); + }); + + // Add hover event for t2 & q plots + timeSeriesQElement.on('plotly_hover', (data) => { + drawShapes(data.points[0].x); + }).on('plotly_unhover', (data) => { + drawShapes(null, false); + }); + // send plotting time + const endTime = performance.now(); + gtag('event', 'PCA_et', { + event_category: 'ExecTime', + event_label: 'JsPlot', + value: endTime - startTime, + }); + + // send data size + gtag('event', 'PCA_ds', { + event_category: 'InputData', + event_label: 'JsPlot', + value: sizeOfData, + }); +}; diff --git a/histview2/static/analyze/js/pca.js b/histview2/static/analyze/js/pca.js new file mode 100644 index 0000000..bd3e095 --- /dev/null +++ b/histview2/static/analyze/js/pca.js @@ -0,0 +1,612 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-use-before-define */ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-undef */ +const REQUEST_TIMEOUT = setRequestTimeOut(60000); // 1 minutes + +const i18n = { + allSelection: $('#i18nAllSelection').text(), + noFilter: $('#i18nNoFilter').text(), + machineNo: $('#i18nMachineNo').text(), + partNo: $('#i18nPartNo').text(), + error: $('#i18nError').text(), + confirmQuestion: $('#i18nConfirmQuestion').text(), + processName: $('#i18nProcessName').text(), + shownName: $('#i18nShownName').text(), + value: $('#i18nValue').text(), + warning: $('#i18nWarning').text(), + gaUnable: $('#i18nGAUnable').text(), + gaCheckConnect: $('#i18nGACheckConnect').text(), + warningTitle: $('#i18nWarningTitle').text(), + traceResulLimited: $('#i18nTraceResultLimited').text() || '', + SQLLimit: $('#i18nSQLLimit').text(), + notEnoughSensor: $('#i18nNotEnoughSensor').text(), + trainingData: $('#i18nTrainingData').text(), + testingData: $('#i18nTestingData').text(), +}; + +const MAX_END_PROC = 60; + +const MSG_MAPPING = { + E_ALL_NA: $('#i18nE01AllNA').text(), + E_PCA_NON_NUMERIC: $('#i18nE02NonNumeric').text(), + E_ZERO_VARIANCE: $('#i18nE03ZeroVariance').text(), + W_PCA_INTEGER: $('#i18nW01IntegerData').text(), +}; + +const CONST = { + dateDiffTrain: 0, + dateDiffTest: 0, +}; + +// formData is FormData object +const chooseTraceTimeIntervalPCA = (formData) => { + const START_DATE = 'START_DATE'; + const START_TIME = 'START_TIME'; + const END_DATE = 'END_DATE'; + const END_TIME = 'END_TIME'; + if (!formData) return formData; + + const traceTimeOption = formData.get('traceTime'); + const recentTimeIntervals = formData.get('recentTimeInterval'); // in minute + + // unit is multiply of minute: minute=1, hour=60, day=1440, week=10080 + const timeUnit = formData.get('timeUnit'); + + // get all datetime + const startDates = [...formData.getAll(START_DATE)]; + const startTimes = [...formData.getAll(START_TIME)]; + const endDates = [...formData.getAll(END_DATE)]; + const endTimes = [...formData.getAll(END_TIME)]; + + // clear all datetimes before customize + [START_DATE, START_TIME, END_DATE, END_TIME].forEach(e => formData.delete(e)); + + formData.append(START_DATE, startDates[0]); + formData.append(START_TIME, startTimes[0]); + formData.append(END_DATE, endDates[0]); + formData.append(END_TIME, endTimes[0]); + + if (traceTimeOption === TRACE_TIME_CONST.RECENT) { + const timeDiffMinute = Number(recentTimeIntervals) * Number(timeUnit); + const newStartDate = moment().add(-timeDiffMinute, 'minute').format(DATE_FORMAT); + const newStartTime = moment().add(-timeDiffMinute, 'minute').format(TIME_FORMAT); + const newEndDate = moment().format(DATE_FORMAT); + const newEndTime = moment().format(TIME_FORMAT); + + formData.append(START_DATE, newStartDate); + formData.append(START_TIME, newStartTime); + formData.append(END_DATE, newEndDate); + formData.append(END_TIME, newEndTime); + } else { + formData.append(START_DATE, startDates[1]); + formData.append(START_TIME, startTimes[1]); + formData.append(END_DATE, endDates[1]); + formData.append(END_TIME, endTimes[1]); + } + + // delete empty conditional procs + [...formData.keys()].forEach((key) => { + if (key.startsWith('cond_proc') && isEmpty(formData.get(key))) { + formData.delete(key); + } + }); + + return formData; +}; + +const drawPCAPlotJSON = (res, clickOnChart) => { + const chartConfig = { + autosize: true, + }; + + const jsonPCABiplot = JSON.parse(res.json_pca_biplot); + const jsonT2TimeSeries = JSON.parse(res.json_t2_time_series); + const jsonQTimeSeries = JSON.parse(res.json_q_time_series); + const jsonPCAScoreTrain = JSON.parse(res.json_pca_score_train); + const jsonPCAScoreTest = JSON.parse(res.json_pca_score_test); + const jsonQContribution = JSON.parse(res.json_q_contribution); + const jsonT2Contribution = JSON.parse(res.json_t2_contribution); + + // draw graphs + if (!clickOnChart) { + if (jsonPCAScoreTrain) { + drawXTrainScatter(jsonPCAScoreTrain, chartConfig, sizeOfData = res.dtsize_pca_score_train); + } + if (jsonPCAScoreTest) { + drawXTestScatter(jsonPCAScoreTest, chartConfig, sizeOfData = res.dtsize_pca_score_test); + if (jsonT2TimeSeries) { + drawTimeSeriesT2Chart(jsonT2TimeSeries, jsonPCAScoreTest, chartConfig, + sizeOfData = res.dtsize_t2_time_series); + } + if (jsonQTimeSeries) { + drawTimeSeriesQChart(jsonQTimeSeries, jsonPCAScoreTest, chartConfig, + sizeOfData = res.dtsize_q_time_series); + } + } + } + if (jsonQContribution) { + drawQContributionChart(jsonQContribution, chartConfig, sizeOfData = res.dtsize_q_contribution); + } + if (jsonT2Contribution) { + drawT2ContributionChart(jsonT2Contribution, chartConfig, + sizeOfData = res.dtsize_t2_contribution); + } + if (jsonPCABiplot) { + drawPCABiplotChart(jsonPCABiplot, chartConfig, + sizeOfData = res.dtsize_pca_biplot); + } +}; + +const drawPCAPlotList = (res, clickOnChart, sampleNo = null) => { + const chartConfig = { + autosize: true, + }; + + const jsonPCAScoreTrain = generateXTrainScatter(res.json_pca_score_train); + if (jsonPCAScoreTrain && !clickOnChart) { + drawXTrainScatter(jsonPCAScoreTrain, chartConfig, sizeOfData = res.dtsize_pca_score_train); + } + + const jsonPCAScoreTest = generateXTestScatter(res.json_pca_score_test, res.json_pca_score_train); + if (jsonPCAScoreTest && !clickOnChart) { + drawXTestScatter(jsonPCAScoreTest, chartConfig, sizeOfData = res.dtsize_pca_score_test); + } + + const jsonPCABiplot = generateBiplot(res.json_pca_biplot, res.json_pca_score_train, sampleNo); + if (jsonPCABiplot) { drawPCABiplotChart(jsonPCABiplot, chartConfig, sizeOfData = res.dtsize_pca_biplot); } + + const jsonT2TimeSeries = res.json_t2_time_series; + const jsonQTimeSeries = res.json_q_time_series; + const jsonQContribution = res.json_q_contribution; + const jsonT2Contribution = res.json_t2_contribution; + + if (jsonT2TimeSeries && !clickOnChart) { + drawTimeSeriesT2ChartFromObj(jsonT2TimeSeries, jsonPCAScoreTest, chartConfig, + sizeOfData = res.dtsize_t2_time_series); + } + if (jsonQTimeSeries && !clickOnChart) { + drawTimeSeriesQChartFromObj(jsonQTimeSeries, jsonPCAScoreTest, chartConfig, + sizeOfData = res.dtsize_q_time_series); + } + if (jsonQContribution) { + drawQContributionChartFromObj(jsonQContribution, sampleNo, chartConfig, + sizeOfData = res.dtsize_q_contribution, dpInfo = res.data_point_info, + shortName = res.short_names); + } + if (jsonT2Contribution) { + drawT2ContributionChartFromObj(jsonT2Contribution, sampleNo, chartConfig, + sizeOfData = res.dtsize_t2_contribution, dpInfo = res.data_point_info, + shortName = res.short_names); + } +}; + + +const checkValidNumSelectedSensors = (formData) => { + const MAX_NUM = 60; + const MIN_NUM = 1; + const selectedSensors = [...formData.keys()].filter(e => e.match(/GET02_VALS_SELECT.+/)); + const numCheckedSensors = selectedSensors.length; + return !(numCheckedSensors > MAX_NUM || numCheckedSensors < MIN_NUM); +}; + +const getPCAPlotsFromBackend = (formData, clickOnChart = false, sampleNo = null) => { + loadingShow(); + + const eleQCont = $(eles.qContributionChart); + const eleT2Cont = $(eles.t2ContributionChart); + const eleBiplot = $(eles.pcaBiplotChart); + const eleRecordInfoTbl = $(eles.recordInfoTable); + + if (!checkValidNumSelectedSensors(formData)) { + loadingHide(); + showTooManySensorToastr(60); + return; + } + + if (clickOnChart) { + showLoading(eleQCont); + showLoading(eleT2Cont); + showLoading(eleBiplot); + showLoading(eleRecordInfoTbl); + } else { + // loading.toggleClass('hide'); + } + + // Synchronize datetime of query with UI date-range + syncTraceDateTime( + parentId = 'testingDC', + dtNames = { + START_DATE: 'START_DATE', + START_TIME: 'START_TIME', + END_DATE: 'END_DATE', + END_TIME: 'END_TIME', + }, + dtValues = { + START_DATE: formData.getAll('START_DATE')[1], + START_TIME: formData.getAll('START_TIME')[1], + END_DATE: formData.getAll('END_DATE')[1], + END_TIME: formData.getAll('END_TIME')[1], + }, + ); + + // convert to UTC datetime to query + formData = convertFormDateTimeToUTC(formData); + + $.ajax({ + url: '/histview2/api/analyze/pca', + data: formData, + dataType: 'json', + type: 'POST', + contentType: false, + processData: false, + timeout: REQUEST_TIMEOUT, + success: (res) => { + const t0 = performance.now(); + loadingShow(true); + + if (clickOnChart) { + hideLoading(eleQCont); + hideLoading(eleT2Cont); + hideLoading(eleBiplot); + hideLoading(eleRecordInfoTbl); + } else { + $('#plot-cards').show(); + } + + const json = false; + if (json) { + drawPCAPlotJSON(res, clickOnChart); + } else { + drawPCAPlotList(res, clickOnChart, sampleNo); + } + + const t1 = performance.now(); + // show processing time at bottom + drawProcessingTime(t0, t1, res.backend_time, res.actual_record_number_test); + + // show delete number of NAN records + // const numSensors = countSelectedSensors(formData) || 1; + // if (res.removed_outlier_nan_train) { + // showToastrDeleteNA( + // i18n.trainingData, + // numSensors * res.actual_record_number_train, + // res.removed_outlier_nan_train, + // ); + // } + // if (res.removed_outlier_nan_test) { + // showToastrDeleteNA(i18n.testingData, + // numSensors * res.actual_record_number_test, + // res.removed_outlier_nan_test); + // } + if (!sampleNo) { + showAllDeleteNAToastrMsgs(res, formData); + } + + if (res.is_send_ga_off) { + showGAToastr(true); + } + + if (res.actual_record_number_train > SQL_LIMIT || res.actual_record_number_test > SQL_LIMIT) { + showToastrMsg(i18n.SQLLimit, i18n.warningTitle); + } + + // show toastr to inform result was truncated upto 5000 + if (res.is_res_limited_train || res.is_res_limited_test) { + showToastrMsg(i18n.traceResulLimited.split('BREAK_LINE').join('
'), i18n.warningTitle); + } + + // update record table info + const jsonDataPointInfo = res.data_point_info; + if (jsonDataPointInfo) { + updateRecordInfo(dataInfos = jsonDataPointInfo, sampleNo = formData.get('sample_no')); + } + + // move invalid filter + setColorAndSortHtmlEle(res.matched_filter_ids, res.unmatched_filter_ids, res.not_exact_match_filter_ids); + // if (checkResultExist(res)) { + // saveInvalidFilterCaller(true); + // } else { + // saveInvalidFilterCaller(); + // } + + // hide loading inside ajax + setTimeout(loadingHide, loadingHideDelayTime(res.actual_record_number)); + + $('html, body').animate({ + scrollTop: $('#plot-cards').offset().top, + }, 1000); + + // export mode + handleZipExport(res); + }, + error: (res) => { + loadingHide(); + + // export mode + handleZipExport(res); + + if (clickOnChart) { + hideLoading(eleQCont); + hideLoading(eleT2Cont); + hideLoading(eleBiplot); + hideLoading(eleRecordInfoTbl); + } else { + // loading.toggleClass('hide'); + loadingHide(); + } + + if (res.responseJSON) { + const resJson = JSON.parse(res.responseJSON) || {}; + showToastr(resJson.json_errors); + return; + } + + errorHandling(res); + }, + }).then(() => { + loadingHide(); + }); +}; + +const isIntegerDatatype = (type) => { + const NUMERIC_TYPE = ['int', 'long']; + if (!type) return false; + // convert to lower case before compare + const lowerType = type.toLowerCase(); + for (let i = 0; i < NUMERIC_TYPE.length; i++) { + if (lowerType.includes(NUMERIC_TYPE[i])) { return true; } + } + return false; +}; + +const countSelectedSensors = (formData) => { + let count = 0; + for (const pair of formData.entries()) { + const key = pair[0]; + if (key.startsWith('GET02_VALS_SELECT')) { + count += 1; + } + } + return count; +}; + +const createSensorRow = (sensor) => { + let toolTip = ''; + for (const tip of sensor.tooltip) { + if (tip.pos < 10) { + toolTip = toolTip.concat(`${tip.pos}  `); + } else if (tip.pos < 100) { + toolTip = toolTip.concat(`${tip.pos} `); + } else { + toolTip = toolTip.concat(`${tip.pos}`); + } + if (tip.pos !== 0) { + toolTip = toolTip.concat(`  ${tip.val}`); + } + toolTip = toolTip.concat('
'); + } + return { + 0: ` + + +
+ + +
`, + 1: `${sensor.proc_name}`, + 2: `${sensor.col_name}`, + 3: `${sensor.col_type}`, + 4: `${sensor.sample}${toolTip}`, + }; +}; + +const collectInputAsFormData = () => { + const traceForm = $(eles.formID); + let formData = new FormData(traceForm[0]); + formData = chooseTraceTimeIntervalPCA(formData); + + let isIntegerColChecked = false; + let valCount = 0; + + for (const [key, value] of formData.entries()) { + if (/GET02_VALS_SELECT/.test(key)) { + valCount += 1; + if ($(`#dataType-${value}`).val().toLowerCase() === 'int') { + isIntegerColChecked = true; + } + } + } + + formData.set('has_integer_col', isIntegerColChecked); + formData.set('checked_val_count', valCount); + + return formData; +}; + +const getPCAPlots = () => { + const isValid = checkValidations({ max: MAX_END_PROC }); + updateStyleOfInvalidElements(); + + if (isValid) { + // close sidebar + beforeShowGraphCommon(); + + const formData = collectInputAsFormData(); + + // validate one sensor + if (Number(formData.get('checked_val_count')) < 2) { + showToastrMsg(i18n.notEnoughSensor, i18n.error, MESSAGE_LEVEL.ERROR); + return; + } + + // warning about integer column has_integer_col + if (formData.get('has_integer_col') === 'true') { + $(eles.msgContent).text(`${MSG_MAPPING.W_PCA_INTEGER}\n${i18n.confirmQuestion}`); + $(eles.msgModal).modal('show'); + } else { + getPCAPlotsFromBackend(formData); + } + } +}; + +const confirmWarningAndGetPCA = () => { + $(eles.msgModal).modal('hide'); + + const formData = collectInputAsFormData(); + getPCAPlotsFromBackend(formData); +}; + + +const bindCheckEvents = () => { + // check all event + $(eles.selectAll).change(() => { + const checkAllValue = $(eles.selectAll).is(':checked'); + $('#pcaConditionTbl tbody tr input').each(function f() { + $(this).prop('checked', checkAllValue); + }); + }); + + // uncheck all if one item is unchecked + $('#pcaConditionTbl tbody tr input').click(function f() { + if (!$(this).is(':checked')) { + $(eles.selectAll).prop('checked', false); + } + }); +}; + +const setupDateTime = () => { + const startDateElements = $('input[name="START_DATE"]'); + const endDateElements = $('input[name="END_DATE"]'); + const startTimeElements = $('input[name="START_TIME"]'); + const endTimeElements = $('input[name="END_TIME"]'); + + initializeDateTime({ + START_DATE: $(startDateElements[0]), + END_DATE: $(endDateElements[0]), + START_TIME: $(startTimeElements[0]), + END_TIME: $(endTimeElements[0]), + }); + initializeDateTime({ + START_DATE: $(startDateElements[1]), + END_DATE: $(endDateElements[1]), + START_TIME: $(startTimeElements[1]), + END_TIME: $(endTimeElements[1]), + }); +}; + +const addToolTip = () => { + $('#pcaConditionTbl td:nth-child(5)').addClass('tooltip-pca'); +}; + +const loadUserInputAgain = (parent) => { + const inputForms = $('form'); + // load user input on page load + setTimeout(() => { + inputForms.each((i, form) => { + try { + const userInput = saveLoadUserInput(`#${form.id}`, window.location.pathname, parent); + userInput(); + } catch (e) { + console.log(e); + } + }); + }, 500); +}; + +const addClickEventAllRows = () => { + // add click event of whole row for pcaConditionTbl + $(eles.pcaConditionTblAllRows).each((i, element) => { + const that = $(element); + that.click((e) => { + const currentRow = $(e.currentTarget); + const currentCheckbox = $(currentRow.find('input[type="checkbox"]')); + currentCheckbox.prop('checked', !currentCheckbox.is(':checked')); + }); + }); +}; + +const appendSensors = (sensors = []) => { + if (!table) return; + + const rows = []; + for (const sensor of sensors) { + const row = createSensorRow(sensor); + rows.push(row); + } + table.rows.add(rows).draw(); + + addToolTip(); + loadUserInputAgain('pcaConditionTbl'); + + if (sensors.length < 60) { + $(eles.spinner).removeClass('spinner-grow'); + } + + addClickEventAllRows(); +}; + +const loadSensors = () => { + $(eles.spinner).addClass('spinner-grow'); + + const myHeaders = new Headers(); + myHeaders.append('Content-Type', 'application/json'); + const requestOptions = { + method: 'GET', + headers: myHeaders, + }; + fetch('/histview2/api/analyze/sensor', requestOptions) + .then(response => response.text()) + .catch(error => console.log('error', error)); + setTimeout(() => { + $(eles.spinner).removeClass('spinner-grow'); + }, 20000); +}; + +let table = null; + +$(() => { + // generate process dropdown data + const endProcs = genProcessDropdownData(procConfigs); + // add first end process + const endProcItem = addEndProcMultiSelect(endProcs.ids, endProcs.names, true, false, false, true); + endProcItem(); + updateSelectedItems(); + addAttributeToElement(); + + // click even of end proc add button + $('#btn-add-end-proc').click(() => { + endProcItem(); + updateSelectedItems(); + addAttributeToElement(); + }); + + // add first condition process + const condProcs = genProcessDropdownData(procConfigs); + const condProcItem = addCondProc(condProcs.ids, condProcs.names, '', eles.formID, 'btn-add-cond-proc'); + condProcItem(); + + // click even of condition proc add button + $(eles.btnAddCondProc).click(() => { + condProcItem(); + }); + + $(eles.msgConfirmBtn).click(() => { + confirmWarningAndGetPCA(); + }); + + // allow number > 0 only for latest time interval + validateRecentTimeInterval($(eles.recentTimeIntervalInput)); + + // bind choose sensors events + bindCheckEvents(); + + setupDateTime(); + + // Load userBookmarkBar + $('#userBookmarkBar').show(); + + initializeDateTimeRangePicker(); + + initValidation(eles.formID); +}); diff --git a/histview2/static/analyze/js/pca_toastr.js b/histview2/static/analyze/js/pca_toastr.js new file mode 100644 index 0000000..ce5b411 --- /dev/null +++ b/histview2/static/analyze/js/pca_toastr.js @@ -0,0 +1,49 @@ + +const showToastr = (errors) => { + if (!errors) { + return; + } + if (errors instanceof Array) { + errors.forEach((error) => { + const msgTitle = `${i18n.error}`; + const msgContent = `

${MSG_MAPPING[error] || JSON.stringify(error)}

`; + showToastrMsg(msgContent, msgTitle, MESSAGE_LEVEL.ERROR); + }); + } else { + const msgTitle = `${i18n.error}`; + const msgContent = `

${MSG_MAPPING[errors] || JSON.stringify(errors)}

`; + showToastrMsg(msgContent, msgTitle, MESSAGE_LEVEL.ERROR); + } +}; + +// show toastr msg to warn about abnormal result +const showToastrDeleteNA = (dataSet, totalCount, removedCount) => { + const i18nTexts = { + warningTitle: $('#i18nWarningTitle').text(), + showDeletedNA: $('#i18nDeleteNA').text() || '', + }; + + const msgTitle = i18nTexts.warningTitle || '注意'; + const msgContent = `${dataSet}: ${i18nTexts.showDeletedNA + .replace('TOTAL_COUNT', totalCount) + .replace('REMOVED_COUNT', removedCount)}`; + const msg = `

${msgContent}

`; + + showToastrMsg(msg, msgTitle, MESSAGE_LEVEL.WARN); +}; + +const showAllDeleteNAToastrMsgs = (res, formData) => { + const numSensors = countSelectedSensors(formData) || 1; + if (res.removed_outlier_nan_train) { + showToastrDeleteNA( + i18n.trainingData, + numSensors * res.actual_record_number_train, + res.removed_outlier_nan_train, + ); + } + if (res.removed_outlier_nan_test) { + showToastrDeleteNA(i18n.testingData, + numSensors * res.actual_record_number_test, + res.removed_outlier_nan_test); + } +}; diff --git a/histview2/static/categorical_plot/css/categorical_plot.css b/histview2/static/categorical_plot/css/categorical_plot.css new file mode 100644 index 0000000..febdc12 --- /dev/null +++ b/histview2/static/categorical_plot/css/categorical_plot.css @@ -0,0 +1,294 @@ +.multi-selectbox { + padding: 0 !important; +} + +#plot-cards .row { + margin: auto 0; +} + +#plot-cards .card { + width: 100%; +} + +.ui-sortable-handle { + max-width: 100% !important; +} + +canvas { + max-height: 300px !important; + min-width: 400px !important; +} + +@keyframes rotate{ + 0% {} + 100% { + transform: rotate(-360deg); + } +} +@-webkit-keyframes rotate{ + 0% {} + 100% { + -webkit-transform: rotate(-360deg); + } +} + +.hide { + display: none; +} + +.loading { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0, 0, 0, .5); + z-index: 999; +} + +.loading::before { + content: ""; + display: block; + position: fixed; + left: 50%; + top: 50%; + width: 150px; + height: 110px; + border-radius: 5px; + margin-top: -25px; + margin-left: -55px; + /* background-color: white; */ +} + +.loading::after { + content: ""; + display: block; + position: fixed; + left: 50%; + top: 50%; + width: 64px; + height: 64px; + border-radius: 40px; + margin-top: -10px; + margin-left: -10px; + border: 4px solid #60ABB9; + border-right: 4px solid white; + animation: rotate 1s infinite linear; +} + + +.cate-plot-cards { + display: inline-block; + width: 100%; + margin-top: 10px; +} + +.plotInfo { + position: absolute; + background-color: rgba(27, 27, 27, .4); + border: 1px solid #65c5f1; + border-radius: 3px; + display: none; + width: 170px; + text-align: center; + margin: 0 20px 10px; + color: #ffddab; + /* opacity: 0.4; */ + /* margin-top: -10px; */ + padding-bottom: 5px; + z-index: 99999; +} + +.plotInfo td { + width: 50%; + font-weight: normal; + font-size: small; +} + +.plotInfo tfoot { + border-top: 2px dotted #444; +} + +.plotInfo .item-name:hover { + text-decoration: underline; +} + +#varInfo { + width: 300px; + display: none; + position: fixed; + border: 1px solid #65c5f1; + border-radius: 3px; + padding: 10px; + background-color: rgb(48, 48, 48); +} + +#vi-title { + color: #62bce6; + text-decoration: underline; + font-size: small; +} + +#vi-content { + font-size: small; +} + +/* overide css from component.css */ +.card .end-proc, +.card .cond-proc { + margin-bottom: 0rem; +} + +.tab-switch { + border: solid 1px #444444; + background-color: #303030; +} + +.col-medium { + min-width: 125px; + margin-bottom: 0rem; +} + +.pinCard { + right: 0; + cursor: pointer; + position: absolute; + color: #ffddab; +} + +.pinned { + color: #65c5f1; + -webkit-transform: rotate(-45deg); + -moz-transform: rotate(-45deg); + -ms-transform: rotate(-45deg); + -o-transform: rotate(-45deg); + transform: rotate(-45deg); + transition: all 0.3s ease; +} + +.item-name { + color: #ffddab; +} + +#compareType .nav-pills { + border-radius: 4px 4px 0 0 ; +} + +#compareType .nav-item .active{ + background-color: #303030; +} + +#compareType{ + border-bottom: 0px; +} + +#compareType .nav-item { + border-top: .5px solid #444; + border-left: .5px solid #444; + border-right: .5px solid #444; + border-radius: 4px 4px 0 0 ; + margin-left: 1px; + width: 225px; + text-align: center; +} + +#varTab, +#termTab { + border-radius: 4px 0 4px 4px; + border-top-color: #303030; +} + +.no-top-border { + border-top: 0px; +} + +.summary-col{ + display: none; +} + +.hist-summary, .hist-summary-detail { + font-size: 0.8vw; + display: none; +} + +.hist-summary table tr td { + vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: center; + padding: 0 15px; +} + +.hist-summary table tr td:nth-child(1) { + text-align: left !important; +} + +.summary-value { + text-decoration: underline; +} + +/* + * Scrollbar darkstyle for summary + */ + .hist-summary::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); + background-color: #444; + } + + .hist-summary::-webkit-scrollbar{ + width: 10px; + height: 10px; + background-color: #444; + } + +.hist-summary::-webkit-scrollbar-thumb{ + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); + background-color: #222222; +} + +.hist-summary-fixed { + position: fixed; + top: 0; + box-sizing: border-box; + z-index: 11; +} + +.hist-summary-fixed.absolute { + position: absolute; +} + +.item-name{ + color: #65c5f1; + text-align: center; + vertical-align: middle; + /* display: table-cell; */ +} + +.cate-plot-cards table tbody { + display: table; + width: 100%; +} + +.tab-name { + text-overflow: ellipsis; + overflow: hidden; + max-width: 12vw; + white-space: nowrap; +} + +/* chart scale options bar */ +.y-scale-group { + display: flex; + justify-content: flex-end; +} + +.y-scale { + margin: 0 0.5rem; +} +.cyclic-term-target-period { + padding-left: 1rem; + border: 2px solid #444444; + border-radius: 3px; + padding-right: 1rem; +} \ No newline at end of file diff --git a/histview2/static/categorical_plot/css/toastr.css b/histview2/static/categorical_plot/css/toastr.css new file mode 100644 index 0000000..49fe789 --- /dev/null +++ b/histview2/static/categorical_plot/css/toastr.css @@ -0,0 +1,200 @@ +.toast-title { + font-weight: bold; +} +.toast-message { + -ms-word-wrap: break-word; + word-wrap: break-word; +} +.toast-message a, +.toast-message label { + color: #ffffff; +} +.toast-message a:hover { + color: #cccccc; + text-decoration: none; +} +.toast-close-button { + position: relative; + right: -0.3em; + top: -0.3em; + float: right; + font-size: 20px; + font-weight: bold; + color: #ffffff; + -webkit-text-shadow: 0 1px 0 #ffffff; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); +} +.toast-close-button:hover, +.toast-close-button:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); +} +/*Additional properties for button version + iOS requires the button element instead of an anchor tag. + If you want the anchor version, it requires `href="#"`.*/ +button.toast-close-button { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.toast-top-center { + top: 0; + right: 0; + width: 100%; +} +.toast-bottom-center { + bottom: 0; + right: 0; + width: 100%; +} +.toast-top-full-width { + top: 0; + right: 0; + width: 100%; +} +.toast-bottom-full-width { + bottom: 0; + right: 0; + width: 100%; +} +.toast-top-left { + top: 12px; + left: 12px; +} +.toast-top-right { + top: 12px; + right: 12px; +} +.toast-bottom-right { + right: 12px; + bottom: 12px; +} +.toast-bottom-left { + bottom: 12px; + left: 12px; +} +#toast-container { + position: fixed; + z-index: 999999; + pointer-events: none; + /*overrides*/ +} +#toast-container * { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +#toast-container > div { + position: relative; + pointer-events: auto; + overflow: hidden; + margin: 0 0 6px; + padding: 15px 15px 15px 50px; + width: 300px; + -moz-border-radius: 3px 3px 3px 3px; + -webkit-border-radius: 3px 3px 3px 3px; + border-radius: 3px 3px 3px 3px; + background-position: 15px center; + background-repeat: no-repeat; + -moz-box-shadow: 0 0 12px #999999; + -webkit-box-shadow: 0 0 12px #999999; + box-shadow: 0 0 12px #999999; + color: #ffffff; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); +} +#toast-container > :hover { + -moz-box-shadow: 0 0 12px #000000; + -webkit-box-shadow: 0 0 12px #000000; + box-shadow: 0 0 12px #000000; + opacity: 1; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + filter: alpha(opacity=100); + cursor: pointer; +} +#toast-container > .toast-info { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important; +} +#toast-container > .toast-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; +} +#toast-container > .toast-success { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important; +} +#toast-container > .toast-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important; +} +#toast-container.toast-top-center > div, +#toast-container.toast-bottom-center > div { + width: 300px; + margin-left: auto; + margin-right: auto; +} +#toast-container.toast-top-full-width > div, +#toast-container.toast-bottom-full-width > div { + width: 96%; + margin-left: auto; + margin-right: auto; +} +.toast { + background-color: #030303; +} +.toast-success { + background-color: #51a351; +} +.toast-error { + background-color: #bd362f; +} +.toast-info { + background-color: #2f96b4; +} +.toast-warning { + background-color: #f89406; +} +.toast-progress { + position: absolute; + left: 0; + bottom: 0; + height: 4px; + background-color: #000000; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); +} +/*Responsive Design*/ +@media all and (max-width: 240px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 11em; + } + #toast-container .toast-close-button { + right: -0.2em; + top: -0.2em; + } +} +@media all and (min-width: 241px) and (max-width: 480px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 18em; + } + #toast-container .toast-close-button { + right: -0.2em; + top: -0.2em; + } +} +@media all and (min-width: 481px) and (max-width: 768px) { + #toast-container > div { + padding: 15px 15px 15px 50px; + width: 25em; + } +} diff --git a/histview2/static/categorical_plot/img/scatter_plot.png b/histview2/static/categorical_plot/img/scatter_plot.png new file mode 100644 index 0000000..b670c77 Binary files /dev/null and b/histview2/static/categorical_plot/img/scatter_plot.png differ diff --git a/histview2/static/categorical_plot/js/categorical_histogram_with_density_curve.js b/histview2/static/categorical_plot/js/categorical_histogram_with_density_curve.js new file mode 100644 index 0000000..4912aa0 --- /dev/null +++ b/histview2/static/categorical_plot/js/categorical_histogram_with_density_curve.js @@ -0,0 +1,285 @@ +/* eslint-disable no-restricted-syntax */ +// eslint-disable-next-line no-unused-vars +const HistogramWithDensityCurve = ($, paramObj) => { + // ////////////// プライベート関数の定義 //////////////////// + const setParam = (key, defaultValue) => { + if (key in paramObj && !isEmpty(paramObj[key])) { + return paramObj[key]; + } + return defaultValue; + }; + + + const canvasId = setParam('canvasId', ''); + const yLabelFreq = setParam('yLabelFreq', '度数(カウント)'); + const kdeData = setParam('kdeData', []); + const minY = setParam('minY', 0); + const maxY = setParam('maxY', 1); + const threshHigh = setParam('threshHigh', ''); + const threshLow = setParam('threshLow', ''); + const prcMax = setParam('prcMax', ''); + const prcMin = setParam('prcMin', ''); + + + const timeCond = setParam('timeCond', ''); + const tabPrefix = setParam('tabPrefix', ''); + const categoryValue = setParam('categoryValue', ''); + + // convert UTC to local time to show to UI + const startLocalDt = moment.utc(timeCond.start_dt).local(); + const endLocalDt = moment.utc(timeCond.end_dt).local(); + const startDate = startLocalDt.format(DATE_FORMAT); + const startTime = startLocalDt.format(TIME_FORMAT); + const endDate = endLocalDt.format(DATE_FORMAT); + const endTime = endLocalDt.format(TIME_FORMAT); + + let graphTitle; + if ([eles.termTabPrefix, eles.cyclicTermTabPrefix].includes(tabPrefix)) { + graphTitle = `${startDate} ${startTime} -
${endDate} ${endTime}
${categoryValue}`; + } else { + graphTitle = categoryValue; + } + + let customBinSize = 1; + if (kdeData && kdeData.hist_labels.length > 1) { + customBinSize = kdeData.hist_labels[1] - kdeData.hist_labels[0]; + } + const maxKDE = Math.max(...kdeData.kde); + const maxHist = Math.max(...kdeData.hist_counts); + const transKDE = kdeData.kde.map(i => maxHist * i / maxKDE); + const kdeDensity = { + y: kdeData.hist_labels, + x: transKDE, + mode: 'lines', + name: 'KDE', + line: { + shape: 'spline', + width: 1.25, + }, + type: 'scatter', + orientation: 'h', + xaxis: 'x', + marker: { + color: '#F39C12', + }, + hoverinfo: 'none', + }; + const histogram = { + y: kdeData.hist_labels, + x: kdeData.hist_counts, + histfunc: 'sum', + orientation: 'h', + marker: { + color: '#89b368', + line: { + color: '#89b368', + width: 0.2, + }, + }, + name: '', + opacity: 0.75, + type: 'histogram', + autobiny: false, + autobinx: false, + // nbinsy: kdeData.hist_counts.length, + ybins: { + end: kdeData.hist_labels[kdeData.hist_labels.length - 1], + size: customBinSize, + start: kdeData.hist_labels[0], + }, + hovertemplate: '%{x}', + }; + const data = [ + histogram, + kdeDensity, + ]; + // thresholds + const lines = []; + if (!isEmpty(threshHigh)) { + lines.push({ + type: 'line', + xref: 'paper', + x0: 0, + y0: threshHigh, + x1: 1, + y1: threshHigh, + line: { + color: 'rgb(255, 0, 0)', + width: 1, + }, + }); + } + if (!isEmpty(threshLow)) { + lines.push({ + type: 'line', + xref: 'paper', + x0: 0, + y0: threshLow, + x1: 1, + y1: threshLow, + line: { + color: 'rgb(255, 0, 0)', + width: 1, + }, + }); + } + if (!isEmpty(prcMax)) { + lines.push({ + type: 'line', + xref: 'paper', + x0: 0, + y0: prcMax, + x1: 1, + y1: prcMax, + line: { + color: '#4276ad', + width: 1, + // dash: 'dot', + }, + }); + } + if (!isEmpty(prcMin)) { + lines.push({ + type: 'line', + xref: 'paper', + x0: 0, + y0: prcMin, + x1: 1, + y1: prcMin, + line: { + color: '#4276ad', + width: 1, + // dash: 'dot', + }, + }); + } + const layout = { + showlegend: false, + title: { + text: graphTitle, + font: { + color: '#65c5f1', + size: 10, + }, + }, + xaxis: { + autorange: true, + automargin: false, + title: { + text: yLabelFreq, + font: { + color: 'rgba(255,255,255,1)', + size: 10, + }, + }, + gridcolor: '#444444', + tickfont: { + color: 'rgba(255,255,255,1)', + size: 8, + }, + spikemode: 'across', + spikethickness: 1, + spikedash: 'solid', + spikecolor: 'rgb(255, 0, 0)', + }, + yaxis: { + // side: 'right', + gridcolor: '#444444', + tickfont: { + color: 'rgba(255,255,255,1)', + size: 8, + }, + spikemode: 'across', + spikethickness: 1, + spikedash: 'solid', + spikecolor: 'rgb(255, 0, 0)', + }, + xaxis2: { + automargin: false, + side: 'top', + overlaying: 'x', + rangemode: 'tozero', + autorange: true, + showgrid: false, + showticklabels: false, + spikemode: 'across', + spikethickness: 1, + spikedash: 'solid', + spikecolor: 'rgb(255, 0, 0)', + tickfont: { + size: 8, + }, + }, + plot_bgcolor: '#222222', + paper_bgcolor: '#222222', + hovermode: 'closest', + autosize: true, + margin: { + l: 30, + r: 7, + b: 35, + t: 35, + pad: 5, + }, + shapes: lines, + }; + + // do not compare minY, maxY vs 0! + if (minY !== null && maxY !== null) { + layout.yaxis.range = [minY, maxY]; + layout.yaxis.autorange = false; + } + try { + Plotly.newPlot(canvasId, data, layout, { + displayModeBar: false, + responsive: true, // responsive histogram + useResizeHandler: true, // responsive histogram + style: { width: '100%', height: '100%' }, // responsive histogram + }); + } catch (e) { + console.log(canvasId, data, layout); + console.log(e); + } + + const drawShapes = (x = null, y = null, display = true) => { + const plots = document.querySelectorAll('.hd-plot'); + for (const plot of plots) { + const currentShapes = plot.layout.shapes; + const layoutUpdates = {}; + layoutUpdates.shapes = [...currentShapes]; + + // remove old crosshair + const lastShape = layoutUpdates.shapes.slice(-1)[0]; + if (lastShape && lastShape.name === 'crosshair') { + layoutUpdates.shapes.pop(); + } + + // add new crosshair + if (display) { + layoutUpdates.shapes.push({ + type: 'line', + xref: 'paper', + x0: 0, + y0: y, + x1: 10000, + y1: y, + line: { + color: 'rgb(255, 0, 0)', + width: 1, + dash: 'solid', + }, + name: 'crosshair', + }); + } + Plotly.update(plot, {}, layoutUpdates); + } + }; + + const hdPlot = document.getElementById(canvasId); + hdPlot.on('plotly_hover', (data) => { + drawShapes(data.points[0].x, data.points[0].y); + }) + .on('plotly_unhover', (data) => { + drawShapes(null, null, false); + }); +}; diff --git a/histview2/static/categorical_plot/js/categorical_plot.js b/histview2/static/categorical_plot/js/categorical_plot.js new file mode 100644 index 0000000..80681d9 --- /dev/null +++ b/histview2/static/categorical_plot/js/categorical_plot.js @@ -0,0 +1,871 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-undef */ +/* eslint-disable no-use-before-define */ +const REQUEST_TIMEOUT = setRequestTimeOut(120000); // 3 minutes +const MAX_NUMBER_OF_GRAPH = 32; +const MAX_END_PROC = 8; +is_sse_listening = false; +const dicTabs = { '#byVarCompare': 'var', '#byTermCompare': 'term', '#byCyclicTerm': 'cyclicTerm' }; +let currentTraceDataVar; +let currentTraceDataTerm; +let currentTraceDataCyclicTerm; +const scaleOptions = {}; + +const eles = { + varTabPrefix: 'var', + termTabPrefix: 'directTerm', + cyclicTermTabPrefix: 'cyclicTerm', + formID: 'CategoricalPlotForm', + mainFormId: '#categoricalPlotForm', + varFormID: '#varCategoricalPlotForm', + termFormID: '#termCategoricalPlotForm', + endProcItems: '#end-proc-row .end-proc', + endProcSelectedItem: '#end-proc-row select', + condProcReg: /cond_proc/g, + condProcProcessDiv: 'CondProcProcessDiv', + condProcPartno: 'condProcPartno', + categoryPlotTime: '.category-plot-time', + categoryVariableSelect: 'CategoryVariableSelect', + categoryPlotCards: 'CatePlotCards', + histScale: 'HistScale', + condMachinePartnoDiv: 'CondMachinePartnoDiv', + endProcProcess: 'EndProcProcess', + endProcProcessDiv: 'EndProcProcessDiv', + endProcVal: 'EndProcVal', + endProcValDiv: 'EndProcValDiv', + startDate: 'StartDate', + startTime: 'StartTime', + endDate: 'EndDate', + endTime: 'EndTime', + DateTimeDivName: 'DateTime', + DateTimeEleName: 'DateTimeElement', + termBtnAddDateTime: '#termBtnAddDateTime', + termBtnAddVariable: '#termBtnAddVariable', + termBtnAddCondProc: '#termbtn-add-cond-proc', + varBtnAddCondProc: '#varbtn-add-cond-proc', + dateTimeCloseBtn: 'DateTimeCloseBtn', + VariableCloseBtn: 'VariableCloseBtn', + categoryVariableName: 'categoryVariable', + categoryValueMulti: 'categoryValueMulti', + histograms: 'Histograms', + plotInfoId: 'PlotDetailInfomation', + termCondProcRow: 'term-cond-proc-row', + traceTime: 'TraceTime', + recentTimeInterval: 'recentTimeInterval', + scatterPlotCards: 'ScatterPlotCards', + summaryOption: 'SummaryOption', + machineID: 'machine-id', + lineList: 'line-list', + checkBoxs: 'input[name=GET02_VALS_SELECT1][type=checkbox][value!=All]', + varShowGraphBtn: '#varShowGraphBtn', + stratifiedVarTabs: '#stratifiedVarTabs', + cyclicTermTabs: '#stratifiedCyclicTermTabs', + showValueKey: 'GET02_VALS_SELECT1', + stratifiedVarsKey: 'categoryValueMulti1', + currentscaleOption: '#currentscaleOption', + yScaleGroup: '#yScaleGroup', + histActiveTab: 'a.nav-link.active.tab-name', + varCategoryItems: 'catItems', + varProcItems: 'procItems', + endProcCate: 'end_proc_cate', + stratifiedTabs: '.stratifiedTabs', +}; + + +const formElements = { + NO_FILTER: 'NO_FILTER', + BY_VAR: 'var', + BY_TERM: 'term', + BY_CYCLIC: 'cyclicTerm', + SCALE_DEFAULT_OPTION: 1, + endProcSelectedItem: '#end-proc-row select', +}; + + +const i18n = { + yLabelKDE: $('#i18nKDE').text(), + yLabelFreq: $('#i18nFrequency').text(), + machineName: $('#i18nMachineNo').text(), + machineNo: $('#i18nMachineNo').text(), + partNoName: $('#i18nPartNo').text(), + partNo: $('#i18nPartNo').text(), + dateRange: $('#i18nDateRange').text(), + dateRecent: $('#i18nRecent').text(), + hours: $('#i18nHour').text(), + summaryDistribution: $('#i18nSummaryDistribution').text(), + nNumber: $('#i18nNNumber').text(), + average: $('#i18nAverage').text(), + median: $('#i18nMedian').text(), + ucl: $('#i18nUCL').text(), + lcl: $('#i18nLCL').text(), + hour: $('#i18nHour').text(), + minute: $('#i18nMinute').text(), + day: $('#i18nDay').text(), + week: $('#i18nWeek').text(), + noFilter: $('#i18nNoFilter').text(), + allSelection: $('#i18nAllSelection').text(), + viewerTabName: $('#i18nViewer').text(), + warningTitle: $('#i18nWarningTitle').text(), + selectTooManyValue: $('#i18nTooManyValue').text(), + selectFacet: $('#i18nSelectCatExpBox').text(), +}; + +const loading = $('.loading'); + +$(() => { + // tabs switch + $(document).ready(() => { + $('#tabs a').click(function f(e) { + e.preventDefault(); + $(this).tab('show'); + }); + }); + + // hide loading screen + loading.addClass('hide'); + + // generate process dropdown data + const endProcs = genProcessDropdownData(procConfigs); + + // add first end process + const varEndProcItem = addEndProcMultiSelect(endProcs.ids, endProcs.names, true, true, true, true); + varEndProcItem(); + + // for multiple end procs setting + // click even of end proc add button + $('#btn-add-end-proc').click(() => { + varEndProcItem(); + updateSelectedItems(); + addAttributeToElement(); + }); + + // add first condition process + const varCondProcItem = addCondProc(endProcs.ids, endProcs.names, eles.varTabPrefix, eles.mainFormId.replace('#', ''), + 'varbtn-add-cond-proc'); + varCondProcItem(); + + // click even of condition proc add button + $(eles.varBtnAddCondProc).click(() => { + varCondProcItem(); + addAttributeToElement(); + }); + + // Load userBookmarkBar + $('#userBookmarkBar').show(); + + // add limit after running load_user_setting + setTimeout(addLimitNumSensors, 600); + + initValidation(eles.mainFormId); + + initTargetPeriod(); + toggleDisableAllInputOfNoneDisplayEl($('#for-cyclicTerm')); + toggleDisableAllInputOfNoneDisplayEl($('#for-directTerm')); + + // add limit after running load_user_setting + setTimeout(() => { + // add validations for target period + validateTargetPeriodInput(); + }, 600); + + // validate and change to default and max value cyclic term + validateInputByNameWithOnchange(CYCLIC_TERM.WINDOW_LENGTH, CYCLIC_TERM.WINDOW_LENGTH_MIN_MAX); + validateInputByNameWithOnchange(CYCLIC_TERM.INTERVAL, CYCLIC_TERM.INTERVAL_MIN_MAX); + validateInputByNameWithOnchange(CYCLIC_TERM.DIV_NUM, { MAX: 32, MIN: 1 }); + + initializeDateTimeRangePicker(); + initializeDateTimePicker(); +}); + +const showScatterPlotImage = (fileNames) => { + const scatterPlotCard = $(`#${eles.varTabPrefix}${eles.scatterPlotCards}`); + scatterPlotCard.empty(); + + if (isEmpty(fileNames)) return; + + let imgs = ''; + fileNames.forEach((e) => { + imgs += ``; + }); + scatterPlotCard.html(`
${imgs}
`); +}; + + +const onChangeHistSummaryEventHandler = (eleIdPrefix = '') => { + $(`input[name=${eleIdPrefix}${eles.summaryOption}]`).unbind('change'); + $(`input[name=${eleIdPrefix}${eles.summaryOption}]`).on('change', function f() { + let summaryHeight = null; + const summaryClass = $(this).val(); + const previousOption = $(`input[name=${eleIdPrefix}${eles.summaryOption}][data-checked=true]`); + if (summaryClass === 'none') { + $(`.${eleIdPrefix}.hist-summary`).each(function showHideSummary() { + $(this).css('display', 'none'); + }); + // if (previousOption.val() && previousOption.val() !== 'none') { + // // rescale histogram + // $(`.${eleIdPrefix}.his .hd-plot`).each(function reScaleHistogram() { + // const histogramId = $(this).attr('id'); + // $(`#${histogramId}`).css('height', GRAPH_CONST.histHeight); + // Plotly.relayout(histogramId, {}); + // }); + // } + $(`.${eleIdPrefix}.his .hd-plot`).each(function reScaleHistogram() { + const histogramId = $(this).attr('id'); + $(`#${histogramId}`).css('height', GRAPH_CONST.histHeight); + Plotly.relayout(histogramId, {}); + }); + + // mark this option as checked and remove others + $(this).attr('data-checked', 'true'); + $(`input[name=${eleIdPrefix}${eles.summaryOption}]:not(:checked)`).removeAttr('data-checked'); + } else { + $(`.${eleIdPrefix}.hist-summary`).each(function showHideSummary() { + $(this).css('display', 'flex'); + $(this).css('justify-content', 'center'); // to unify with FPP + }); + + $(`.${eleIdPrefix}.hist-summary-detail`).each(function showUponOption() { + $(this).css('display', 'none'); + if ($(this).hasClass(summaryClass)) { + $(this).css('display', 'block'); + const h = $(this).height(); + summaryHeight = h < summaryHeight ? summaryHeight : h; + } + }); + + $(`.${eleIdPrefix}.his .hd-plot`).each(function reScaleHistogram() { + const histogramId = $(this).attr('id'); + const chartHeight = `calc(${GRAPH_CONST.histHeight} - ${summaryHeight + 6}px)`; + $(`#${histogramId}`).css('height', chartHeight); + Plotly.relayout(histogramId, {}); + }); + + // mark this option as checked and remove others + $(this).attr('data-checked', 'true'); + $(`input[name=${eleIdPrefix}${eles.summaryOption}]:not(:checked)`).removeAttr('data-checked'); + } + }); +}; + +const onChangeHistScale = (prefix) => { + $(`select[name=${prefix}HistScale]`).unbind('change'); + $(`select[name=${prefix}HistScale]`).on('change', function f() { + // reset summary option + resetSummaryOption(`${prefix}${eles.summaryOption}`); + + let data = null; + let sensorID = 0; + const scaleOption = $(this).children('option:selected').val() || '1'; + sensorID = $(`#${eles.categoryPlotCards}`).find($(eles.histActiveTab)).data('sensor-id'); + if (prefix === eles.varTabPrefix) { + data = currentTraceDataVar; + } else if (prefix === eles.cyclicTermTabPrefix) { + data = currentTraceDataCyclicTerm; + } else { + data = currentTraceDataTerm; + } + + scaleOptions[sensorID] = scaleOption; + showTabsAndCharts(prefix, data, scaleOption, false, sensorID); + }); +}; + +const buildHistogramSummariesHTML = (eleIdPrefix, tableIndex, chartOption, generalInfo) => { + const [nTotalHTML, noLinkedHTML] = genTotalAndNonLinkedHTML(chartOption, generalInfo); + + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${noLinkedHTML} + +
Ntotal + ${nTotalHTML} +
+ outCL + + ${chartOption.p}% (${chartOption.pn}) + + outCL+ : ${chartOption.pPlus}% (${chartOption.pnPlus})
+ outCL- : ${chartOption.pMinus}% (${chartOption.pnMinus})
+
+
+
+ outAL + + ${chartOption.pProc}% (${chartOption.pnProc}) + + outAL+ : ${chartOption.pProcPlus}% (${chartOption.pnProcPlus})
+ outAL- : ${chartOption.pProcMinus}% (${chartOption.pnProcMinus})
+
+
+
PNA${chartOption.pNA}% (${chartOption.pnNA})
PNaN${chartOption.pNaN}% (${chartOption.pnNaN})
PInf+${chartOption.pInf}% (${chartOption.pnInf})
PInf-${chartOption.pNegInf}% (${chartOption.pnNegInf})
PTotal${chartOption.pTotal}% (${chartOption.pnTotal})
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N + ${isEmpty(chartOption.nStats) ? '-' : chartOption.nStats} +
${i18nCommon.average} + ${isEmpty(chartOption.bsAverage) ? '-' : chartOption.bsAverage} +
+ ${isEmpty(chartOption.sigma3) ? '-' : chartOption.sigma3} +
Cp + ${isEmpty(chartOption.cp) ? '-' : chartOption.cp} +
Cpk + ${isEmpty(chartOption.cpk) ? '-' : chartOption.cpk} +
σ + ${isEmpty(chartOption.sigma) ? '-' : chartOption.sigma} +
${i18nCommon.maxValue || 'Max'} + ${isEmpty(chartOption.maxValue) ? '-' : chartOption.maxValue} +
${i18nCommon.minValue || 'Min'} + ${isEmpty(chartOption.minValue) ? '-' : chartOption.minValue} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
${i18nCommon.median || '中央値'} + ${isEmpty(chartOption.median) ? '-' : chartOption.median} +
P95 + ${isEmpty(chartOption.p95) ? '-' : chartOption.p95} +
P75 Q3 + ${isEmpty(chartOption.p75Q3) ? '-' : chartOption.p75Q3} +
P25 Q1 + ${isEmpty(chartOption.p25Q1) ? '-' : chartOption.p25Q1} +
P5 + ${isEmpty(chartOption.p5) ? '-' : chartOption.p5} +
IQR + ${isEmpty(chartOption.iqr) ? '-' : chartOption.iqr} +
NIQR + ${isEmpty(chartOption.niqr) ? '-' : chartOption.niqr} +
Mode + ${isEmpty(chartOption.mode) ? '-' : chartOption.mode} +
+ NOverflow+ + + ${isEmpty(chartOption.numOverUpper) ? '-' : chartOption.numOverUpper} +
+ NOverflow- + + ${isEmpty(chartOption.numOverLower) ? '-' : chartOption.numOverLower} +
`; +}; + +const calculateSummaryData = (summaries, summaryIdx = 0) => { + const summary = summaries[summaryIdx || 0] || summaries[0]; + + // count statistics + const ntotal = getNode(summary, ['count', 'ntotal'], 0) || 0; + const countUnlinked = getNode(summaries, ['count', 'count_unlinked'], 0) || 0; + const p = getNode(summary, ['count', 'p'], '0') || '0'; + const pMinus = getNode(summary, ['count', 'p_minus'], '0') || '0'; + const pPlus = getNode(summary, ['count', 'p_plus'], '0') || '0'; + const pn = getNode(summary, ['count', 'pn'], '0') || '0'; + const pnMinus = getNode(summary, ['count', 'pn_minus'], '0') || '0'; + const pnPlus = getNode(summary, ['count', 'pn_plus'], '0') || '0'; + const pnProc = getNode(summary, ['count', 'pn_proc'], '0') || '0'; + const pnProcPlus = getNode(summary, ['count', 'pn_proc_plus'], '0') || '0'; + const pnProcMinus = getNode(summary, ['count', 'pn_proc_minus'], '0') || '0'; + const pProc = getNode(summary, ['count', 'p_proc'], '0') || '0'; + const pProcPlus = getNode(summary, ['count', 'p_proc_plus'], '0') || '0'; + const pProcMinus = getNode(summary, ['count', 'p_proc_minus'], '0') || '0'; + const pNA = getNode(summary, ['count', 'p_na'], '0') || '0'; + const pnNA = getNode(summary, ['count', 'pn_na'], '0') || '0'; + const pNaN = getNode(summary, ['count', 'p_nan'], '0') || '0'; + const pnNaN = getNode(summary, ['count', 'pn_nan'], '0') || '0'; + const pInf = getNode(summary, ['count', 'p_inf'], '0') || '0'; + const pnInf = getNode(summary, ['count', 'pn_inf'], '0') || '0'; + const pNegInf = getNode(summary, ['count', 'p_neg_inf'], '0') || '0'; + const pnNegInf = getNode(summary, ['count', 'pn_neg_inf'], '0') || '0'; + const pTotal = getNode(summary, ['count', 'p_total'], '0') || '0'; + const pnTotal = getNode(summary, ['count', 'pn_total'], '0') || '0'; + const linkedPct = getNode(summary, ['count', 'linked_pct'], '0') || '0'; + const noLinkedPct = getNode(summary, ['count', 'no_linked_pct'], '0') || '0'; + // basic-statistics + const nStats = getNode(summary, ['basic_statistics', 'n_stats'], '-') || '-'; + const cp = getNode(summary, ['basic_statistics', 'Cp'], '-') || '-'; + const cpk = getNode(summary, ['basic_statistics', 'Cpk'], '-') || '-'; + const maxValue = getNode(summary, ['basic_statistics', 'Max'], '0') || '0'; + const maxValueOrg = getNode(summary, ['basic_statistics', 'max_org'], '0') || '0'; + const minValue = getNode(summary, ['basic_statistics', 'Min'], '0') || '0'; + const minValueOrg = getNode(summary, ['basic_statistics', 'min_org'], '0') || '0'; + const bsAverage = getNode(summary, ['basic_statistics', 'average'], '0') || '0'; + const sigma = getNode(summary, ['basic_statistics', 'sigma'], '0') || '0'; + const sigma3 = getNode(summary, ['basic_statistics', 'sigma_3'], '0') || '0'; + // non-parametric + const median = getNode(summary, ['non_parametric', 'median'], '0') || '0'; + const p5 = getNode(summary, ['non_parametric', 'p5'], '0') || '0'; + const p25Q1 = getNode(summary, ['non_parametric', 'p25'], '0') || '0'; + const p25Q1Org = getNode(summary, ['non_parametric', 'p25_org'], '0') || '0'; + const p75Q3 = getNode(summary, ['non_parametric', 'p75'], '0') || '0'; + const p75Q3Org = getNode(summary, ['non_parametric', 'p75_org'], '0') || '0'; + const p95 = getNode(summary, ['non_parametric', 'p95'], '0') || '0'; + const numOverLower = getNode(summary, ['non_parametric', 'num_over_lower'], '0') || '0'; + const numOverUpper = getNode(summary, ['non_parametric', 'num_over_upper'], '0') || '0'; + const iqr = getNode(summary, ['non_parametric', 'iqr'], '-') || '-'; + const niqr = getNode(summary, ['non_parametric', 'niqr'], '-') || '-'; + const mode = getNode(summary, ['non_parametric', 'mode'], '0') || '0'; + + return { + // count + ntotal, + countUnlinked, + p, + pMinus, + pPlus, + pn, + pnPlus, + pnMinus, + pnProc, + pnProcPlus, + pnProcMinus, + pProc, + pProcPlus, + pProcMinus, + pNA, + pnNA, + pNaN, + pnNaN, + pInf, + pnInf, + pNegInf, + pnNegInf, + pTotal, + pnTotal, + linkedPct, + noLinkedPct, + // basic-statistics + nStats, + cp, + cpk, + maxValue, + maxValueOrg, + minValue, + minValueOrg, + bsAverage, + sigma, + sigma3, + // non-parametric + p95, + p75Q3, + p75Q3Org, + median, + p25Q1, + p25Q1Org, + p5, + numOverLower, + numOverUpper, + iqr, + niqr, + mode, + }; +}; + +const showTabsAndCharts = (eleIdPrefix, data, scaleOption = '1', genTab = true, onlySensorId = null) => { + let sensors = data.COMMON.GET02_VALS_SELECT || []; + if (typeof (sensors) === 'string') { + sensors = [sensors]; + } + + const numSensors = sensors.length; + + // prepare tabs HTML + if (genTab) { + let showViewerTab = false; + if (data.images && data.images.length > 0) { + showViewerTab = true; + } + showResultTabHTMLs(eleIdPrefix, data.ARRAY_FORMVAL, sensors, showViewerTab); + } + + // /////////////// each sensor //////////////////// + const startProc = data.COMMON.start_proc; + for (let sensorIdx = 0; sensorIdx < numSensors; sensorIdx++) { + if (onlySensorId !== null && onlySensorId !== Number(sensors[sensorIdx])) { + continue; + } + const tabId = `#${eleIdPrefix}${eles.categoryPlotCards}-${sensorIdx}`; + const tabElement = $(tabId); + tabElement.empty(); + tabElement.css('display', 'block'); + + + const sensor = sensors[sensorIdx]; + const sensorPlotDatas = eleIdPrefix !== 'directTerm' ? data.array_plotdata[sensor] + : data.array_plotdata.filter(plot => plot.end_col === Number(sensor)); + if (!sensorPlotDatas) { + continue; + } + const numGraphs = sensorPlotDatas.length; + + // カラム名を取得する。 + const endProc = getEndProcFromFormVal(sensors[sensorIdx], data.ARRAY_FORMVAL); + const displayColName = getColumnName(endProc, sensor); + + const generalInfo = { + startProc, endProcName: endProc, + }; + + // /////////////// each histogram //////////////////// + for (let i = 0; i < numGraphs; i++) { + // const catValue = sensorPlotDatas[i].cate_name || 'NA'; + const catExpValue = sensorPlotDatas[i].catExpBox || ''; + const categoryValue = catExpValue; + const termIdx = sensorPlotDatas[i].term_id || 0; + + // get latest thresholds -> show thresholds in scatter, histogram, summary + const [chartInfos, chartInfosOrg] = getChartInfo(sensorPlotDatas[i]); + const [latestChartInfo, latestChartInfoIdx] = chooseLatestThresholds(chartInfos, chartInfosOrg); + const threshHigh = latestChartInfo['thresh-high']; + const threshLow = latestChartInfo['thresh-low']; + const prcMax = latestChartInfo['prc-max']; + const prcMin = latestChartInfo['prc-min']; + + const scaleInfo = getScaleInfo(sensorPlotDatas[i], scaleOption); + // y_min/max are defined in backend -> get only + const kdeData = scaleInfo.kde_data; + const maxY = scaleInfo['y-max']; + const minY = scaleInfo['y-min']; + + // produce summary data + const { summaries } = sensorPlotDatas[i]; + const summaryData = calculateSummaryData(summaries, latestChartInfoIdx); + + // create summaries HTMLs + const summariesHTML = buildHistogramSummariesHTML(eleIdPrefix, i + 1, summaryData, generalInfo); + const histogramId = `${eleIdPrefix}-${sensor}-${eles.histograms}${i + 1}`; + const cardHtml = `
+
+
+
+ ${summariesHTML} +
+
+
`; + tabElement.append(cardHtml); + + const timeCond = data.time_conds[termIdx]; + const histParamObj = { + tabPrefix: eleIdPrefix, + canvasId: histogramId, + xLabel: displayColName, + yLabelKDE: i18n.yLabelKDE, + yLabelFreq: i18n.yLabelFreq, + kdeData, + sensorName: displayColName, + categoryValue, + threshHigh, + threshLow, + timeCond, + minY, + maxY, + prcMin, + prcMax, + sensorIdx, + }; + + // draw histogram + HistogramWithDensityCurve($, histParamObj); + } + // //////////////////////////////////// + // report progress + loadingUpdate(loadingProgressBackend + sensorIdx * ((100 - loadingProgressBackend) / (numSensors || 1))); + + // trigger to fix size of graph + $('.nav-item').off('click'); + $('.nav-item').on('click', (e) => { + $(`input[name=${eleIdPrefix}${eles.summaryOption}]:checked`).trigger('change'); + }); + } +}; + +const setNameWithPrefix = (prefix) => { + // change name of hist scale select + $('.scale-dropdown').attr('name', `${prefix}${eles.histScale}`); + // change name of summary option + $(`.${eles.summaryOption}`).attr('name', `${prefix}${eles.summaryOption}`); + + // bind change events for summary select menu + onChangeHistSummaryEventHandler(eles.varTabPrefix); + onChangeHistSummaryEventHandler(eles.cyclicTermTabPrefix); + onChangeHistSummaryEventHandler(eles.termTabPrefix); + onChangeHistScale(eles.varTabPrefix); + onChangeHistScale(eles.cyclicTermTabPrefix); + onChangeHistScale(eles.termTabPrefix); +}; + +const bindToChangeSensorItem = () => { + $('#tabs .nav-link.tab-name').on('click', (e) => { + let selectedOption; + const sensorID = $(e.currentTarget).data('sensor-id'); + if (scaleOptions[sensorID]) { + selectedOption = scaleOptions[sensorID]; + } else { + selectedOption = formElements.SCALE_DEFAULT_OPTION; // 1 is default option + } + }); +}; +const showGraph = () => { + const eleIdPrefix = $('select[name=compareType]').val(); + + const isValid = checkValidations({ max: MAX_END_PROC }); + updateStyleOfInvalidElements(); + if (!isValid) return; + + // close sidebar + beforeShowGraphCommon(); + + // show loading screen + loadingShow(); + + // reset sumary option + resetSummaryOption(`${eleIdPrefix}${eles.summaryOption}`); + + // collect form data + const traceForm = $(eles.mainFormId); + let formData = new FormData(traceForm[0]); + + formData = transformFacetParams(formData); + formData = transformDatetimeRange(formData); + // reformat form data + formData = reformatFormData(eleIdPrefix, formData); + formData = transformCategoryVariableParams(formData, procConfigs); + + // validate form + const validateFlg = isFormDataValid(eleIdPrefix, formData); + if (validateFlg === 4) { + // did not select catExpBox as endProcCate + loadingHide(); + showToastrMsg(i18n.selectFacet, i18n.warningTitle); + return; + } + + + if (validateFlg === 5) { + loadingHide(); + showToastrMsg(i18n.selectTooManyValue.replace('8', '1'), i18n.warningTitle); + return; + } + + if (validateFlg === 1) { + loadingHide(); + showToastrMsg(i18n.selectTooManyValue, i18n.warningTitle); + return; + } + if (validateFlg !== 0) { + loadingHide(); + showToastrAnomalGraph(); + return; + } + + $.ajax({ + url: '/histview2/api/stp/index', + data: formData, + dataType: 'json', + type: 'POST', + contentType: false, + processData: false, + timeout: REQUEST_TIMEOUT, + success: (res) => { + const t0 = performance.now(); + loadingShow(true); + + // set summary bar for prefix + setNameWithPrefix(eleIdPrefix); + // reset scale option + $(`select[name=${eleIdPrefix}HistScale]`).val(1); + // show result section + $('#categoricalPlotArea').show(); + + if (res.array_plotdata) { + if (eleIdPrefix === eles.varTabPrefix) { + currentTraceDataVar = res; + } else if (eleIdPrefix === eles.termTabPrefix) { + currentTraceDataTerm = res; + } else { + currentTraceDataCyclicTerm = res; + } + } + + // show graphs + // if (eleIdPrefix === eles.varTabPrefix || eleIdPrefix === eles.cyclicTermTabPrefix) { + showTabsAndCharts(eleIdPrefix, res, scaleOptionConst.SETTING); + + // Move screen to graph after pushing グラフ表示 button + $('html, body').animate({ + scrollTop: $(`#${eles.categoryPlotCards}`).offset().top, + }, 500); + + // check result and show toastr msg + if (isEmpty(res.array_plotdata) || isEmpty(Object.values(res.array_plotdata)[0])) { + showToastrAnomalGraph(); + } + + // show limit graphs displayed in one tab message + if (res.isGraphLimited) { + showToastrMsg(i18nCommon.limitDisplayedGraphsInOneTab.replace('NUMBER', MAX_NUMBER_OF_GRAPH)); + } + + bindToChangeSensorItem(); + // } else { + // // clear old content from card + // $('#CatePlotCards').html(''); + // // show tracedata for term + // traceDataChart(eleIdPrefix, res); + // + // // Move screen to graph after pushing グラフ表示 button + // $('html, body').animate({ + // scrollTop: $(`#${eles.categoryPlotCards}`).offset().top, + // }, 500); + // + // // check result and show toastr msg + // if (isEmpty(res.array_plotdata)) { + // showToastrAnomalGraph(); + // } + // } + + // show scatter plot tab + const imgFile = res.images; + if (imgFile) { + showScatterPlotImage(imgFile); + } + + const t1 = performance.now(); + // show processing time at bottom + drawProcessingTime(t0, t1, res.backend_time, res.actual_record_number); + + // move invalid filter + setColorAndSortHtmlEle(res.matched_filter_ids, res.unmatched_filter_ids, res.not_exact_match_filter_ids); + // if (checkResultExist(res)) { + // saveInvalidFilterCaller(true); + // } else { + // saveInvalidFilterCaller(); + // } + + // auto update + autoUpdate(formData); + + // hide loading inside ajax + setTimeout(loadingHide, loadingHideDelayTime(res.actual_record_number)); + + // drag & drop for tables + $('.ui-sortable').sortable(); + + // export mode + handleZipExport(res); + }, + error: (res) => { + loadingHide(); + errorHandling(res); + // export mode + handleZipExport(res); + }, + }).then(() => { + afterRequestAction(); + }); +}; diff --git a/histview2/static/categorical_plot/js/categorical_plot_utils.js b/histview2/static/categorical_plot/js/categorical_plot_utils.js new file mode 100644 index 0000000..4716cb9 --- /dev/null +++ b/histview2/static/categorical_plot/js/categorical_plot_utils.js @@ -0,0 +1,259 @@ +/* eslint-disable guard-for-in */ +/* eslint-disable no-restricted-syntax */ +const cyclicEles = { + datetimeFrom: $('#i18nDatetimeFrom').text(), + datetimeTo: $('#i18nDatetimeTo').text(), + datetimeRange: $('#i18nDatetimeRange').text(), + timestamp: $('#i18nTimestamp').text(), + windowLength: $('#i18nWindowLength').text(), + windowLengthHover: $('#i18nWindowLengthHover').text(), + numRL: $('#i18nNumRL').text(), + numRLHover: $('#i18nNumRLHover').text(), + interval: $('#i18nInterval').text(), + intervalHover: $('#i18nIntervalHover').text(), + formID: 'cyclicTermCategoricalPlotForm', + cyclicTermDivNum: 'cyclicTermDivNum', + cyclicTermInterval: 'cyclicTermInterval', + cyclicTermWindowLength: 'cyclicTermWindowLength', + START_DATE: 'START_DATE', + START_TIME: 'START_TIME', + END_DATE: 'END_DATE', + END_TIME: 'END_TIME', +}; + +const addLimitNumSensors = () => { + // validate number of selected show values + $(`${eles.checkBoxs}`).each(function f() { + $(this).on('change', () => { + const numSelectedCheckBoxes = $(`${eles.checkBoxs}:checked`).length; + if (numSelectedCheckBoxes < 1) { + $(eles.varShowGraphBtn).attr('disabled', true); + $(eles.varShowGraphBtn).css('cursor', 'not-allowed'); + } else { + $(eles.varShowGraphBtn).attr('disabled', false); + $(eles.varShowGraphBtn).css('cursor', 'default'); + } + }); + }); +}; + + +// generate HTML for tabs +const generateTabHTML = (eleIdPrefix, arrayFormVal, sensors, showViewer = false) => { + const genNavItemHTML = (tabId, sensorMasterName, status = '', sensorID = null) => ``; + + const genTabContentHTML = (tabId, plotCardId, status = '') => `
+
+
`; + + const navItemHTMLs = []; + const tabContentHTMLs = []; + for (let sensorIdx = 0; sensorIdx < sensors.length; sensorIdx++) { + // カラム名を取得する。 + const endProc = getEndProcFromFormVal(sensors[sensorIdx], arrayFormVal); + const sensorName = getColumnName(endProc, sensors[sensorIdx]); + + const sensorMasterName = getNode(procConfigs, [endProc, 'value_master', sensorName], sensorName) || sensorName; + + let status = ''; + if (sensorIdx === 0) { + status = 'active'; + } + const tabId = `${eleIdPrefix}HistogramsTab-${sensorIdx}`; + const sensorID = sensors[sensorIdx]; + const navItemHTML = genNavItemHTML(tabId, sensorMasterName, status, sensorID); + navItemHTMLs.push(navItemHTML); + const plotCardId = `${eleIdPrefix}CatePlotCards-${sensorIdx}`; + const tabContentHTML = genTabContentHTML(tabId, plotCardId, status); + tabContentHTMLs.push(tabContentHTML); + } + let viewerNavHTML = ''; + let viewerContentHTML = ''; + if (showViewer) { + viewerNavHTML = genNavItemHTML(tabId = 'scattersTab', sensorMasterName = i18n.viewerTabName); + viewerContentHTML = genTabContentHTML(tabId = 'scattersTab', plotCardId = 'varScatterPlotCards'); + } + + const stratifiedVarTabHTML = ` +
+ ${tabContentHTMLs.join(' ')} + ${viewerContentHTML} +
`; + + return stratifiedVarTabHTML; +}; + +const showResultTabHTMLs = (eleIdPrefix, arrayFormVal, sensors, showViewer = false) => { + const tabHTMLs = generateTabHTML(eleIdPrefix, arrayFormVal, sensors, showViewer); + const parentEle = `#${eles.categoryPlotCards}`; + + $(parentEle).html(); + $(parentEle).html(tabHTMLs); + + // show tooltip + $(`${parentEle} [data-toggle="tab"]`).tooltip({ + trigger: 'hover', + placement: 'top', + animate: true, + delay: 100, + container: 'body', + }); +}; + +const getEndProcFromFormVal = (sensorId, arrayFormval) => { + const proc = arrayFormval.filter(proc => proc.GET02_VALS_SELECT.includes(sensorId.toString())); + return proc[0].end_proc; +}; + +const isFormDataValid = (eleIdPrefix, formData) => { + if (eleIdPrefix === 'var' || eleIdPrefix === 'category') { + const endProcCat = formData.get('end_proc_cate1'); + if (!endProcCat || endProcCat === 'null') { + return 4; + } + } + + // check required keys + let requiredKeys = ['end_proc', eles.categoryVariableName, eles.categoryValueMulti]; + if (eleIdPrefix === 'directTerm' || eleIdPrefix === 'cyclicTerm') { + requiredKeys = ['end_proc']; + } + const formKeys = [...formData.keys()]; + for (const chkKey of requiredKeys) { + const result = formKeys.some(e => e.startsWith(chkKey)); + if (result === false) { + return 2; + } + } + + // check endProc = '---' + const endProc = formData.get('end_proc1'); + if (endProc === '') { + return 3; + } + + return 0; +}; + +const reformatFormData = (eleIdPrefix, formData) => { + let formatedFormData = formData; + + // add GET_CATE_VAL + if (eleIdPrefix === eles.varTabPrefix) { + const endProcCate = formatedFormData.get('categoryVariable1'); + formatedFormData.set('GET02_CATE_SELECT1', endProcCate); + } + + if (eleIdPrefix === eles.cyclicTermTabPrefix) { + formatedFormData = chooseCyclicTraceTimeInterval(formatedFormData); + } else if (eleIdPrefix === eles.varTabPrefix) { + formatedFormData = chooseTraceTimeIntervals(formatedFormData); + } + // convert to UTC datetime to query + formatedFormData = convertFormDateTimeToUTC(formatedFormData); + return formatedFormData; +}; + +const getChartThreshHolds = (tabPrefix, traceData, chartIdx, sensorId = null) => { + if ([eles.varTabPrefix, eles.cyclicTermTabPrefix].includes(tabPrefix)) { + const arrayY = traceData.array_plotdata[sensorId][chartIdx].array_y; + const setYMax = traceData.array_plotdata[sensorId][chartIdx]['y-max']; + const setYMin = traceData.array_plotdata[sensorId][chartIdx]['y-min']; + const chartInfos = traceData.array_plotdata[sensorId][chartIdx].chart_infos || []; + const [latestChartInfo, latestIndex] = chooseLatestThresholds(chartInfos); + const corrSummary = traceData.array_plotdata[sensorId][chartIdx].summaries[latestIndex] || {}; + return { + arrayY, setYMax, setYMin, latestChartInfo, corrSummary, + }; + } + const arrayY = traceData.array_plotdata[chartIdx].array_y; + const setYMax = traceData.array_plotdata[chartIdx]['y-max']; + const setYMin = traceData.array_plotdata[chartIdx]['y-min']; + const chartInfos = traceData.array_plotdata[chartIdx].chart_infos || []; + const [latestChartInfo, latestIndex] = chooseLatestThresholds(chartInfos); + const corrSummary = traceData.array_plotdata[chartIdx].summaries[latestIndex] || {}; + return { + arrayY, setYMax, setYMin, latestChartInfo, corrSummary, + }; +}; + +const concatAllArrayY = (tabPrefix, traceData, numChart, sensorID = '') => { + let arrayPlotdataY = []; + for (let idx = 0; idx < numChart; idx++) { + if (tabPrefix === eles.varTabPrefix) { + arrayPlotdataY = arrayPlotdataY.concat(traceData.array_plotdata[sensorID][idx].array_y); + } else if (tabPrefix === eles.termTabPrefix) { + arrayPlotdataY = arrayPlotdataY.concat(traceData.array_plotdata[idx].array_y); + } + } + arrayPlotdataY = arrayPlotdataY.filter(e => $.isNumeric(e)); + return arrayPlotdataY; +}; + +const updateHistogramWhenChaneScale = (currentTraceData, scaleOption = '1', sensorID = null, tabPrefix = '') => { + if (tabPrefix === eles.varTabPrefix && !sensorID) { + return; + } + + let numChart = 0; + if ([eles.varTabPrefix, eles.cyclicTermTabPrefix].includes(tabPrefix)) { + numChart = currentTraceData.array_plotdata[sensorID].length; + } else { + numChart = currentTraceData.array_plotdata.length; + } + if (!numChart) return; + + + for (let i = 0; i < numChart; i++) { + const scaleInfo = getScaleInfo(currentTraceData.array_plotdata[sensorID][i], scaleOption); + const kdeData = scaleInfo.kde_data; + const yMax = scaleInfo['y-max']; + const yMin = scaleInfo['y-min']; + + const kdeDensity = { + y: kdeData.hist_labels, + x: kdeData.kde, + }; + + const histogram = { + y: kdeData.hist_labels, + x: kdeData.kde, + ybins: { end: yMax, size: 128, start: yMin }, + }; + const dataUpdate = [ + histogram, + kdeDensity, + ]; + + const canvasId = `${tabPrefix}-${sensorID}-Histograms${i + 1}`; + Plotly.update(canvasId, dataUpdate); + } +}; + +const shouldCreateNewSSESource = (formData) => { + if (is_sse_listening) { + return false; + } + + const autoUpdateInterval = formData.get('autoUpdateInterval'); + if (autoUpdateInterval) { + is_sse_listening = true; + return true; + } + return false; +}; + +const autoUpdate = (formData) => { + if (shouldCreateNewSSESource(formData)) { + const source = openServerSentEvent(); + source.addEventListener(serverSentEventType.procLink, (event) => { + $(`${eles.mainFormId} button.show-graph`).click(); + }, false); + } +}; diff --git a/histview2/static/co_occurrence/css/co_occurrence.css b/histview2/static/co_occurrence/css/co_occurrence.css new file mode 100644 index 0000000..27e4eac --- /dev/null +++ b/histview2/static/co_occurrence/css/co_occurrence.css @@ -0,0 +1,70 @@ +#plot-card { + margin-top: 20px; + display: table; + border-collapse: collapse; + height: 80vh; + width: inherit +} + +.node-label-text-path { + fill: #FFFFFF !important; + text-shadow: none !important; +} + +.coef-item { + border: 1px solid #444444; +} + +.sc-row { + display: table-row; +} + +.sc-col { + display: table-cell; + border: 1px solid #4e4e4e; + width: auto; + padding: 10px; +} + +.sc-col span { + color: #60b5dd; +} + +canvas { + /*max-width: 95%;*/ + margin: 0 auto; +} + +.chart-column { + float: left; + border: 1px solid #4e4e4e; +} + +.chart-column-border { + float: left; + border: 1px solid #65c5f1; +} + +.chart-column-border { + float: left; + border: 1px solid #65c5f1; +} + +/* Clear floats after the columns */ +.chart-row:after { + content: ""; + display: table; + clear: both; +} + +.center { + margin: 0; + position: relative; + top: 50%; + left: 50%; + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} +.file-selection-group { + margin-top: 10px; +} \ No newline at end of file diff --git a/histview2/static/co_occurrence/css/co_occurrence_csv.css b/histview2/static/co_occurrence/css/co_occurrence_csv.css new file mode 100644 index 0000000..bc54b19 --- /dev/null +++ b/histview2/static/co_occurrence/css/co_occurrence_csv.css @@ -0,0 +1,56 @@ +#plot-card { + height: 80vh; + display: none; +} + +#plotCard { + display: none; +} + +.form-group { + margin-bottom: 0 !important; +} + +.edge-hover { + text-decoration:none; + position:relative; +} + +.edge-hover span { + display:none; + position:fixed; + overflow:hidden; + color: #00aeff; + font-size: 11px; + font-weight: bold; +} + +.edge-hover:hover span { + display:block; + position:fixed; + overflow:hidden; +} + +.coo { + height: 83vh !important; +} + +.btn-control{ + text-align: right; + padding: 0.5rem 0 0 0; +} +.collapse-option { + float: right; + /*text-decoration: underline;*/ +} +.file-selection-group { + margin: 10px 0 !important; +} +.drag-card { + border: none; + height: 250px; + padding: 0; +} +#fileSelection { + /*border: 3px dotted #444444;*/ +} diff --git a/histview2/static/co_occurrence/js/co_occurrence_csv.js b/histview2/static/co_occurrence/js/co_occurrence_csv.js new file mode 100644 index 0000000..229f0e9 --- /dev/null +++ b/histview2/static/co_occurrence/js/co_occurrence_csv.js @@ -0,0 +1,289 @@ +/* eslint-disable prefer-const */ +const REQUEST_TIMEOUT = setRequestTimeOut(60000); // 1 minutes +let settingFile = false; +const coOccElements = { + apiCheckFileUrl: '/histview2/api/cog/check_file', + apiShowGraphUrl: '/histview2/api/cog/show_graph', + fileUrlInput: 'input[name="fileUrl"]', + connectResourceBtn: '#connectResourceBtn', + i18nFileExist: 'i18nFileExist', + i18nFileNotExist: 'i18nFileNotExist', + alertMsgCheckFile: '#alertMsgCheckFile', + showGraphBtn: '#showGraphBtn', + delimiter: 'input[type="radio"][name="fileType"]:checked', + aggregateBy: 'input[type="radio"][name="aggregateBy"]:checked', + threshold: 'input[name="threshold"]', + layout: 'input[type="radio"][name="layout"]:checked', + plotCard: $('#plotCard'), + plotCOG: $('#plot-card'), // TODO name + plotCOGId: 'plot-card', + plotPareto: $('#plotCardPareto'), + plotParetoId: 'plotCardPareto', + tooltipSpan: $('#tooltip-span'), + restoreZoom: $('#restoreZoom'), +}; + +const i18nCoOccCfg = { + subFolderWrongFormat: $('#i18nSubFolderWrongFormat').text(), + fileExist: $(`#${coOccElements.i18nFileExist}`).text(), + fileNotExist: $(`#${coOccElements.i18nFileNotExist}`).text(), +}; + +const HttpStatusCode = { + isOk: 200, + serverErr: 500, +}; + +const loading = $('.loading'); + +let s = null; +const cam = null; + +const showCoOccurrencePlot = (nodes, edges) => { + const N = nodes.length; + const E = edges.length; + + const g = { + nodes: [], + edges: [], + }; + + // Generate a random graph: + for (let i = 0; i < N; i++) { + const node = nodes[i]; + g.nodes.push({ + id: node.id, + label: `${node.id}: ${node.size}`, + x: node.x, + y: node.y, + size: node.size, + color: node.color || '#a9a9a9', + }); + } + + for (let i = 0; i < E; i++) { + const edge = edges[i]; + if (`${edge.label}` === '0') continue; + + g.edges.push({ + id: edge.id, + label: `${edge.label}`, + source: edge.source, + target: edge.target, + size: edge.label, + color: '#a9a9a9', + hover_color: '#00aeff', + type: 'line', + }); + } + + // Instantiate sigma: + s = new sigma({ + graph: g, + renderer: { + container: document.getElementById(coOccElements.plotCOGId), + type: 'canvas', + }, + settings: { + edgeLabelSize: 'proportional', + defaultLabelColor: '#fff', + defaultEdgeLabelColor: '#fff', + defaultLabelSize: 16, + minEdgeSize: 0.05, + maxEdgeSize: 5, + minNodeSize: 5, + maxNodeSize: 20, + enableEdgeHovering: true, + edgeHoverColor: 'edge', + defaultEdgeHoverColor: '#00aeff', + edgeHoverSizeRatio: 1, + edgeHoverExtremities: true, + borderSize: 2, + outerBorderSize: 2, + defaultNodeOuterBorderColor: '#fff', + edgeHoverHighlightNodes: 'circle', + sideMargin: 1, + // scalingMode: 'outside', // TODO later + }, + }); + + s.bind('clickEdge', (e) => { + const x = e.data.captor.clientX; + const y = e.data.captor.clientY; // todo check null + if (y && x) { + coOccElements.tooltipSpan.text(e.data.edge.label); + coOccElements.tooltipSpan.css('top', `${(y - 10)}px`); + coOccElements.tooltipSpan.css('left', `${(x + 10)}px`); + coOccElements.tooltipSpan.css('display', 'block'); + } + }); + + s.bind('outEdge', (e) => { + coOccElements.tooltipSpan.css('display', 'none'); + }); + + + s.bind('nodeClick', (e) => { + coOccElements.tooltipSpan.css('display', 'none'); + }); + + if ($(coOccElements.layout).val() === 'FORCE_ATLAS_2') { + s.startForceAtlas2({ worker: true, barnesHutOptimize: false }); + + setTimeout(() => { + s.stopForceAtlas2(); + }, 100); + + const config = { + nodeMargin: 3.0, + scaleNodes: 1.3, + gravity: 1, + }; + + // Configure the algorithm + const listener = s.configNoverlap(config); + + // Bind all events: + listener.bind('start stop interpolate', (event) => { + // console.log(event.type); // TODO + }); + + // Start the algorithm: + s.startNoverlap(); + } + + // Initialize the dragNodes plugin: + const dragListener = sigma.plugins.dragNodes(s, s.renderers[0]); + + // dragListener.bind('startdrag', (event) => { + // console.log(event); + // }); + // dragListener.bind('drag', (event) => { + // console.log(event); + // }); + // dragListener.bind('drop', (event) => { + // console.log(event); + // }); + // dragListener.bind('dragend', (event) => { + // console.log(event); + // }); + + + $('html, body').animate({ + scrollTop: coOccElements.plotCard.offset().top, + }, 200); + + + coOccElements.restoreZoom.on('click', () => { + s.cameras[0].goTo({ + x: 0, y: 0, angle: 0, ratio: 1, + }); + }); +}; + +// check to use upload setting file from browser +const useUploadFile = () => { + if (settingFile.name) { + return settingFile; + } + return null; +}; + +const showGraph = () => { + loadingShow(); + + // close sidebar + beforeShowGraphCommon(); + + coOccElements.plotCard.hide(); + coOccElements.plotCOG.html(''); + coOccElements.plotPareto.html(''); + + // const fileUrl = $(coOccElements.fileUrlInput).val(); + // check uploaded file from browser + const fileUpload = useUploadFile(); + const formDat = new FormData(); + formDat.append('url', ''); + formDat.append('delimiter', $(coOccElements.delimiter).val()); + formDat.append('aggregate_by', $(coOccElements.aggregateBy).val()); + formDat.append('threshold', $(coOccElements.threshold).val()); + formDat.append('layout', $(coOccElements.layout).val()); + formDat.append('file', fileUpload); + $.ajax({ + url: coOccElements.apiShowGraphUrl, + method: 'POST', + data: formDat, + // contentType: 'application/json', + contentType: false, + enctype: 'multipart/form-data', + processData: false, + timeout: REQUEST_TIMEOUT, + success: (res) => { + loadingShow(true); + + coOccElements.plotCard.show(); + coOccElements.plotCOG.show(); + + const dicRes = JSON.parse(res); + showCoOccurrencePlot(dicRes.nodes, dicRes.edges); + loadingUpdate(75); + showParetoPlot(dicRes.pareto); + + // move invalid filter + // setColorAndSortHtmlEle(res.matched_filter_ids, res.unmatched_filter_ids, res.not_exact_match_filter_ids); + // if (checkResultExist(res)) { + // saveInvalidFilterCaller(true); + // } else { + // saveInvalidFilterCaller(); + // } + + // hide loading inside ajax + setTimeout(loadingHide, loadingHideDelayTime(res.actual_record_number)); + + // export mode + handleZipExport(res); + }, + error: (res) => { + loadingHide(); + errorHandling(res); + // export mode + handleZipExport(res); + }, + }).then(() => { + loadingHide(); + }); +}; + +const showParetoPlot = (paretoData) => { + const prop = paretoData; + prop.plotId = coOccElements.plotParetoId; + paretoPlot(prop); +}; + +function bindUpdateThresholdValue() { + const thresholdValue = $('#thresholdValue'); + const threshold = $('#threshold'); + thresholdValue.html(100); + threshold.on('input change', () => { + thresholdValue.text(threshold.val()); + }); +} + + +$(() => { + const dragAreaCls = '.co-occurrence-drag-area'; + const selectFileBtnId = '#selectFileBtn'; + const selectFileInputId = '#selectFileInput'; + genTriggerFileSetting(dragAreaCls, selectFileBtnId, selectFileInputId); + + loading.addClass('hide'); + + $(coOccElements.showGraphBtn).on('click', () => { + showGraph(); + }); + + bindUpdateThresholdValue(); + + // Load userBookmarkBar + $('#userBookmarkBar').show(); +}); diff --git a/histview2/static/co_occurrence/js/pareto_plot.js b/histview2/static/co_occurrence/js/pareto_plot.js new file mode 100644 index 0000000..7cd3a7d --- /dev/null +++ b/histview2/static/co_occurrence/js/pareto_plot.js @@ -0,0 +1,94 @@ +const paretoPlot = (prop) => { + const alarmNames = prop.bar.y || []; + const totalOccurenceBar = { + y: alarmNames.reverse(), + x: prop.bar.x.reverse(), + text: prop.bar.text.reverse(), + name: prop.bar.name, + marker: { + color: prop.bar.marker_color.reverse(), + }, + type: 'bar', + orientation: prop.bar.orientation, + hovertemplate: '%{y}:%{text}', + textposition: 'outside', + textfont: { + color: '#ffffff', + } + }; + + const lineCumRatio = { + y: alarmNames, + x: prop.line_cum_ratio.x.reverse(), + name: prop.line_cum_ratio.name, + text: prop.line_cum_ratio.text.reverse(), + mode: 'lines+markers', + hovertemplate: '%{y}:%{text:.2f} %', + }; + + const line80Percent = { + y: alarmNames, + x: prop.line_80_percent.x.reverse(), + name: prop.line_80_percent.name, + mode: 'lines', + line: { + color: prop.line_80_percent.marker_color, + }, + hoverinfo: 'skip', + }; + + const data = [totalOccurenceBar, lineCumRatio, line80Percent]; + + const layout = { + showlegend: true, + legend: { + x: 0.9, + y: 0.1, + xanchor: 'right', + bgcolor: 'rgba(0,0,0,0)', + font: { + color: '#ffffff', + size: 12, + }, + autorange: 'reversed', + }, + annotations: [], + xaxis: { + autorange: true, + gridcolor: '#444444', + tickfont: { + color: 'rgba(255,255,255,1)', + size: 12, + }, + }, + yaxis: { + autorange: true, + autotick: false, + gridcolor: '#444444', + tickfont: { + color: 'rgba(255,255,255,1)', + size: 12, + }, + }, + plot_bgcolor: '#303030', + paper_bgcolor: '#303030', + margin: { + r: 30, + b: 20, + t: 20, + pad: 5, + }, + hovermode: 'closest', + autosize: true, // responsive histogram + }; + + Plotly.newPlot(prop.plotId, data, layout, { + ...genPlotlyIconSettings(), + displaylogo: false, + responsive: true, + useResizeHandler: true, + style: {width: '100%', height: '100%'}, + }); + + +} \ No newline at end of file diff --git a/histview2/static/common/css/all.min.css b/histview2/static/common/css/all.min.css new file mode 100644 index 0000000..8465128 --- /dev/null +++ b/histview2/static/common/css/all.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;font-display:auto;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/histview2/static/common/css/bootstrap-select.min.css b/histview2/static/common/css/bootstrap-select.min.css new file mode 100644 index 0000000..9d96ebb --- /dev/null +++ b/histview2/static/common/css/bootstrap-select.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap-select v1.12.4 (http://silviomoreto.github.io/bootstrap-select) + * + * Copyright 2013-2017 bootstrap-select + * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) + */select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\9}.bootstrap-select>.dropdown-toggle{width:100%;padding-right:25px;z-index:1}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2}.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.form-control.input-group-btn:not(:first-child):not(:last-child)>.btn{border-radius:0}.bootstrap-select.btn-group:not(.input-group-btn),.bootstrap-select.btn-group[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.btn-group.dropdown-menu-right,.bootstrap-select.btn-group[class*=col-].dropdown-menu-right,.row .bootstrap-select.btn-group[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group{margin-bottom:0}.form-group-lg .bootstrap-select.btn-group.form-control,.form-group-sm .bootstrap-select.btn-group.form-control{padding:0}.form-group-lg .bootstrap-select.btn-group.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.btn-group.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.form-inline .bootstrap-select.btn-group .form-control{width:100%}.bootstrap-select.btn-group.disabled,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group.disabled:focus,.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group.bs-container{position:absolute;height:0!important;padding:0!important}.bootstrap-select.btn-group.bs-container .dropdown-menu{z-index:1060}.bootstrap-select.btn-group .dropdown-toggle .filter-option{display:inline-block;overflow:hidden;width:100%;text-align:left}.bootstrap-select.btn-group .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li.active small{color:#fff}.bootstrap-select.btn-group .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select.btn-group .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select.btn-group .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select.btn-group .dropdown-menu li a span.check-mark{display:none}.bootstrap-select.btn-group .dropdown-menu li a span.text{display:inline-block}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.btn-group.fit-width .dropdown-toggle .filter-option{position:static}.bootstrap-select.btn-group.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark{position:absolute;display:inline-block;right:15px;margin-top:5px}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none} \ No newline at end of file diff --git a/histview2/static/common/css/bootstrap-table.min.css b/histview2/static/common/css/bootstrap-table.min.css new file mode 100644 index 0000000..7a591bd --- /dev/null +++ b/histview2/static/common/css/bootstrap-table.min.css @@ -0,0 +1,10 @@ +/** + * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) + * + * @version v1.16.0 + * @homepage https://bootstrap-table.com + * @author wenzhixin (http://wenzhixin.net.cn/) + * @license MIT + */ + +.bootstrap-table .fixed-table-toolbar::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-toolbar .bs-bars,.bootstrap-table .fixed-table-toolbar .columns,.bootstrap-table .fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group>.btn{border-radius:0}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu{text-align:left;max-height:300px;overflow:auto;-ms-overflow-style:scrollbar;z-index:1001}.bootstrap-table .fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429}.bootstrap-table .fixed-table-toolbar .columns-left{margin-right:5px}.bootstrap-table .fixed-table-toolbar .columns-right{margin-left:5px}.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu{right:0;left:auto}.bootstrap-table .fixed-table-container{position:relative;clear:both}.bootstrap-table .fixed-table-container .table{width:100%;margin-bottom:0!important}.bootstrap-table .fixed-table-container .table td,.bootstrap-table .fixed-table-container .table th{vertical-align:middle;box-sizing:border-box}.bootstrap-table .fixed-table-container .table thead th{vertical-align:bottom;padding:0;margin:0}.bootstrap-table .fixed-table-container .table thead th:focus{outline:0 solid transparent}.bootstrap-table .fixed-table-container .table thead th.detail{width:30px}.bootstrap-table .fixed-table-container .table thead th .th-inner{padding:.75rem;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bootstrap-table .fixed-table-container .table thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px!important}.bootstrap-table .fixed-table-container .table thead th .both{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC")}.bootstrap-table .fixed-table-container .table thead th .asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==)}.bootstrap-table .fixed-table-container .table thead th .desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=)}.bootstrap-table .fixed-table-container .table tbody tr.selected td{background-color:rgba(0,0,0,.075)}.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td{text-align:center}.bootstrap-table .fixed-table-container .table tbody tr .card-view{display:flex}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title{font-weight:700;display:inline-block;min-width:30%;text-align:left!important}.bootstrap-table .fixed-table-container .table .bs-checkbox{text-align:center}.bootstrap-table .fixed-table-container .table .bs-checkbox label{margin-bottom:0}.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox],.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio]{margin:0 auto!important}.bootstrap-table .fixed-table-container .table.table-sm .th-inner{padding:.3rem}.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer){border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height.has-card-view{border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border{border-left:1px solid #dee2e6;border-right:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table thead th{border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th{border-bottom:1px solid #32383e}.bootstrap-table .fixed-table-container .fixed-table-header{overflow:hidden}.bootstrap-table .fixed-table-container .fixed-table-body{overflow-x:auto;overflow-y:auto;height:100%}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading{align-items:center;background:#fff;display:none;justify-content:center;position:absolute;bottom:0;width:100%;z-index:1000}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap{align-items:baseline;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text{font-size:2rem;margin-right:6px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap{align-items:center;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before{content:"";animation-duration:1.5s;animation-iteration-count:infinite;animation-name:LOADING;background:#212529;border-radius:50%;display:block;height:5px;margin:0 4px;opacity:0;width:5px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot{animation-delay:.3s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after{animation-delay:.6s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark{background:#212529}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before{background:#fff}.bootstrap-table .fixed-table-container .fixed-table-footer{overflow:hidden}.bootstrap-table .fixed-table-pagination::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-pagination>.pagination,.bootstrap-table .fixed-table-pagination>.pagination-detail{margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-pagination>.pagination-detail .pagination-info{line-height:34px;margin-right:5px}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list{display:inline-block}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group{position:relative;display:inline-block;vertical-align:middle}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group .dropdown-menu{margin-bottom:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination{margin:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination a{padding:6px 12px;line-height:1.428571429}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a{color:#c8c8c8}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::before{content:'\2B05'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::after{content:'\27A1'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.disabled a{pointer-events:none;cursor:default}.bootstrap-table.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100%!important;background:#fff;height:calc(100vh);overflow-y:scroll}div.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}@keyframes LOADING{0%{opacity:0}50%{opacity:1}to{opacity:0}} \ No newline at end of file diff --git a/histview2/static/common/css/bootstrap-theme.css b/histview2/static/common/css/bootstrap-theme.css new file mode 100644 index 0000000..0b65e79 --- /dev/null +++ b/histview2/static/common/css/bootstrap-theme.css @@ -0,0 +1,587 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + 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-default:active, +.btn-primary:active, +.btn-success:active, +.btn-info:active, +.btn-warning:active, +.btn-danger:active, +.btn-default.active, +.btn-primary.active, +.btn-success.active, +.btn-info.active, +.btn-warning.active, +.btn-danger.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-default.disabled, +.btn-primary.disabled, +.btn-success.disabled, +.btn-info.disabled, +.btn-warning.disabled, +.btn-danger.disabled, +.btn-default[disabled], +.btn-primary[disabled], +.btn-success[disabled], +.btn-info[disabled], +.btn-warning[disabled], +.btn-danger[disabled], +fieldset[disabled] .btn-default, +fieldset[disabled] .btn-primary, +fieldset[disabled] .btn-success, +fieldset[disabled] .btn-info, +fieldset[disabled] .btn-warning, +fieldset[disabled] .btn-danger { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-default .badge, +.btn-primary .badge, +.btn-success .badge, +.btn-info .badge, +.btn-warning .badge, +.btn-danger .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:hover, +.btn-default:focus { + 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], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + 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:hover, +.btn-primary:focus { + 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], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + 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:hover, +.btn-success:focus { + 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], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + 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:hover, +.btn-info:focus { + 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], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + 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:hover, +.btn-warning:focus { + 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], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + 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:hover, +.btn-danger:focus { + 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], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #c12e2a; + background-image: none; +} +.thumbnail, +.img-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:hover, +.dropdown-menu > li > a:focus { + 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:hover, +.dropdown-menu > .active > a:focus { + 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 > .open > a, +.navbar-default .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); + background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255, 255, 255, .25); +} +.navbar-inverse { + background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); + background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-radius: 4px; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .active > 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-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} +@media (max-width: 767px) { + .navbar .navbar-nav .open .dropdown-menu > .active > a, + .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { + 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:hover, +.list-group-item.active:focus { + 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:hover .badge, +.list-group-item.active:focus .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); +} +/*# sourceMappingURL=bootstrap-theme.css.map */ diff --git a/histview2/static/common/css/bootstrap-theme.css.map b/histview2/static/common/css/bootstrap-theme.css.map new file mode 100644 index 0000000..d876f60 --- /dev/null +++ b/histview2/static/common/css/bootstrap-theme.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap-theme.css","less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAAA;;;;GAIG;ACeH;;;;;;EAME,yCAAA;EC2CA,4FAAA;EACQ,oFAAA;CFvDT;ACgBC;;;;;;;;;;;;ECsCA,yDAAA;EACQ,iDAAA;CFxCT;ACMC;;;;;;;;;;;;;;;;;;ECiCA,yBAAA;EACQ,iBAAA;CFnBT;AC/BD;;;;;;EAuBI,kBAAA;CDgBH;ACyBC;;EAEE,uBAAA;CDvBH;AC4BD;EErEI,sEAAA;EACA,iEAAA;EACA,2FAAA;EAAA,oEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;EAuC2C,0BAAA;EAA2B,mBAAA;CDjBvE;ACpBC;;EAEE,0BAAA;EACA,6BAAA;CDsBH;ACnBC;;EAEE,0BAAA;EACA,sBAAA;CDqBH;ACfG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD6BL;ACbD;EEtEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CD8DD;AC5DC;;EAEE,0BAAA;EACA,6BAAA;CD8DH;AC3DC;;EAEE,0BAAA;EACA,sBAAA;CD6DH;ACvDG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDqEL;ACpDD;EEvEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CDsGD;ACpGC;;EAEE,0BAAA;EACA,6BAAA;CDsGH;ACnGC;;EAEE,0BAAA;EACA,sBAAA;CDqGH;AC/FG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD6GL;AC3FD;EExEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CD8ID;AC5IC;;EAEE,0BAAA;EACA,6BAAA;CD8IH;AC3IC;;EAEE,0BAAA;EACA,sBAAA;CD6IH;ACvIG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDqJL;AClID;EEzEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CDsLD;ACpLC;;EAEE,0BAAA;EACA,6BAAA;CDsLH;ACnLC;;EAEE,0BAAA;EACA,sBAAA;CDqLH;AC/KG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD6LL;ACzKD;EE1EI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CD8ND;AC5NC;;EAEE,0BAAA;EACA,6BAAA;CD8NH;AC3NC;;EAEE,0BAAA;EACA,sBAAA;CD6NH;ACvNG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDqOL;AC1MD;;EClCE,mDAAA;EACQ,2CAAA;CFgPT;ACrMD;;EE3FI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF0FF,0BAAA;CD2MD;ACzMD;;;EEhGI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EFgGF,0BAAA;CD+MD;ACtMD;EE7GI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;ECnBF,oEAAA;EH+HA,mBAAA;ECjEA,4FAAA;EACQ,oFAAA;CF8QT;ACjND;;EE7GI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;ED2CF,yDAAA;EACQ,iDAAA;CFwRT;AC9MD;;EAEE,+CAAA;CDgND;AC5MD;EEhII,sEAAA;EACA,iEAAA;EACA,2FAAA;EAAA,oEAAA;EACA,4BAAA;EACA,uHAAA;ECnBF,oEAAA;EHkJA,mBAAA;CDkND;ACrND;;EEhII,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;ED2CF,wDAAA;EACQ,gDAAA;CF+ST;AC/ND;;EAYI,0CAAA;CDuNH;AClND;;;EAGE,iBAAA;CDoND;AC/LD;EAfI;;;IAGE,YAAA;IE7JF,yEAAA;IACA,oEAAA;IACA,8FAAA;IAAA,uEAAA;IACA,4BAAA;IACA,uHAAA;GH+WD;CACF;AC3MD;EACE,8CAAA;EC3HA,2FAAA;EACQ,mFAAA;CFyUT;ACnMD;EEtLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CD+MD;AC1MD;EEvLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CDuND;ACjND;EExLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CD+ND;ACxND;EEzLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CDuOD;ACxND;EEjMI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH4ZH;ACrND;EE3MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHmaH;AC3ND;EE5MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH0aH;ACjOD;EE7MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHibH;ACvOD;EE9MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHwbH;AC7OD;EE/MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH+bH;AChPD;EElLI,8MAAA;EACA,yMAAA;EACA,sMAAA;CHqaH;AC5OD;EACE,mBAAA;EC9KA,mDAAA;EACQ,2CAAA;CF6ZT;AC7OD;;;EAGE,8BAAA;EEnOE,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EFiOF,sBAAA;CDmPD;ACxPD;;;EAQI,kBAAA;CDqPH;AC3OD;ECnME,kDAAA;EACQ,0CAAA;CFibT;ACrOD;EE5PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHoeH;AC3OD;EE7PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH2eH;ACjPD;EE9PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHkfH;ACvPD;EE/PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHyfH;AC7PD;EEhQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHggBH;ACnQD;EEjQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHugBH;ACnQD;EExQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EFsQF,sBAAA;EC3NA,0FAAA;EACQ,kFAAA;CFqeT","file":"bootstrap-theme.css","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default.disabled,\n.btn-primary.disabled,\n.btn-success.disabled,\n.btn-info.disabled,\n.btn-warning.disabled,\n.btn-danger.disabled,\n.btn-default[disabled],\n.btn-primary[disabled],\n.btn-success[disabled],\n.btn-info[disabled],\n.btn-warning[disabled],\n.btn-danger[disabled],\nfieldset[disabled] .btn-default,\nfieldset[disabled] .btn-primary,\nfieldset[disabled] .btn-success,\nfieldset[disabled] .btn-info,\nfieldset[disabled] .btn-warning,\nfieldset[disabled] .btn-danger {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} \ No newline at end of file diff --git a/histview2/static/common/css/bootstrap-theme.min.css b/histview2/static/common/css/bootstrap-theme.min.css new file mode 100644 index 0000000..88f27eb --- /dev/null +++ b/histview2/static/common/css/bootstrap-theme.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} +/*# sourceMappingURL=bootstrap-theme.min.css.map */ \ No newline at end of file diff --git a/histview2/static/common/css/bootstrap-theme.min.css.map b/histview2/static/common/css/bootstrap-theme.min.css.map new file mode 100644 index 0000000..94813e9 --- /dev/null +++ b/histview2/static/common/css/bootstrap-theme.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} \ No newline at end of file diff --git a/histview2/static/common/css/bootstrap.css b/histview2/static/common/css/bootstrap.css new file mode 100644 index 0000000..b41fac3 --- /dev/null +++ b/histview2/static/common/css/bootstrap.css @@ -0,0 +1,6758 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + border-color: #444444 !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + padding: .2em; + background-color: #fcf8e3; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777; +} +.text-primary { + color: #337ab7; +} +a.text-primary:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover, +a.text-danger:focus { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + min-height: .01%; + overflow-x: auto; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #eee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + min-height: 34px; + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 46px; + line-height: 46px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + min-height: 38px; + padding: 11px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 11px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:focus, +.btn-default.focus { + color: #333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333; + background-color: #d4d4d4; + border-color: #8c8c8c; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:focus, +.btn-primary.focus { + color: #fff; + background-color: #286090; + border-color: #122b40; +} +.btn-primary:hover { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #fff; + background-color: #204d74; + border-color: #122b40; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #337ab7; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus, +.btn-success.focus { + color: #fff; + background-color: #449d44; + border-color: #255625; +} +.btn-success:hover { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #fff; + background-color: #398439; + border-color: #255625; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus, +.btn-info.focus { + color: #fff; + background-color: #31b0d5; + border-color: #1b6d85; +} +.btn-info:hover { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #fff; + background-color: #269abc; + border-color: #1b6d85; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:focus, +.btn-warning.focus { + color: #fff; + background-color: #ec971f; + border-color: #985f0d; +} +.btn-warning:hover { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #fff; + background-color: #d58512; + border-color: #985f0d; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:focus, +.btn-danger.focus { + color: #fff; + background-color: #c9302c; + border-color: #761c19; +} +.btn-danger:hover { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #fff; + background-color: #ac2925; + border-color: #761c19; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + color: #337ab7; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #337ab7; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group .form-control:focus { + z-index: 3; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eee; +} +.nav > li.disabled > a { + color: #777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + border-color: #337ab7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #337ab7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: middle; + background-color: #777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #337ab7; + background-color: #fff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + padding-right: 15px; + padding-left: 15px; + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border .2s ease-in-out; + -o-transition: border .2s ease-in-out; + transition: border .2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7; +} +.thumbnail .caption { + padding: 9px; + color: #333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar, +.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); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + 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); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + 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); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + 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); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + 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); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +a.list-group-item, +button.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success, +button.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info, +button.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-right: 15px; + padding-left: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #337ab7; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #337ab7; +} +.panel-primary > .panel-heading .badge { + color: #337ab7; + background-color: #fff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #337ab7; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + filter: alpha(opacity=0); + opacity: 0; + + line-break: auto; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + right: 5px; + bottom: 0; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + + line-break: auto; +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform .6s ease-in-out; + -o-transition: -o-transform .6s ease-in-out; + transition: transform .6s ease-in-out; + + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + left: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + background-color: rgba(0, 0, 0, 0); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + margin-top: -10px; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + font-family: serif; + line-height: 1; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -10px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -10px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -10px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-header:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/histview2/static/common/css/bootstrap.css.map b/histview2/static/common/css/bootstrap.css.map new file mode 100644 index 0000000..f010c82 --- /dev/null +++ b/histview2/static/common/css/bootstrap.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap.css","less/normalize.less","less/print.less","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/mixins/reset-text.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,4EAA4E;ACG5E;EACE,wBAAA;EACA,2BAAA;EACA,+BAAA;CDDD;ACQD;EACE,UAAA;CDND;ACmBD;;;;;;;;;;;;;EAaE,eAAA;CDjBD;ACyBD;;;;EAIE,sBAAA;EACA,yBAAA;CDvBD;AC+BD;EACE,cAAA;EACA,UAAA;CD7BD;ACqCD;;EAEE,cAAA;CDnCD;AC6CD;EACE,8BAAA;CD3CD;ACmDD;;EAEE,WAAA;CDjDD;AC2DD;EACE,0BAAA;CDzDD;ACgED;;EAEE,kBAAA;CD9DD;ACqED;EACE,mBAAA;CDnED;AC2ED;EACE,eAAA;EACA,iBAAA;CDzED;ACgFD;EACE,iBAAA;EACA,YAAA;CD9ED;ACqFD;EACE,eAAA;CDnFD;AC0FD;;EAEE,eAAA;EACA,eAAA;EACA,mBAAA;EACA,yBAAA;CDxFD;AC2FD;EACE,YAAA;CDzFD;AC4FD;EACE,gBAAA;CD1FD;ACoGD;EACE,UAAA;CDlGD;ACyGD;EACE,iBAAA;CDvGD;ACiHD;EACE,iBAAA;CD/GD;ACsHD;EACE,gCAAA;KAAA,6BAAA;UAAA,wBAAA;EACA,UAAA;CDpHD;AC2HD;EACE,eAAA;CDzHD;ACgID;;;;EAIE,kCAAA;EACA,eAAA;CD9HD;ACgJD;;;;;EAKE,eAAA;EACA,cAAA;EACA,UAAA;CD9ID;ACqJD;EACE,kBAAA;CDnJD;AC6JD;;EAEE,qBAAA;CD3JD;ACsKD;;;;EAIE,2BAAA;EACA,gBAAA;CDpKD;AC2KD;;EAEE,gBAAA;CDzKD;ACgLD;;EAEE,UAAA;EACA,WAAA;CD9KD;ACsLD;EACE,oBAAA;CDpLD;AC+LD;;EAEE,+BAAA;KAAA,4BAAA;UAAA,uBAAA;EACA,WAAA;CD7LD;ACsMD;;EAEE,aAAA;CDpMD;AC4MD;EACE,8BAAA;EACA,gCAAA;KAAA,6BAAA;UAAA,wBAAA;CD1MD;ACmND;;EAEE,yBAAA;CDjND;ACwND;EACE,0BAAA;EACA,cAAA;EACA,+BAAA;CDtND;AC8ND;EACE,UAAA;EACA,WAAA;CD5ND;ACmOD;EACE,eAAA;CDjOD;ACyOD;EACE,kBAAA;CDvOD;ACiPD;EACE,0BAAA;EACA,kBAAA;CD/OD;ACkPD;;EAEE,WAAA;CDhPD;AACD,qFAAqF;AElFrF;EA7FI;;;IAGI,mCAAA;IACA,uBAAA;IACA,oCAAA;YAAA,4BAAA;IACA,6BAAA;GFkLL;EE/KC;;IAEI,2BAAA;GFiLL;EE9KC;IACI,6BAAA;GFgLL;EE7KC;IACI,8BAAA;GF+KL;EE1KC;;IAEI,YAAA;GF4KL;EEzKC;;IAEI,uBAAA;IACA,yBAAA;GF2KL;EExKC;IACI,4BAAA;GF0KL;EEvKC;;IAEI,yBAAA;GFyKL;EEtKC;IACI,2BAAA;GFwKL;EErKC;;;IAGI,WAAA;IACA,UAAA;GFuKL;EEpKC;;IAEI,wBAAA;GFsKL;EEhKC;IACI,cAAA;GFkKL;EEhKC;;IAGQ,kCAAA;GFiKT;EE9JC;IACI,uBAAA;GFgKL;EE7JC;IACI,qCAAA;GF+JL;EEhKC;;IAKQ,kCAAA;GF+JT;EE5JC;;IAGQ,kCAAA;GF6JT;CACF;AGnPD;EACE,oCAAA;EACA,sDAAA;EACA,gYAAA;CHqPD;AG7OD;EACE,mBAAA;EACA,SAAA;EACA,sBAAA;EACA,oCAAA;EACA,mBAAA;EACA,oBAAA;EACA,eAAA;EACA,oCAAA;EACA,mCAAA;CH+OD;AG3OmC;EAAW,iBAAA;CH8O9C;AG7OmC;EAAW,iBAAA;CHgP9C;AG9OmC;;EAAW,iBAAA;CHkP9C;AGjPmC;EAAW,iBAAA;CHoP9C;AGnPmC;EAAW,iBAAA;CHsP9C;AGrPmC;EAAW,iBAAA;CHwP9C;AGvPmC;EAAW,iBAAA;CH0P9C;AGzPmC;EAAW,iBAAA;CH4P9C;AG3PmC;EAAW,iBAAA;CH8P9C;AG7PmC;EAAW,iBAAA;CHgQ9C;AG/PmC;EAAW,iBAAA;CHkQ9C;AGjQmC;EAAW,iBAAA;CHoQ9C;AGnQmC;EAAW,iBAAA;CHsQ9C;AGrQmC;EAAW,iBAAA;CHwQ9C;AGvQmC;EAAW,iBAAA;CH0Q9C;AGzQmC;EAAW,iBAAA;CH4Q9C;AG3QmC;EAAW,iBAAA;CH8Q9C;AG7QmC;EAAW,iBAAA;CHgR9C;AG/QmC;EAAW,iBAAA;CHkR9C;AGjRmC;EAAW,iBAAA;CHoR9C;AGnRmC;EAAW,iBAAA;CHsR9C;AGrRmC;EAAW,iBAAA;CHwR9C;AGvRmC;EAAW,iBAAA;CH0R9C;AGzRmC;EAAW,iBAAA;CH4R9C;AG3RmC;EAAW,iBAAA;CH8R9C;AG7RmC;EAAW,iBAAA;CHgS9C;AG/RmC;EAAW,iBAAA;CHkS9C;AGjSmC;EAAW,iBAAA;CHoS9C;AGnSmC;EAAW,iBAAA;CHsS9C;AGrSmC;EAAW,iBAAA;CHwS9C;AGvSmC;EAAW,iBAAA;CH0S9C;AGzSmC;EAAW,iBAAA;CH4S9C;AG3SmC;EAAW,iBAAA;CH8S9C;AG7SmC;EAAW,iBAAA;CHgT9C;AG/SmC;EAAW,iBAAA;CHkT9C;AGjTmC;EAAW,iBAAA;CHoT9C;AGnTmC;EAAW,iBAAA;CHsT9C;AGrTmC;EAAW,iBAAA;CHwT9C;AGvTmC;EAAW,iBAAA;CH0T9C;AGzTmC;EAAW,iBAAA;CH4T9C;AG3TmC;EAAW,iBAAA;CH8T9C;AG7TmC;EAAW,iBAAA;CHgU9C;AG/TmC;EAAW,iBAAA;CHkU9C;AGjUmC;EAAW,iBAAA;CHoU9C;AGnUmC;EAAW,iBAAA;CHsU9C;AGrUmC;EAAW,iBAAA;CHwU9C;AGvUmC;EAAW,iBAAA;CH0U9C;AGzUmC;EAAW,iBAAA;CH4U9C;AG3UmC;EAAW,iBAAA;CH8U9C;AG7UmC;EAAW,iBAAA;CHgV9C;AG/UmC;EAAW,iBAAA;CHkV9C;AGjVmC;EAAW,iBAAA;CHoV9C;AGnVmC;EAAW,iBAAA;CHsV9C;AGrVmC;EAAW,iBAAA;CHwV9C;AGvVmC;EAAW,iBAAA;CH0V9C;AGzVmC;EAAW,iBAAA;CH4V9C;AG3VmC;EAAW,iBAAA;CH8V9C;AG7VmC;EAAW,iBAAA;CHgW9C;AG/VmC;EAAW,iBAAA;CHkW9C;AGjWmC;EAAW,iBAAA;CHoW9C;AGnWmC;EAAW,iBAAA;CHsW9C;AGrWmC;EAAW,iBAAA;CHwW9C;AGvWmC;EAAW,iBAAA;CH0W9C;AGzWmC;EAAW,iBAAA;CH4W9C;AG3WmC;EAAW,iBAAA;CH8W9C;AG7WmC;EAAW,iBAAA;CHgX9C;AG/WmC;EAAW,iBAAA;CHkX9C;AGjXmC;EAAW,iBAAA;CHoX9C;AGnXmC;EAAW,iBAAA;CHsX9C;AGrXmC;EAAW,iBAAA;CHwX9C;AGvXmC;EAAW,iBAAA;CH0X9C;AGzXmC;EAAW,iBAAA;CH4X9C;AG3XmC;EAAW,iBAAA;CH8X9C;AG7XmC;EAAW,iBAAA;CHgY9C;AG/XmC;EAAW,iBAAA;CHkY9C;AGjYmC;EAAW,iBAAA;CHoY9C;AGnYmC;EAAW,iBAAA;CHsY9C;AGrYmC;EAAW,iBAAA;CHwY9C;AGvYmC;EAAW,iBAAA;CH0Y9C;AGzYmC;EAAW,iBAAA;CH4Y9C;AG3YmC;EAAW,iBAAA;CH8Y9C;AG7YmC;EAAW,iBAAA;CHgZ9C;AG/YmC;EAAW,iBAAA;CHkZ9C;AGjZmC;EAAW,iBAAA;CHoZ9C;AGnZmC;EAAW,iBAAA;CHsZ9C;AGrZmC;EAAW,iBAAA;CHwZ9C;AGvZmC;EAAW,iBAAA;CH0Z9C;AGzZmC;EAAW,iBAAA;CH4Z9C;AG3ZmC;EAAW,iBAAA;CH8Z9C;AG7ZmC;EAAW,iBAAA;CHga9C;AG/ZmC;EAAW,iBAAA;CHka9C;AGjamC;EAAW,iBAAA;CHoa9C;AGnamC;EAAW,iBAAA;CHsa9C;AGramC;EAAW,iBAAA;CHwa9C;AGvamC;EAAW,iBAAA;CH0a9C;AGzamC;EAAW,iBAAA;CH4a9C;AG3amC;EAAW,iBAAA;CH8a9C;AG7amC;EAAW,iBAAA;CHgb9C;AG/amC;EAAW,iBAAA;CHkb9C;AGjbmC;EAAW,iBAAA;CHob9C;AGnbmC;EAAW,iBAAA;CHsb9C;AGrbmC;EAAW,iBAAA;CHwb9C;AGvbmC;EAAW,iBAAA;CH0b9C;AGzbmC;EAAW,iBAAA;CH4b9C;AG3bmC;EAAW,iBAAA;CH8b9C;AG7bmC;EAAW,iBAAA;CHgc9C;AG/bmC;EAAW,iBAAA;CHkc9C;AGjcmC;EAAW,iBAAA;CHoc9C;AGncmC;EAAW,iBAAA;CHsc9C;AGrcmC;EAAW,iBAAA;CHwc9C;AGvcmC;EAAW,iBAAA;CH0c9C;AGzcmC;EAAW,iBAAA;CH4c9C;AG3cmC;EAAW,iBAAA;CH8c9C;AG7cmC;EAAW,iBAAA;CHgd9C;AG/cmC;EAAW,iBAAA;CHkd9C;AGjdmC;EAAW,iBAAA;CHod9C;AGndmC;EAAW,iBAAA;CHsd9C;AGrdmC;EAAW,iBAAA;CHwd9C;AGvdmC;EAAW,iBAAA;CH0d9C;AGzdmC;EAAW,iBAAA;CH4d9C;AG3dmC;EAAW,iBAAA;CH8d9C;AG7dmC;EAAW,iBAAA;CHge9C;AG/dmC;EAAW,iBAAA;CHke9C;AGjemC;EAAW,iBAAA;CHoe9C;AGnemC;EAAW,iBAAA;CHse9C;AGremC;EAAW,iBAAA;CHwe9C;AGvemC;EAAW,iBAAA;CH0e9C;AGzemC;EAAW,iBAAA;CH4e9C;AG3emC;EAAW,iBAAA;CH8e9C;AG7emC;EAAW,iBAAA;CHgf9C;AG/emC;EAAW,iBAAA;CHkf9C;AGjfmC;EAAW,iBAAA;CHof9C;AGnfmC;EAAW,iBAAA;CHsf9C;AGrfmC;EAAW,iBAAA;CHwf9C;AGvfmC;EAAW,iBAAA;CH0f9C;AGzfmC;EAAW,iBAAA;CH4f9C;AG3fmC;EAAW,iBAAA;CH8f9C;AG7fmC;EAAW,iBAAA;CHggB9C;AG/fmC;EAAW,iBAAA;CHkgB9C;AGjgBmC;EAAW,iBAAA;CHogB9C;AGngBmC;EAAW,iBAAA;CHsgB9C;AGrgBmC;EAAW,iBAAA;CHwgB9C;AGvgBmC;EAAW,iBAAA;CH0gB9C;AGzgBmC;EAAW,iBAAA;CH4gB9C;AG3gBmC;EAAW,iBAAA;CH8gB9C;AG7gBmC;EAAW,iBAAA;CHghB9C;AG/gBmC;EAAW,iBAAA;CHkhB9C;AGjhBmC;EAAW,iBAAA;CHohB9C;AGnhBmC;EAAW,iBAAA;CHshB9C;AGrhBmC;EAAW,iBAAA;CHwhB9C;AGvhBmC;EAAW,iBAAA;CH0hB9C;AGzhBmC;EAAW,iBAAA;CH4hB9C;AG3hBmC;EAAW,iBAAA;CH8hB9C;AG7hBmC;EAAW,iBAAA;CHgiB9C;AG/hBmC;EAAW,iBAAA;CHkiB9C;AGjiBmC;EAAW,iBAAA;CHoiB9C;AGniBmC;EAAW,iBAAA;CHsiB9C;AGriBmC;EAAW,iBAAA;CHwiB9C;AGviBmC;EAAW,iBAAA;CH0iB9C;AGziBmC;EAAW,iBAAA;CH4iB9C;AG3iBmC;EAAW,iBAAA;CH8iB9C;AG7iBmC;EAAW,iBAAA;CHgjB9C;AG/iBmC;EAAW,iBAAA;CHkjB9C;AGjjBmC;EAAW,iBAAA;CHojB9C;AGnjBmC;EAAW,iBAAA;CHsjB9C;AGrjBmC;EAAW,iBAAA;CHwjB9C;AGvjBmC;EAAW,iBAAA;CH0jB9C;AGzjBmC;EAAW,iBAAA;CH4jB9C;AG3jBmC;EAAW,iBAAA;CH8jB9C;AG7jBmC;EAAW,iBAAA;CHgkB9C;AG/jBmC;EAAW,iBAAA;CHkkB9C;AGjkBmC;EAAW,iBAAA;CHokB9C;AGnkBmC;EAAW,iBAAA;CHskB9C;AGrkBmC;EAAW,iBAAA;CHwkB9C;AGvkBmC;EAAW,iBAAA;CH0kB9C;AGzkBmC;EAAW,iBAAA;CH4kB9C;AG3kBmC;EAAW,iBAAA;CH8kB9C;AG7kBmC;EAAW,iBAAA;CHglB9C;AG/kBmC;EAAW,iBAAA;CHklB9C;AGjlBmC;EAAW,iBAAA;CHolB9C;AGnlBmC;EAAW,iBAAA;CHslB9C;AGrlBmC;EAAW,iBAAA;CHwlB9C;AGvlBmC;EAAW,iBAAA;CH0lB9C;AGzlBmC;EAAW,iBAAA;CH4lB9C;AG3lBmC;EAAW,iBAAA;CH8lB9C;AG7lBmC;EAAW,iBAAA;CHgmB9C;AG/lBmC;EAAW,iBAAA;CHkmB9C;AGjmBmC;EAAW,iBAAA;CHomB9C;AGnmBmC;EAAW,iBAAA;CHsmB9C;AGrmBmC;EAAW,iBAAA;CHwmB9C;AGvmBmC;EAAW,iBAAA;CH0mB9C;AGzmBmC;EAAW,iBAAA;CH4mB9C;AG3mBmC;EAAW,iBAAA;CH8mB9C;AG7mBmC;EAAW,iBAAA;CHgnB9C;AG/mBmC;EAAW,iBAAA;CHknB9C;AGjnBmC;EAAW,iBAAA;CHonB9C;AGnnBmC;EAAW,iBAAA;CHsnB9C;AGrnBmC;EAAW,iBAAA;CHwnB9C;AGvnBmC;EAAW,iBAAA;CH0nB9C;AGznBmC;EAAW,iBAAA;CH4nB9C;AG3nBmC;EAAW,iBAAA;CH8nB9C;AG7nBmC;EAAW,iBAAA;CHgoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AGvoBmC;EAAW,iBAAA;CH0oB9C;AGzoBmC;EAAW,iBAAA;CH4oB9C;AG3oBmC;EAAW,iBAAA;CH8oB9C;AG7oBmC;EAAW,iBAAA;CHgpB9C;AG/oBmC;EAAW,iBAAA;CHkpB9C;AGjpBmC;EAAW,iBAAA;CHopB9C;AGnpBmC;EAAW,iBAAA;CHspB9C;AGrpBmC;EAAW,iBAAA;CHwpB9C;AGvpBmC;EAAW,iBAAA;CH0pB9C;AGzpBmC;EAAW,iBAAA;CH4pB9C;AG3pBmC;EAAW,iBAAA;CH8pB9C;AG7pBmC;EAAW,iBAAA;CHgqB9C;AG/pBmC;EAAW,iBAAA;CHkqB9C;AGjqBmC;EAAW,iBAAA;CHoqB9C;AGnqBmC;EAAW,iBAAA;CHsqB9C;AGrqBmC;EAAW,iBAAA;CHwqB9C;AGvqBmC;EAAW,iBAAA;CH0qB9C;AGzqBmC;EAAW,iBAAA;CH4qB9C;AG3qBmC;EAAW,iBAAA;CH8qB9C;AG7qBmC;EAAW,iBAAA;CHgrB9C;AG/qBmC;EAAW,iBAAA;CHkrB9C;AGjrBmC;EAAW,iBAAA;CHorB9C;AGnrBmC;EAAW,iBAAA;CHsrB9C;AGrrBmC;EAAW,iBAAA;CHwrB9C;AGvrBmC;EAAW,iBAAA;CH0rB9C;AGzrBmC;EAAW,iBAAA;CH4rB9C;AG3rBmC;EAAW,iBAAA;CH8rB9C;AG7rBmC;EAAW,iBAAA;CHgsB9C;AG/rBmC;EAAW,iBAAA;CHksB9C;AGjsBmC;EAAW,iBAAA;CHosB9C;AGnsBmC;EAAW,iBAAA;CHssB9C;AGrsBmC;EAAW,iBAAA;CHwsB9C;AGvsBmC;EAAW,iBAAA;CH0sB9C;AGzsBmC;EAAW,iBAAA;CH4sB9C;AG3sBmC;EAAW,iBAAA;CH8sB9C;AG7sBmC;EAAW,iBAAA;CHgtB9C;AG/sBmC;EAAW,iBAAA;CHktB9C;AGjtBmC;EAAW,iBAAA;CHotB9C;AGntBmC;EAAW,iBAAA;CHstB9C;AGrtBmC;EAAW,iBAAA;CHwtB9C;AGvtBmC;EAAW,iBAAA;CH0tB9C;AGztBmC;EAAW,iBAAA;CH4tB9C;AG3tBmC;EAAW,iBAAA;CH8tB9C;AG7tBmC;EAAW,iBAAA;CHguB9C;AG/tBmC;EAAW,iBAAA;CHkuB9C;AGjuBmC;EAAW,iBAAA;CHouB9C;AGnuBmC;EAAW,iBAAA;CHsuB9C;AGruBmC;EAAW,iBAAA;CHwuB9C;AGvuBmC;EAAW,iBAAA;CH0uB9C;AGzuBmC;EAAW,iBAAA;CH4uB9C;AG3uBmC;EAAW,iBAAA;CH8uB9C;AG7uBmC;EAAW,iBAAA;CHgvB9C;AIthCD;ECgEE,+BAAA;EACG,4BAAA;EACK,uBAAA;CLy9BT;AIxhCD;;EC6DE,+BAAA;EACG,4BAAA;EACK,uBAAA;CL+9BT;AIthCD;EACE,gBAAA;EACA,8CAAA;CJwhCD;AIrhCD;EACE,4DAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;CJuhCD;AInhCD;;;;EAIE,qBAAA;EACA,mBAAA;EACA,qBAAA;CJqhCD;AI/gCD;EACE,eAAA;EACA,sBAAA;CJihCD;AI/gCC;;EAEE,eAAA;EACA,2BAAA;CJihCH;AI9gCC;EEnDA,2CAAA;EACA,qBAAA;CNokCD;AIvgCD;EACE,UAAA;CJygCD;AIngCD;EACE,uBAAA;CJqgCD;AIjgCD;;;;;EGvEE,eAAA;EACA,gBAAA;EACA,aAAA;CP+kCD;AIrgCD;EACE,mBAAA;CJugCD;AIjgCD;EACE,aAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;EC6FA,yCAAA;EACK,oCAAA;EACG,iCAAA;EEvLR,sBAAA;EACA,gBAAA;EACA,aAAA;CP+lCD;AIjgCD;EACE,mBAAA;CJmgCD;AI7/BD;EACE,iBAAA;EACA,oBAAA;EACA,UAAA;EACA,8BAAA;CJ+/BD;AIv/BD;EACE,mBAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,WAAA;EACA,iBAAA;EACA,uBAAA;EACA,UAAA;CJy/BD;AIj/BC;;EAEE,iBAAA;EACA,YAAA;EACA,aAAA;EACA,UAAA;EACA,kBAAA;EACA,WAAA;CJm/BH;AIx+BD;EACE,gBAAA;CJ0+BD;AQjoCD;;;;;;;;;;;;EAEE,qBAAA;EACA,iBAAA;EACA,iBAAA;EACA,eAAA;CR6oCD;AQlpCD;;;;;;;;;;;;;;;;;;;;;;;;EASI,oBAAA;EACA,eAAA;EACA,eAAA;CRmqCH;AQ/pCD;;;;;;EAGE,iBAAA;EACA,oBAAA;CRoqCD;AQxqCD;;;;;;;;;;;;EAQI,eAAA;CR8qCH;AQ3qCD;;;;;;EAGE,iBAAA;EACA,oBAAA;CRgrCD;AQprCD;;;;;;;;;;;;EAQI,eAAA;CR0rCH;AQtrCD;;EAAU,gBAAA;CR0rCT;AQzrCD;;EAAU,gBAAA;CR6rCT;AQ5rCD;;EAAU,gBAAA;CRgsCT;AQ/rCD;;EAAU,gBAAA;CRmsCT;AQlsCD;;EAAU,gBAAA;CRssCT;AQrsCD;;EAAU,gBAAA;CRysCT;AQnsCD;EACE,iBAAA;CRqsCD;AQlsCD;EACE,oBAAA;EACA,gBAAA;EACA,iBAAA;EACA,iBAAA;CRosCD;AQ/rCD;EAwOA;IA1OI,gBAAA;GRqsCD;CACF;AQ7rCD;;EAEE,eAAA;CR+rCD;AQ5rCD;;EAEE,0BAAA;EACA,cAAA;CR8rCD;AQ1rCD;EAAuB,iBAAA;CR6rCtB;AQ5rCD;EAAuB,kBAAA;CR+rCtB;AQ9rCD;EAAuB,mBAAA;CRisCtB;AQhsCD;EAAuB,oBAAA;CRmsCtB;AQlsCD;EAAuB,oBAAA;CRqsCtB;AQlsCD;EAAuB,0BAAA;CRqsCtB;AQpsCD;EAAuB,0BAAA;CRusCtB;AQtsCD;EAAuB,2BAAA;CRysCtB;AQtsCD;EACE,eAAA;CRwsCD;AQtsCD;ECrGE,eAAA;CT8yCD;AS7yCC;;EAEE,eAAA;CT+yCH;AQ1sCD;ECxGE,eAAA;CTqzCD;ASpzCC;;EAEE,eAAA;CTszCH;AQ9sCD;EC3GE,eAAA;CT4zCD;AS3zCC;;EAEE,eAAA;CT6zCH;AQltCD;EC9GE,eAAA;CTm0CD;ASl0CC;;EAEE,eAAA;CTo0CH;AQttCD;ECjHE,eAAA;CT00CD;ASz0CC;;EAEE,eAAA;CT20CH;AQttCD;EAGE,YAAA;EE3HA,0BAAA;CVk1CD;AUj1CC;;EAEE,0BAAA;CVm1CH;AQxtCD;EE9HE,0BAAA;CVy1CD;AUx1CC;;EAEE,0BAAA;CV01CH;AQ5tCD;EEjIE,0BAAA;CVg2CD;AU/1CC;;EAEE,0BAAA;CVi2CH;AQhuCD;EEpIE,0BAAA;CVu2CD;AUt2CC;;EAEE,0BAAA;CVw2CH;AQpuCD;EEvIE,0BAAA;CV82CD;AU72CC;;EAEE,0BAAA;CV+2CH;AQnuCD;EACE,oBAAA;EACA,oBAAA;EACA,iCAAA;CRquCD;AQ7tCD;;EAEE,cAAA;EACA,oBAAA;CR+tCD;AQluCD;;;;EAMI,iBAAA;CRkuCH;AQ3tCD;EACE,gBAAA;EACA,iBAAA;CR6tCD;AQztCD;EALE,gBAAA;EACA,iBAAA;EAMA,kBAAA;CR4tCD;AQ9tCD;EAKI,sBAAA;EACA,kBAAA;EACA,mBAAA;CR4tCH;AQvtCD;EACE,cAAA;EACA,oBAAA;CRytCD;AQvtCD;;EAEE,wBAAA;CRytCD;AQvtCD;EACE,kBAAA;CRytCD;AQvtCD;EACE,eAAA;CRytCD;AQhsCD;EA6EA;IAvFM,YAAA;IACA,aAAA;IACA,YAAA;IACA,kBAAA;IGtNJ,iBAAA;IACA,wBAAA;IACA,oBAAA;GXq6CC;EQ7nCH;IAhFM,mBAAA;GRgtCH;CACF;AQvsCD;;EAGE,aAAA;EACA,kCAAA;CRwsCD;AQtsCD;EACE,eAAA;EA9IqB,0BAAA;CRu1CtB;AQpsCD;EACE,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,+BAAA;CRssCD;AQjsCG;;;EACE,iBAAA;CRqsCL;AQ/sCD;;;EAmBI,eAAA;EACA,eAAA;EACA,wBAAA;EACA,eAAA;CRisCH;AQ/rCG;;;EACE,uBAAA;CRmsCL;AQ3rCD;;EAEE,oBAAA;EACA,gBAAA;EACA,gCAAA;EACA,eAAA;EACA,kBAAA;CR6rCD;AQvrCG;;;;;;EAAW,YAAA;CR+rCd;AQ9rCG;;;;;;EACE,uBAAA;CRqsCL;AQ/rCD;EACE,oBAAA;EACA,mBAAA;EACA,wBAAA;CRisCD;AYv+CD;;;;EAIE,+DAAA;CZy+CD;AYr+CD;EACE,iBAAA;EACA,eAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CZu+CD;AYn+CD;EACE,iBAAA;EACA,eAAA;EACA,YAAA;EACA,uBAAA;EACA,mBAAA;EACA,uDAAA;UAAA,+CAAA;CZq+CD;AY3+CD;EASI,WAAA;EACA,gBAAA;EACA,kBAAA;EACA,yBAAA;UAAA,iBAAA;CZq+CH;AYh+CD;EACE,eAAA;EACA,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,sBAAA;EACA,sBAAA;EACA,eAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;CZk+CD;AY7+CD;EAeI,WAAA;EACA,mBAAA;EACA,eAAA;EACA,sBAAA;EACA,8BAAA;EACA,iBAAA;CZi+CH;AY59CD;EACE,kBAAA;EACA,mBAAA;CZ89CD;AaxhDD;ECHE,mBAAA;EACA,kBAAA;EACA,mBAAA;EACA,oBAAA;Cd8hDD;AaxhDC;EAqEF;IAvEI,aAAA;Gb8hDD;CACF;Aa1hDC;EAkEF;IApEI,aAAA;GbgiDD;CACF;Aa5hDD;EA+DA;IAjEI,cAAA;GbkiDD;CACF;AazhDD;ECvBE,mBAAA;EACA,kBAAA;EACA,mBAAA;EACA,oBAAA;CdmjDD;AathDD;ECvBE,mBAAA;EACA,oBAAA;CdgjDD;AehjDG;EACE,mBAAA;EAEA,gBAAA;EAEA,mBAAA;EACA,oBAAA;CfgjDL;AehiDG;EACE,YAAA;CfkiDL;Ae3hDC;EACE,YAAA;Cf6hDH;Ae9hDC;EACE,oBAAA;CfgiDH;AejiDC;EACE,oBAAA;CfmiDH;AepiDC;EACE,WAAA;CfsiDH;AeviDC;EACE,oBAAA;CfyiDH;Ae1iDC;EACE,oBAAA;Cf4iDH;Ae7iDC;EACE,WAAA;Cf+iDH;AehjDC;EACE,oBAAA;CfkjDH;AenjDC;EACE,oBAAA;CfqjDH;AetjDC;EACE,WAAA;CfwjDH;AezjDC;EACE,oBAAA;Cf2jDH;Ae5jDC;EACE,mBAAA;Cf8jDH;AehjDC;EACE,YAAA;CfkjDH;AenjDC;EACE,oBAAA;CfqjDH;AetjDC;EACE,oBAAA;CfwjDH;AezjDC;EACE,WAAA;Cf2jDH;Ae5jDC;EACE,oBAAA;Cf8jDH;Ae/jDC;EACE,oBAAA;CfikDH;AelkDC;EACE,WAAA;CfokDH;AerkDC;EACE,oBAAA;CfukDH;AexkDC;EACE,oBAAA;Cf0kDH;Ae3kDC;EACE,WAAA;Cf6kDH;Ae9kDC;EACE,oBAAA;CfglDH;AejlDC;EACE,mBAAA;CfmlDH;Ae/kDC;EACE,YAAA;CfilDH;AejmDC;EACE,WAAA;CfmmDH;AepmDC;EACE,mBAAA;CfsmDH;AevmDC;EACE,mBAAA;CfymDH;Ae1mDC;EACE,UAAA;Cf4mDH;Ae7mDC;EACE,mBAAA;Cf+mDH;AehnDC;EACE,mBAAA;CfknDH;AennDC;EACE,UAAA;CfqnDH;AetnDC;EACE,mBAAA;CfwnDH;AeznDC;EACE,mBAAA;Cf2nDH;Ae5nDC;EACE,UAAA;Cf8nDH;Ae/nDC;EACE,mBAAA;CfioDH;AeloDC;EACE,kBAAA;CfooDH;AehoDC;EACE,WAAA;CfkoDH;AepnDC;EACE,kBAAA;CfsnDH;AevnDC;EACE,0BAAA;CfynDH;Ae1nDC;EACE,0BAAA;Cf4nDH;Ae7nDC;EACE,iBAAA;Cf+nDH;AehoDC;EACE,0BAAA;CfkoDH;AenoDC;EACE,0BAAA;CfqoDH;AetoDC;EACE,iBAAA;CfwoDH;AezoDC;EACE,0BAAA;Cf2oDH;Ae5oDC;EACE,0BAAA;Cf8oDH;Ae/oDC;EACE,iBAAA;CfipDH;AelpDC;EACE,0BAAA;CfopDH;AerpDC;EACE,yBAAA;CfupDH;AexpDC;EACE,gBAAA;Cf0pDH;Aa1pDD;EElCI;IACE,YAAA;Gf+rDH;EexrDD;IACE,YAAA;Gf0rDD;Ee3rDD;IACE,oBAAA;Gf6rDD;Ee9rDD;IACE,oBAAA;GfgsDD;EejsDD;IACE,WAAA;GfmsDD;EepsDD;IACE,oBAAA;GfssDD;EevsDD;IACE,oBAAA;GfysDD;Ee1sDD;IACE,WAAA;Gf4sDD;Ee7sDD;IACE,oBAAA;Gf+sDD;EehtDD;IACE,oBAAA;GfktDD;EentDD;IACE,WAAA;GfqtDD;EettDD;IACE,oBAAA;GfwtDD;EeztDD;IACE,mBAAA;Gf2tDD;Ee7sDD;IACE,YAAA;Gf+sDD;EehtDD;IACE,oBAAA;GfktDD;EentDD;IACE,oBAAA;GfqtDD;EettDD;IACE,WAAA;GfwtDD;EeztDD;IACE,oBAAA;Gf2tDD;Ee5tDD;IACE,oBAAA;Gf8tDD;Ee/tDD;IACE,WAAA;GfiuDD;EeluDD;IACE,oBAAA;GfouDD;EeruDD;IACE,oBAAA;GfuuDD;EexuDD;IACE,WAAA;Gf0uDD;Ee3uDD;IACE,oBAAA;Gf6uDD;Ee9uDD;IACE,mBAAA;GfgvDD;Ee5uDD;IACE,YAAA;Gf8uDD;Ee9vDD;IACE,WAAA;GfgwDD;EejwDD;IACE,mBAAA;GfmwDD;EepwDD;IACE,mBAAA;GfswDD;EevwDD;IACE,UAAA;GfywDD;Ee1wDD;IACE,mBAAA;Gf4wDD;Ee7wDD;IACE,mBAAA;Gf+wDD;EehxDD;IACE,UAAA;GfkxDD;EenxDD;IACE,mBAAA;GfqxDD;EetxDD;IACE,mBAAA;GfwxDD;EezxDD;IACE,UAAA;Gf2xDD;Ee5xDD;IACE,mBAAA;Gf8xDD;Ee/xDD;IACE,kBAAA;GfiyDD;Ee7xDD;IACE,WAAA;Gf+xDD;EejxDD;IACE,kBAAA;GfmxDD;EepxDD;IACE,0BAAA;GfsxDD;EevxDD;IACE,0BAAA;GfyxDD;Ee1xDD;IACE,iBAAA;Gf4xDD;Ee7xDD;IACE,0BAAA;Gf+xDD;EehyDD;IACE,0BAAA;GfkyDD;EenyDD;IACE,iBAAA;GfqyDD;EetyDD;IACE,0BAAA;GfwyDD;EezyDD;IACE,0BAAA;Gf2yDD;Ee5yDD;IACE,iBAAA;Gf8yDD;Ee/yDD;IACE,0BAAA;GfizDD;EelzDD;IACE,yBAAA;GfozDD;EerzDD;IACE,gBAAA;GfuzDD;CACF;Aa/yDD;EE3CI;IACE,YAAA;Gf61DH;Eet1DD;IACE,YAAA;Gfw1DD;Eez1DD;IACE,oBAAA;Gf21DD;Ee51DD;IACE,oBAAA;Gf81DD;Ee/1DD;IACE,WAAA;Gfi2DD;Eel2DD;IACE,oBAAA;Gfo2DD;Eer2DD;IACE,oBAAA;Gfu2DD;Eex2DD;IACE,WAAA;Gf02DD;Ee32DD;IACE,oBAAA;Gf62DD;Ee92DD;IACE,oBAAA;Gfg3DD;Eej3DD;IACE,WAAA;Gfm3DD;Eep3DD;IACE,oBAAA;Gfs3DD;Eev3DD;IACE,mBAAA;Gfy3DD;Ee32DD;IACE,YAAA;Gf62DD;Ee92DD;IACE,oBAAA;Gfg3DD;Eej3DD;IACE,oBAAA;Gfm3DD;Eep3DD;IACE,WAAA;Gfs3DD;Eev3DD;IACE,oBAAA;Gfy3DD;Ee13DD;IACE,oBAAA;Gf43DD;Ee73DD;IACE,WAAA;Gf+3DD;Eeh4DD;IACE,oBAAA;Gfk4DD;Een4DD;IACE,oBAAA;Gfq4DD;Eet4DD;IACE,WAAA;Gfw4DD;Eez4DD;IACE,oBAAA;Gf24DD;Ee54DD;IACE,mBAAA;Gf84DD;Ee14DD;IACE,YAAA;Gf44DD;Ee55DD;IACE,WAAA;Gf85DD;Ee/5DD;IACE,mBAAA;Gfi6DD;Eel6DD;IACE,mBAAA;Gfo6DD;Eer6DD;IACE,UAAA;Gfu6DD;Eex6DD;IACE,mBAAA;Gf06DD;Ee36DD;IACE,mBAAA;Gf66DD;Ee96DD;IACE,UAAA;Gfg7DD;Eej7DD;IACE,mBAAA;Gfm7DD;Eep7DD;IACE,mBAAA;Gfs7DD;Eev7DD;IACE,UAAA;Gfy7DD;Ee17DD;IACE,mBAAA;Gf47DD;Ee77DD;IACE,kBAAA;Gf+7DD;Ee37DD;IACE,WAAA;Gf67DD;Ee/6DD;IACE,kBAAA;Gfi7DD;Eel7DD;IACE,0BAAA;Gfo7DD;Eer7DD;IACE,0BAAA;Gfu7DD;Eex7DD;IACE,iBAAA;Gf07DD;Ee37DD;IACE,0BAAA;Gf67DD;Ee97DD;IACE,0BAAA;Gfg8DD;Eej8DD;IACE,iBAAA;Gfm8DD;Eep8DD;IACE,0BAAA;Gfs8DD;Eev8DD;IACE,0BAAA;Gfy8DD;Ee18DD;IACE,iBAAA;Gf48DD;Ee78DD;IACE,0BAAA;Gf+8DD;Eeh9DD;IACE,yBAAA;Gfk9DD;Een9DD;IACE,gBAAA;Gfq9DD;CACF;Aa18DD;EE9CI;IACE,YAAA;Gf2/DH;Eep/DD;IACE,YAAA;Gfs/DD;Eev/DD;IACE,oBAAA;Gfy/DD;Ee1/DD;IACE,oBAAA;Gf4/DD;Ee7/DD;IACE,WAAA;Gf+/DD;EehgED;IACE,oBAAA;GfkgED;EengED;IACE,oBAAA;GfqgED;EetgED;IACE,WAAA;GfwgED;EezgED;IACE,oBAAA;Gf2gED;Ee5gED;IACE,oBAAA;Gf8gED;Ee/gED;IACE,WAAA;GfihED;EelhED;IACE,oBAAA;GfohED;EerhED;IACE,mBAAA;GfuhED;EezgED;IACE,YAAA;Gf2gED;Ee5gED;IACE,oBAAA;Gf8gED;Ee/gED;IACE,oBAAA;GfihED;EelhED;IACE,WAAA;GfohED;EerhED;IACE,oBAAA;GfuhED;EexhED;IACE,oBAAA;Gf0hED;Ee3hED;IACE,WAAA;Gf6hED;Ee9hED;IACE,oBAAA;GfgiED;EejiED;IACE,oBAAA;GfmiED;EepiED;IACE,WAAA;GfsiED;EeviED;IACE,oBAAA;GfyiED;Ee1iED;IACE,mBAAA;Gf4iED;EexiED;IACE,YAAA;Gf0iED;Ee1jED;IACE,WAAA;Gf4jED;Ee7jED;IACE,mBAAA;Gf+jED;EehkED;IACE,mBAAA;GfkkED;EenkED;IACE,UAAA;GfqkED;EetkED;IACE,mBAAA;GfwkED;EezkED;IACE,mBAAA;Gf2kED;Ee5kED;IACE,UAAA;Gf8kED;Ee/kED;IACE,mBAAA;GfilED;EellED;IACE,mBAAA;GfolED;EerlED;IACE,UAAA;GfulED;EexlED;IACE,mBAAA;Gf0lED;Ee3lED;IACE,kBAAA;Gf6lED;EezlED;IACE,WAAA;Gf2lED;Ee7kED;IACE,kBAAA;Gf+kED;EehlED;IACE,0BAAA;GfklED;EenlED;IACE,0BAAA;GfqlED;EetlED;IACE,iBAAA;GfwlED;EezlED;IACE,0BAAA;Gf2lED;Ee5lED;IACE,0BAAA;Gf8lED;Ee/lED;IACE,iBAAA;GfimED;EelmED;IACE,0BAAA;GfomED;EermED;IACE,0BAAA;GfumED;EexmED;IACE,iBAAA;Gf0mED;Ee3mED;IACE,0BAAA;Gf6mED;Ee9mED;IACE,yBAAA;GfgnED;EejnED;IACE,gBAAA;GfmnED;CACF;AgBvrED;EACE,8BAAA;ChByrED;AgBvrED;EACE,iBAAA;EACA,oBAAA;EACA,eAAA;EACA,iBAAA;ChByrED;AgBvrED;EACE,iBAAA;ChByrED;AgBnrED;EACE,YAAA;EACA,gBAAA;EACA,oBAAA;ChBqrED;AgBxrED;;;;;;EAWQ,aAAA;EACA,wBAAA;EACA,oBAAA;EACA,2BAAA;ChBqrEP;AgBnsED;EAoBI,uBAAA;EACA,8BAAA;ChBkrEH;AgBvsED;;;;;;EA8BQ,cAAA;ChBirEP;AgB/sED;EAoCI,2BAAA;ChB8qEH;AgBltED;EAyCI,uBAAA;ChB4qEH;AgBrqED;;;;;;EAOQ,aAAA;ChBsqEP;AgB3pED;EACE,uBAAA;ChB6pED;AgB9pED;;;;;;EAQQ,uBAAA;ChB8pEP;AgBtqED;;EAeM,yBAAA;ChB2pEL;AgBjpED;EAEI,0BAAA;ChBkpEH;AgBzoED;EAEI,0BAAA;ChB0oEH;AgBjoED;EACE,iBAAA;EACA,YAAA;EACA,sBAAA;ChBmoED;AgB9nEG;;EACE,iBAAA;EACA,YAAA;EACA,oBAAA;ChBioEL;AiB7wEC;;;;;;;;;;;;EAOI,0BAAA;CjBoxEL;AiB9wEC;;;;;EAMI,0BAAA;CjB+wEL;AiBlyEC;;;;;;;;;;;;EAOI,0BAAA;CjByyEL;AiBnyEC;;;;;EAMI,0BAAA;CjBoyEL;AiBvzEC;;;;;;;;;;;;EAOI,0BAAA;CjB8zEL;AiBxzEC;;;;;EAMI,0BAAA;CjByzEL;AiB50EC;;;;;;;;;;;;EAOI,0BAAA;CjBm1EL;AiB70EC;;;;;EAMI,0BAAA;CjB80EL;AiBj2EC;;;;;;;;;;;;EAOI,0BAAA;CjBw2EL;AiBl2EC;;;;;EAMI,0BAAA;CjBm2EL;AgBjtED;EACE,iBAAA;EACA,kBAAA;ChBmtED;AgBtpED;EACA;IA3DI,YAAA;IACA,oBAAA;IACA,mBAAA;IACA,6CAAA;IACA,uBAAA;GhBotED;EgB7pEH;IAnDM,iBAAA;GhBmtEH;EgBhqEH;;;;;;IA1CY,oBAAA;GhBktET;EgBxqEH;IAlCM,UAAA;GhB6sEH;EgB3qEH;;;;;;IAzBY,eAAA;GhB4sET;EgBnrEH;;;;;;IArBY,gBAAA;GhBgtET;EgB3rEH;;;;IARY,iBAAA;GhBysET;CACF;AkBn6ED;EACE,WAAA;EACA,UAAA;EACA,UAAA;EAIA,aAAA;ClBk6ED;AkB/5ED;EACE,eAAA;EACA,YAAA;EACA,WAAA;EACA,oBAAA;EACA,gBAAA;EACA,qBAAA;EACA,eAAA;EACA,UAAA;EACA,iCAAA;ClBi6ED;AkB95ED;EACE,sBAAA;EACA,gBAAA;EACA,mBAAA;EACA,kBAAA;ClBg6ED;AkBr5ED;Eb4BE,+BAAA;EACG,4BAAA;EACK,uBAAA;CL43ET;AkBr5ED;;EAEE,gBAAA;EACA,mBAAA;EACA,oBAAA;ClBu5ED;AkBp5ED;EACE,eAAA;ClBs5ED;AkBl5ED;EACE,eAAA;EACA,YAAA;ClBo5ED;AkBh5ED;;EAEE,aAAA;ClBk5ED;AkB94ED;;;EZrEE,2CAAA;EACA,qBAAA;CNw9ED;AkB74ED;EACE,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;ClB+4ED;AkBr3ED;EACE,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;EbxDA,yDAAA;EACQ,iDAAA;EAyHR,uFAAA;EACK,0EAAA;EACG,uEAAA;CLwzET;AmBh8EC;EACE,sBAAA;EACA,WAAA;EdUF,uFAAA;EACQ,+EAAA;CLy7ET;AKx5EC;EACE,YAAA;EACA,WAAA;CL05EH;AKx5EC;EAA0B,YAAA;CL25E3B;AK15EC;EAAgC,YAAA;CL65EjC;AkBj4EC;EACE,UAAA;EACA,8BAAA;ClBm4EH;AkB33EC;;;EAGE,0BAAA;EACA,WAAA;ClB63EH;AkB13EC;;EAEE,oBAAA;ClB43EH;AkBx3EC;EACE,aAAA;ClB03EH;AkB92ED;EACE,yBAAA;ClBg3ED;AkBx0ED;EAtBI;;;;IACE,kBAAA;GlBo2EH;EkBj2EC;;;;;;;;IAEE,kBAAA;GlBy2EH;EkBt2EC;;;;;;;;IAEE,kBAAA;GlB82EH;CACF;AkBp2ED;EACE,oBAAA;ClBs2ED;AkB91ED;;EAEE,mBAAA;EACA,eAAA;EACA,iBAAA;EACA,oBAAA;ClBg2ED;AkBr2ED;;EAQI,iBAAA;EACA,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,gBAAA;ClBi2EH;AkB91ED;;;;EAIE,mBAAA;EACA,mBAAA;EACA,mBAAA;ClBg2ED;AkB71ED;;EAEE,iBAAA;ClB+1ED;AkB31ED;;EAEE,mBAAA;EACA,sBAAA;EACA,mBAAA;EACA,iBAAA;EACA,uBAAA;EACA,oBAAA;EACA,gBAAA;ClB61ED;AkB31ED;;EAEE,cAAA;EACA,kBAAA;ClB61ED;AkBp1EC;;;;;;EAGE,oBAAA;ClBy1EH;AkBn1EC;;;;EAEE,oBAAA;ClBu1EH;AkBj1EC;;;;EAGI,oBAAA;ClBo1EL;AkBz0ED;EAEE,iBAAA;EACA,oBAAA;EAEA,iBAAA;EACA,iBAAA;ClBy0ED;AkBv0EC;;EAEE,gBAAA;EACA,iBAAA;ClBy0EH;AkB5zED;ECnQE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnBkkFD;AmBhkFC;EACE,aAAA;EACA,kBAAA;CnBkkFH;AmB/jFC;;EAEE,aAAA;CnBikFH;AkBx0ED;EAEI,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;ClBy0EH;AkB/0ED;EASI,aAAA;EACA,kBAAA;ClBy0EH;AkBn1ED;;EAcI,aAAA;ClBy0EH;AkBv1ED;EAiBI,aAAA;EACA,iBAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;ClBy0EH;AkBr0ED;EC/RE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnBumFD;AmBrmFC;EACE,aAAA;EACA,kBAAA;CnBumFH;AmBpmFC;;EAEE,aAAA;CnBsmFH;AkBj1ED;EAEI,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;ClBk1EH;AkBx1ED;EASI,aAAA;EACA,kBAAA;ClBk1EH;AkB51ED;;EAcI,aAAA;ClBk1EH;AkBh2ED;EAiBI,aAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;ClBk1EH;AkBz0ED;EAEE,mBAAA;ClB00ED;AkB50ED;EAMI,sBAAA;ClBy0EH;AkBr0ED;EACE,mBAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;EACA,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,mBAAA;EACA,qBAAA;ClBu0ED;AkBr0ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBu0ED;AkBr0ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBu0ED;AkBn0ED;;;;;;;;;;EC1ZI,eAAA;CnByuFH;AkB/0ED;ECtZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CL0rFT;AmBxuFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL+rFT;AkBz1ED;EC5YI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBwuFH;AkB91ED;ECtYI,eAAA;CnBuuFH;AkB91ED;;;;;;;;;;EC7ZI,eAAA;CnBuwFH;AkB12ED;ECzZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CLwtFT;AmBtwFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL6tFT;AkBp3ED;EC/YI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBswFH;AkBz3ED;ECzYI,eAAA;CnBqwFH;AkBz3ED;;;;;;;;;;EChaI,eAAA;CnBqyFH;AkBr4ED;EC5ZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CLsvFT;AmBpyFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL2vFT;AkB/4ED;EClZI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBoyFH;AkBp5ED;EC5YI,eAAA;CnBmyFH;AkBh5EC;EACE,UAAA;ClBk5EH;AkBh5EC;EACE,OAAA;ClBk5EH;AkBx4ED;EACE,eAAA;EACA,gBAAA;EACA,oBAAA;EACA,eAAA;ClB04ED;AkBvzED;EAwEA;IAtIM,sBAAA;IACA,iBAAA;IACA,uBAAA;GlBy3EH;EkBrvEH;IA/HM,sBAAA;IACA,YAAA;IACA,uBAAA;GlBu3EH;EkB1vEH;IAxHM,sBAAA;GlBq3EH;EkB7vEH;IApHM,sBAAA;IACA,uBAAA;GlBo3EH;EkBjwEH;;;IA9GQ,YAAA;GlBo3EL;EkBtwEH;IAxGM,YAAA;GlBi3EH;EkBzwEH;IApGM,iBAAA;IACA,uBAAA;GlBg3EH;EkB7wEH;;IA5FM,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlB62EH;EkBpxEH;;IAtFQ,gBAAA;GlB82EL;EkBxxEH;;IAjFM,mBAAA;IACA,eAAA;GlB62EH;EkB7xEH;IA3EM,OAAA;GlB22EH;CACF;AkBj2ED;;;;EASI,cAAA;EACA,iBAAA;EACA,iBAAA;ClB81EH;AkBz2ED;;EAiBI,iBAAA;ClB41EH;AkB72ED;EJthBE,mBAAA;EACA,oBAAA;Cds4FD;AkB10EC;EAyBF;IAnCM,kBAAA;IACA,iBAAA;IACA,iBAAA;GlBw1EH;CACF;AkBx3ED;EAwCI,YAAA;ClBm1EH;AkBr0EC;EAUF;IAdQ,kBAAA;IACA,gBAAA;GlB60EL;CACF;AkBn0EC;EAEF;IANQ,iBAAA;IACA,gBAAA;GlB20EL;CACF;AoBp6FD;EACE,sBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,+BAAA;MAAA,2BAAA;EACA,gBAAA;EACA,uBAAA;EACA,8BAAA;EACA,oBAAA;EC0CA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,mBAAA;EhB+JA,0BAAA;EACG,uBAAA;EACC,sBAAA;EACI,kBAAA;CL+tFT;AoBv6FG;;;;;;EdnBF,2CAAA;EACA,qBAAA;CNk8FD;AoB16FC;;;EAGE,YAAA;EACA,sBAAA;CpB46FH;AoBz6FC;;EAEE,WAAA;EACA,uBAAA;Ef2BF,yDAAA;EACQ,iDAAA;CLi5FT;AoBz6FC;;;EAGE,oBAAA;EE7CF,cAAA;EAGA,0BAAA;EjB8DA,yBAAA;EACQ,iBAAA;CL05FT;AoBz6FG;;EAEE,qBAAA;CpB26FL;AoBl6FD;EC3DE,YAAA;EACA,uBAAA;EACA,mBAAA;CrBg+FD;AqB99FC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBg+FP;AqB99FC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBg+FP;AqB99FC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBg+FP;AqB99FG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBs+FT;AqBn+FC;;;EAGE,uBAAA;CrBq+FH;AqBh+FG;;;;;;;;;EAGE,uBAAA;EACI,mBAAA;CrBw+FT;AoBv9FD;ECZI,YAAA;EACA,uBAAA;CrBs+FH;AoBx9FD;EC9DE,YAAA;EACA,0BAAA;EACA,sBAAA;CrByhGD;AqBvhGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrByhGP;AqBvhGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrByhGP;AqBvhGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrByhGP;AqBvhGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB+hGT;AqB5hGC;;;EAGE,uBAAA;CrB8hGH;AqBzhGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBiiGT;AoB7gGD;ECfI,eAAA;EACA,uBAAA;CrB+hGH;AoB7gGD;EClEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBklGD;AqBhlGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBklGP;AqBhlGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBklGP;AqBhlGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBklGP;AqBhlGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBwlGT;AqBrlGC;;;EAGE,uBAAA;CrBulGH;AqBllGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrB0lGT;AoBlkGD;ECnBI,eAAA;EACA,uBAAA;CrBwlGH;AoBlkGD;ECtEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB2oGD;AqBzoGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2oGP;AqBzoGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2oGP;AqBzoGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2oGP;AqBzoGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBipGT;AqB9oGC;;;EAGE,uBAAA;CrBgpGH;AqB3oGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBmpGT;AoBvnGD;ECvBI,eAAA;EACA,uBAAA;CrBipGH;AoBvnGD;EC1EE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBosGD;AqBlsGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBosGP;AqBlsGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBosGP;AqBlsGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBosGP;AqBlsGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB0sGT;AqBvsGC;;;EAGE,uBAAA;CrBysGH;AqBpsGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrB4sGT;AoB5qGD;EC3BI,eAAA;EACA,uBAAA;CrB0sGH;AoB5qGD;EC9EE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB6vGD;AqB3vGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6vGP;AqB3vGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6vGP;AqB3vGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6vGP;AqB3vGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBmwGT;AqBhwGC;;;EAGE,uBAAA;CrBkwGH;AqB7vGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBqwGT;AoBjuGD;EC/BI,eAAA;EACA,uBAAA;CrBmwGH;AoB5tGD;EACE,eAAA;EACA,oBAAA;EACA,iBAAA;CpB8tGD;AoB5tGC;;;;;EAKE,8BAAA;EfnCF,yBAAA;EACQ,iBAAA;CLkwGT;AoB7tGC;;;;EAIE,0BAAA;CpB+tGH;AoB7tGC;;EAEE,eAAA;EACA,2BAAA;EACA,8BAAA;CpB+tGH;AoB3tGG;;;;EAEE,eAAA;EACA,sBAAA;CpB+tGL;AoBttGD;;ECxEE,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CrBkyGD;AoBztGD;;EC5EE,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrByyGD;AoB5tGD;;EChFE,iBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrBgzGD;AoB3tGD;EACE,eAAA;EACA,YAAA;CpB6tGD;AoBztGD;EACE,gBAAA;CpB2tGD;AoBptGC;;;EACE,YAAA;CpBwtGH;AuBl3GD;EACE,WAAA;ElBoLA,yCAAA;EACK,oCAAA;EACG,iCAAA;CLisGT;AuBr3GC;EACE,WAAA;CvBu3GH;AuBn3GD;EACE,cAAA;CvBq3GD;AuBn3GC;EAAY,eAAA;CvBs3Gb;AuBr3GC;EAAY,mBAAA;CvBw3Gb;AuBv3GC;EAAY,yBAAA;CvB03Gb;AuBv3GD;EACE,mBAAA;EACA,UAAA;EACA,iBAAA;ElBuKA,gDAAA;EACQ,2CAAA;KAAA,wCAAA;EAOR,mCAAA;EACQ,8BAAA;KAAA,2BAAA;EAGR,yCAAA;EACQ,oCAAA;KAAA,iCAAA;CL2sGT;AwBr5GD;EACE,sBAAA;EACA,SAAA;EACA,UAAA;EACA,iBAAA;EACA,uBAAA;EACA,uBAAA;EACA,yBAAA;EACA,oCAAA;EACA,mCAAA;CxBu5GD;AwBn5GD;;EAEE,mBAAA;CxBq5GD;AwBj5GD;EACE,WAAA;CxBm5GD;AwB/4GD;EACE,mBAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,YAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,iBAAA;EACA,uBAAA;EACA,uBAAA;EACA,sCAAA;EACA,mBAAA;EnBsBA,oDAAA;EACQ,4CAAA;EmBrBR,qCAAA;UAAA,6BAAA;CxBk5GD;AwB74GC;EACE,SAAA;EACA,WAAA;CxB+4GH;AwBx6GD;ECzBE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzBo8GD;AwB96GD;EAmCI,eAAA;EACA,kBAAA;EACA,YAAA;EACA,oBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxB84GH;AwBx4GC;;EAEE,sBAAA;EACA,eAAA;EACA,0BAAA;CxB04GH;AwBp4GC;;;EAGE,YAAA;EACA,sBAAA;EACA,WAAA;EACA,0BAAA;CxBs4GH;AwB73GC;;;EAGE,eAAA;CxB+3GH;AwB33GC;;EAEE,sBAAA;EACA,8BAAA;EACA,uBAAA;EE3GF,oEAAA;EF6GE,oBAAA;CxB63GH;AwBx3GD;EAGI,eAAA;CxBw3GH;AwB33GD;EAQI,WAAA;CxBs3GH;AwB92GD;EACE,WAAA;EACA,SAAA;CxBg3GD;AwBx2GD;EACE,QAAA;EACA,YAAA;CxB02GD;AwBt2GD;EACE,eAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxBw2GD;AwBp2GD;EACE,gBAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,OAAA;EACA,aAAA;CxBs2GD;AwBl2GD;EACE,SAAA;EACA,WAAA;CxBo2GD;AwB51GD;;EAII,cAAA;EACA,0BAAA;EACA,4BAAA;EACA,YAAA;CxB41GH;AwBn2GD;;EAWI,UAAA;EACA,aAAA;EACA,mBAAA;CxB41GH;AwBv0GD;EAXE;IApEA,WAAA;IACA,SAAA;GxB05GC;EwBv1GD;IA1DA,QAAA;IACA,YAAA;GxBo5GC;CACF;A2BpiHD;;EAEE,mBAAA;EACA,sBAAA;EACA,uBAAA;C3BsiHD;A2B1iHD;;EAMI,mBAAA;EACA,YAAA;C3BwiHH;A2BtiHG;;;;;;;;EAIE,WAAA;C3B4iHL;A2BtiHD;;;;EAKI,kBAAA;C3BuiHH;A2BliHD;EACE,kBAAA;C3BoiHD;A2BriHD;;;EAOI,YAAA;C3BmiHH;A2B1iHD;;;EAYI,iBAAA;C3BmiHH;A2B/hHD;EACE,iBAAA;C3BiiHD;A2B7hHD;EACE,eAAA;C3B+hHD;A2B9hHC;EClDA,8BAAA;EACG,2BAAA;C5BmlHJ;A2B7hHD;;EC/CE,6BAAA;EACG,0BAAA;C5BglHJ;A2B5hHD;EACE,YAAA;C3B8hHD;A2B5hHD;EACE,iBAAA;C3B8hHD;A2B5hHD;;ECnEE,8BAAA;EACG,2BAAA;C5BmmHJ;A2B3hHD;ECjEE,6BAAA;EACG,0BAAA;C5B+lHJ;A2B1hHD;;EAEE,WAAA;C3B4hHD;A2B3gHD;EACE,kBAAA;EACA,mBAAA;C3B6gHD;A2B3gHD;EACE,mBAAA;EACA,oBAAA;C3B6gHD;A2BxgHD;EtB/CE,yDAAA;EACQ,iDAAA;CL0jHT;A2BxgHC;EtBnDA,yBAAA;EACQ,iBAAA;CL8jHT;A2BrgHD;EACE,eAAA;C3BugHD;A2BpgHD;EACE,wBAAA;EACA,uBAAA;C3BsgHD;A2BngHD;EACE,wBAAA;C3BqgHD;A2B9/GD;;;EAII,eAAA;EACA,YAAA;EACA,YAAA;EACA,gBAAA;C3B+/GH;A2BtgHD;EAcM,YAAA;C3B2/GL;A2BzgHD;;;;EAsBI,iBAAA;EACA,eAAA;C3By/GH;A2Bp/GC;EACE,iBAAA;C3Bs/GH;A2Bp/GC;EC3KA,6BAAA;EACC,4BAAA;EAOD,8BAAA;EACC,6BAAA;C5B4pHF;A2Bt/GC;EC/KA,2BAAA;EACC,0BAAA;EAOD,gCAAA;EACC,+BAAA;C5BkqHF;A2Bv/GD;EACE,iBAAA;C3By/GD;A2Bv/GD;;EC/KE,8BAAA;EACC,6BAAA;C5B0qHF;A2Bt/GD;EC7LE,2BAAA;EACC,0BAAA;C5BsrHF;A2Bl/GD;EACE,eAAA;EACA,YAAA;EACA,oBAAA;EACA,0BAAA;C3Bo/GD;A2Bx/GD;;EAOI,YAAA;EACA,oBAAA;EACA,UAAA;C3Bq/GH;A2B9/GD;EAYI,YAAA;C3Bq/GH;A2BjgHD;EAgBI,WAAA;C3Bo/GH;A2Bn+GD;;;;EAKM,mBAAA;EACA,uBAAA;EACA,qBAAA;C3Bo+GL;A6B9sHD;EACE,mBAAA;EACA,eAAA;EACA,0BAAA;C7BgtHD;A6B7sHC;EACE,YAAA;EACA,gBAAA;EACA,iBAAA;C7B+sHH;A6BxtHD;EAeI,mBAAA;EACA,WAAA;EAKA,YAAA;EAEA,YAAA;EACA,iBAAA;C7BusHH;A6BrsHG;EACE,WAAA;C7BusHL;A6B7rHD;;;EV0BE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnBwqHD;AmBtqHC;;;EACE,aAAA;EACA,kBAAA;CnB0qHH;AmBvqHC;;;;;;EAEE,aAAA;CnB6qHH;A6B/sHD;;;EVqBE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnB+rHD;AmB7rHC;;;EACE,aAAA;EACA,kBAAA;CnBisHH;AmB9rHC;;;;;;EAEE,aAAA;CnBosHH;A6B7tHD;;;EAGE,oBAAA;C7B+tHD;A6B7tHC;;;EACE,iBAAA;C7BiuHH;A6B7tHD;;EAEE,UAAA;EACA,oBAAA;EACA,uBAAA;C7B+tHD;A6B1tHD;EACE,kBAAA;EACA,gBAAA;EACA,oBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;C7B4tHD;A6BztHC;EACE,kBAAA;EACA,gBAAA;EACA,mBAAA;C7B2tHH;A6BztHC;EACE,mBAAA;EACA,gBAAA;EACA,mBAAA;C7B2tHH;A6B/uHD;;EA0BI,cAAA;C7BytHH;A6BptHD;;;;;;;EDpGE,8BAAA;EACG,2BAAA;C5Bi0HJ;A6BrtHD;EACE,gBAAA;C7ButHD;A6BrtHD;;;;;;;EDxGE,6BAAA;EACG,0BAAA;C5Bs0HJ;A6BttHD;EACE,eAAA;C7BwtHD;A6BntHD;EACE,mBAAA;EAGA,aAAA;EACA,oBAAA;C7BmtHD;A6BxtHD;EAUI,mBAAA;C7BitHH;A6B3tHD;EAYM,kBAAA;C7BktHL;A6B/sHG;;;EAGE,WAAA;C7BitHL;A6B5sHC;;EAGI,mBAAA;C7B6sHL;A6B1sHC;;EAGI,WAAA;EACA,kBAAA;C7B2sHL;A8B12HD;EACE,iBAAA;EACA,gBAAA;EACA,iBAAA;C9B42HD;A8B/2HD;EAOI,mBAAA;EACA,eAAA;C9B22HH;A8Bn3HD;EAWM,mBAAA;EACA,eAAA;EACA,mBAAA;C9B22HL;A8B12HK;;EAEE,sBAAA;EACA,0BAAA;C9B42HP;A8Bv2HG;EACE,eAAA;C9By2HL;A8Bv2HK;;EAEE,eAAA;EACA,sBAAA;EACA,8BAAA;EACA,oBAAA;C9By2HP;A8Bl2HG;;;EAGE,0BAAA;EACA,sBAAA;C9Bo2HL;A8B74HD;ELHE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzBm5HD;A8Bn5HD;EA0DI,gBAAA;C9B41HH;A8Bn1HD;EACE,8BAAA;C9Bq1HD;A8Bt1HD;EAGI,YAAA;EAEA,oBAAA;C9Bq1HH;A8B11HD;EASM,kBAAA;EACA,wBAAA;EACA,8BAAA;EACA,2BAAA;C9Bo1HL;A8Bn1HK;EACE,mCAAA;C9Bq1HP;A8B/0HK;;;EAGE,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,iCAAA;EACA,gBAAA;C9Bi1HP;A8B50HC;EAqDA,YAAA;EA8BA,iBAAA;C9B6vHD;A8Bh1HC;EAwDE,YAAA;C9B2xHH;A8Bn1HC;EA0DI,mBAAA;EACA,mBAAA;C9B4xHL;A8Bv1HC;EAgEE,UAAA;EACA,WAAA;C9B0xHH;A8B9wHD;EA0DA;IAjEM,oBAAA;IACA,UAAA;G9ByxHH;E8BztHH;IA9DQ,iBAAA;G9B0xHL;CACF;A8Bp2HC;EAuFE,gBAAA;EACA,mBAAA;C9BgxHH;A8Bx2HC;;;EA8FE,uBAAA;C9B+wHH;A8BjwHD;EA2BA;IApCM,8BAAA;IACA,2BAAA;G9B8wHH;E8B3uHH;;;IA9BM,0BAAA;G9B8wHH;CACF;A8B/2HD;EAEI,YAAA;C9Bg3HH;A8Bl3HD;EAMM,mBAAA;C9B+2HL;A8Br3HD;EASM,iBAAA;C9B+2HL;A8B12HK;;;EAGE,YAAA;EACA,0BAAA;C9B42HP;A8Bp2HD;EAEI,YAAA;C9Bq2HH;A8Bv2HD;EAIM,gBAAA;EACA,eAAA;C9Bs2HL;A8B11HD;EACE,YAAA;C9B41HD;A8B71HD;EAII,YAAA;C9B41HH;A8Bh2HD;EAMM,mBAAA;EACA,mBAAA;C9B61HL;A8Bp2HD;EAYI,UAAA;EACA,WAAA;C9B21HH;A8B/0HD;EA0DA;IAjEM,oBAAA;IACA,UAAA;G9B01HH;E8B1xHH;IA9DQ,iBAAA;G9B21HL;CACF;A8Bn1HD;EACE,iBAAA;C9Bq1HD;A8Bt1HD;EAKI,gBAAA;EACA,mBAAA;C9Bo1HH;A8B11HD;;;EAYI,uBAAA;C9Bm1HH;A8Br0HD;EA2BA;IApCM,8BAAA;IACA,2BAAA;G9Bk1HH;E8B/yHH;;;IA9BM,0BAAA;G9Bk1HH;CACF;A8Bz0HD;EAEI,cAAA;C9B00HH;A8B50HD;EAKI,eAAA;C9B00HH;A8Bj0HD;EAEE,iBAAA;EF3OA,2BAAA;EACC,0BAAA;C5B8iIF;A+BxiID;EACE,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,8BAAA;C/B0iID;A+BliID;EA8nBA;IAhoBI,mBAAA;G/BwiID;CACF;A+BzhID;EAgnBA;IAlnBI,YAAA;G/B+hID;CACF;A+BjhID;EACE,oBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kCAAA;EACA,2DAAA;UAAA,mDAAA;EAEA,kCAAA;C/BkhID;A+BhhIC;EACE,iBAAA;C/BkhIH;A+Bt/HD;EA6jBA;IArlBI,YAAA;IACA,cAAA;IACA,yBAAA;YAAA,iBAAA;G/BkhID;E+BhhIC;IACE,0BAAA;IACA,wBAAA;IACA,kBAAA;IACA,6BAAA;G/BkhIH;E+B/gIC;IACE,oBAAA;G/BihIH;E+B5gIC;;;IAGE,gBAAA;IACA,iBAAA;G/B8gIH;CACF;A+B1gID;;EAGI,kBAAA;C/B2gIH;A+BtgIC;EAmjBF;;IArjBM,kBAAA;G/B6gIH;CACF;A+BpgID;;;;EAII,oBAAA;EACA,mBAAA;C/BsgIH;A+BhgIC;EAgiBF;;;;IAniBM,gBAAA;IACA,eAAA;G/B0gIH;CACF;A+B9/HD;EACE,cAAA;EACA,sBAAA;C/BggID;A+B3/HD;EA8gBA;IAhhBI,iBAAA;G/BigID;CACF;A+B7/HD;;EAEE,gBAAA;EACA,SAAA;EACA,QAAA;EACA,cAAA;C/B+/HD;A+Bz/HD;EAggBA;;IAlgBI,iBAAA;G/BggID;CACF;A+B9/HD;EACE,OAAA;EACA,sBAAA;C/BggID;A+B9/HD;EACE,UAAA;EACA,iBAAA;EACA,sBAAA;C/BggID;A+B1/HD;EACE,YAAA;EACA,mBAAA;EACA,gBAAA;EACA,kBAAA;EACA,aAAA;C/B4/HD;A+B1/HC;;EAEE,sBAAA;C/B4/HH;A+BrgID;EAaI,eAAA;C/B2/HH;A+Bl/HD;EALI;;IAEE,mBAAA;G/B0/HH;CACF;A+Bh/HD;EACE,mBAAA;EACA,aAAA;EACA,mBAAA;EACA,kBAAA;EC9LA,gBAAA;EACA,mBAAA;ED+LA,8BAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;C/Bm/HD;A+B/+HC;EACE,WAAA;C/Bi/HH;A+B//HD;EAmBI,eAAA;EACA,YAAA;EACA,YAAA;EACA,mBAAA;C/B++HH;A+BrgID;EAyBI,gBAAA;C/B++HH;A+Bz+HD;EAqbA;IAvbI,cAAA;G/B++HD;CACF;A+Bt+HD;EACE,oBAAA;C/Bw+HD;A+Bz+HD;EAII,kBAAA;EACA,qBAAA;EACA,kBAAA;C/Bw+HH;A+B58HC;EA2YF;IAjaM,iBAAA;IACA,YAAA;IACA,YAAA;IACA,cAAA;IACA,8BAAA;IACA,UAAA;IACA,yBAAA;YAAA,iBAAA;G/Bs+HH;E+B3kHH;;IAxZQ,2BAAA;G/Bu+HL;E+B/kHH;IArZQ,kBAAA;G/Bu+HL;E+Bt+HK;;IAEE,uBAAA;G/Bw+HP;CACF;A+Bt9HD;EA+XA;IA1YI,YAAA;IACA,UAAA;G/Bq+HD;E+B5lHH;IAtYM,YAAA;G/Bq+HH;E+B/lHH;IApYQ,kBAAA;IACA,qBAAA;G/Bs+HL;CACF;A+B39HD;EACE,mBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kCAAA;EACA,qCAAA;E1B9NA,6FAAA;EACQ,qFAAA;E2B/DR,gBAAA;EACA,mBAAA;ChC4vID;AkBtuHD;EAwEA;IAtIM,sBAAA;IACA,iBAAA;IACA,uBAAA;GlBwyHH;EkBpqHH;IA/HM,sBAAA;IACA,YAAA;IACA,uBAAA;GlBsyHH;EkBzqHH;IAxHM,sBAAA;GlBoyHH;EkB5qHH;IApHM,sBAAA;IACA,uBAAA;GlBmyHH;EkBhrHH;;;IA9GQ,YAAA;GlBmyHL;EkBrrHH;IAxGM,YAAA;GlBgyHH;EkBxrHH;IApGM,iBAAA;IACA,uBAAA;GlB+xHH;EkB5rHH;;IA5FM,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlB4xHH;EkBnsHH;;IAtFQ,gBAAA;GlB6xHL;EkBvsHH;;IAjFM,mBAAA;IACA,eAAA;GlB4xHH;EkB5sHH;IA3EM,OAAA;GlB0xHH;CACF;A+BpgIC;EAmWF;IAzWM,mBAAA;G/B8gIH;E+B5gIG;IACE,iBAAA;G/B8gIL;CACF;A+B7/HD;EAoVA;IA5VI,YAAA;IACA,UAAA;IACA,eAAA;IACA,gBAAA;IACA,eAAA;IACA,kBAAA;I1BzPF,yBAAA;IACQ,iBAAA;GLmwIP;CACF;A+BngID;EACE,cAAA;EHpUA,2BAAA;EACC,0BAAA;C5B00IF;A+BngID;EACE,iBAAA;EHzUA,6BAAA;EACC,4BAAA;EAOD,8BAAA;EACC,6BAAA;C5By0IF;A+B//HD;EChVE,gBAAA;EACA,mBAAA;ChCk1ID;A+BhgIC;ECnVA,iBAAA;EACA,oBAAA;ChCs1ID;A+BjgIC;ECtVA,iBAAA;EACA,oBAAA;ChC01ID;A+B3/HD;EChWE,iBAAA;EACA,oBAAA;ChC81ID;A+Bv/HD;EAsSA;IA1SI,YAAA;IACA,kBAAA;IACA,mBAAA;G/B+/HD;CACF;A+Bl+HD;EAhBE;IExWA,uBAAA;GjC81IC;E+Br/HD;IE5WA,wBAAA;IF8WE,oBAAA;G/Bu/HD;E+Bz/HD;IAKI,gBAAA;G/Bu/HH;CACF;A+B9+HD;EACE,0BAAA;EACA,sBAAA;C/Bg/HD;A+Bl/HD;EAKI,YAAA;C/Bg/HH;A+B/+HG;;EAEE,eAAA;EACA,8BAAA;C/Bi/HL;A+B1/HD;EAcI,YAAA;C/B++HH;A+B7/HD;EAmBM,YAAA;C/B6+HL;A+B3+HK;;EAEE,YAAA;EACA,8BAAA;C/B6+HP;A+Bz+HK;;;EAGE,YAAA;EACA,0BAAA;C/B2+HP;A+Bv+HK;;;EAGE,YAAA;EACA,8BAAA;C/By+HP;A+BjhID;EA8CI,mBAAA;C/Bs+HH;A+Br+HG;;EAEE,uBAAA;C/Bu+HL;A+BxhID;EAoDM,uBAAA;C/Bu+HL;A+B3hID;;EA0DI,sBAAA;C/Bq+HH;A+B99HK;;;EAGE,0BAAA;EACA,YAAA;C/Bg+HP;A+B/7HC;EAoKF;IA7LU,YAAA;G/B49HP;E+B39HO;;IAEE,YAAA;IACA,8BAAA;G/B69HT;E+Bz9HO;;;IAGE,YAAA;IACA,0BAAA;G/B29HT;E+Bv9HO;;;IAGE,YAAA;IACA,8BAAA;G/By9HT;CACF;A+B3jID;EA8GI,YAAA;C/Bg9HH;A+B/8HG;EACE,YAAA;C/Bi9HL;A+BjkID;EAqHI,YAAA;C/B+8HH;A+B98HG;;EAEE,YAAA;C/Bg9HL;A+B58HK;;;;EAEE,YAAA;C/Bg9HP;A+Bx8HD;EACE,uBAAA;EACA,sBAAA;C/B08HD;A+B58HD;EAKI,eAAA;C/B08HH;A+Bz8HG;;EAEE,YAAA;EACA,8BAAA;C/B28HL;A+Bp9HD;EAcI,eAAA;C/By8HH;A+Bv9HD;EAmBM,eAAA;C/Bu8HL;A+Br8HK;;EAEE,YAAA;EACA,8BAAA;C/Bu8HP;A+Bn8HK;;;EAGE,YAAA;EACA,0BAAA;C/Bq8HP;A+Bj8HK;;;EAGE,YAAA;EACA,8BAAA;C/Bm8HP;A+B3+HD;EA+CI,mBAAA;C/B+7HH;A+B97HG;;EAEE,uBAAA;C/Bg8HL;A+Bl/HD;EAqDM,uBAAA;C/Bg8HL;A+Br/HD;;EA2DI,sBAAA;C/B87HH;A+Bx7HK;;;EAGE,0BAAA;EACA,YAAA;C/B07HP;A+Bn5HC;EAwBF;IAvDU,sBAAA;G/Bs7HP;E+B/3HH;IApDU,0BAAA;G/Bs7HP;E+Bl4HH;IAjDU,eAAA;G/Bs7HP;E+Br7HO;;IAEE,YAAA;IACA,8BAAA;G/Bu7HT;E+Bn7HO;;;IAGE,YAAA;IACA,0BAAA;G/Bq7HT;E+Bj7HO;;;IAGE,YAAA;IACA,8BAAA;G/Bm7HT;CACF;A+B3hID;EA+GI,eAAA;C/B+6HH;A+B96HG;EACE,YAAA;C/Bg7HL;A+BjiID;EAsHI,eAAA;C/B86HH;A+B76HG;;EAEE,YAAA;C/B+6HL;A+B36HK;;;;EAEE,YAAA;C/B+6HP;AkCzjJD;EACE,kBAAA;EACA,oBAAA;EACA,iBAAA;EACA,0BAAA;EACA,mBAAA;ClC2jJD;AkChkJD;EAQI,sBAAA;ClC2jJH;AkCnkJD;EAWM,kBAAA;EACA,eAAA;EACA,YAAA;ClC2jJL;AkCxkJD;EAkBI,eAAA;ClCyjJH;AmC7kJD;EACE,sBAAA;EACA,gBAAA;EACA,eAAA;EACA,mBAAA;CnC+kJD;AmCnlJD;EAOI,gBAAA;CnC+kJH;AmCtlJD;;EAUM,mBAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,sBAAA;EACA,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,kBAAA;CnCglJL;AmC9kJG;;EAGI,eAAA;EPXN,+BAAA;EACG,4BAAA;C5B2lJJ;AmC7kJG;;EPvBF,gCAAA;EACG,6BAAA;C5BwmJJ;AmCxkJG;;;;EAEE,WAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CnC4kJL;AmCtkJG;;;;;;EAGE,WAAA;EACA,YAAA;EACA,0BAAA;EACA,sBAAA;EACA,gBAAA;CnC2kJL;AmCloJD;;;;;;EAkEM,eAAA;EACA,uBAAA;EACA,mBAAA;EACA,oBAAA;CnCwkJL;AmC/jJD;;EC3EM,mBAAA;EACA,gBAAA;EACA,uBAAA;CpC8oJL;AoC5oJG;;ERKF,+BAAA;EACG,4BAAA;C5B2oJJ;AoC3oJG;;ERTF,gCAAA;EACG,6BAAA;C5BwpJJ;AmC1kJD;;EChFM,kBAAA;EACA,gBAAA;EACA,iBAAA;CpC8pJL;AoC5pJG;;ERKF,+BAAA;EACG,4BAAA;C5B2pJJ;AoC3pJG;;ERTF,gCAAA;EACG,6BAAA;C5BwqJJ;AqC3qJD;EACE,gBAAA;EACA,eAAA;EACA,iBAAA;EACA,mBAAA;CrC6qJD;AqCjrJD;EAOI,gBAAA;CrC6qJH;AqCprJD;;EAUM,sBAAA;EACA,kBAAA;EACA,uBAAA;EACA,uBAAA;EACA,oBAAA;CrC8qJL;AqC5rJD;;EAmBM,sBAAA;EACA,0BAAA;CrC6qJL;AqCjsJD;;EA2BM,aAAA;CrC0qJL;AqCrsJD;;EAkCM,YAAA;CrCuqJL;AqCzsJD;;;;EA2CM,eAAA;EACA,uBAAA;EACA,oBAAA;CrCoqJL;AsCltJD;EACE,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,mBAAA;EACA,oBAAA;EACA,yBAAA;EACA,qBAAA;CtCotJD;AsChtJG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CtCktJL;AsC7sJC;EACE,cAAA;CtC+sJH;AsC3sJC;EACE,mBAAA;EACA,UAAA;CtC6sJH;AsCtsJD;ECtCE,0BAAA;CvC+uJD;AuC5uJG;;EAEE,0BAAA;CvC8uJL;AsCzsJD;EC1CE,0BAAA;CvCsvJD;AuCnvJG;;EAEE,0BAAA;CvCqvJL;AsC5sJD;EC9CE,0BAAA;CvC6vJD;AuC1vJG;;EAEE,0BAAA;CvC4vJL;AsC/sJD;EClDE,0BAAA;CvCowJD;AuCjwJG;;EAEE,0BAAA;CvCmwJL;AsCltJD;ECtDE,0BAAA;CvC2wJD;AuCxwJG;;EAEE,0BAAA;CvC0wJL;AsCrtJD;EC1DE,0BAAA;CvCkxJD;AuC/wJG;;EAEE,0BAAA;CvCixJL;AwCnxJD;EACE,sBAAA;EACA,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,kBAAA;EACA,YAAA;EACA,eAAA;EACA,uBAAA;EACA,oBAAA;EACA,mBAAA;EACA,0BAAA;EACA,oBAAA;CxCqxJD;AwClxJC;EACE,cAAA;CxCoxJH;AwChxJC;EACE,mBAAA;EACA,UAAA;CxCkxJH;AwC/wJC;;EAEE,OAAA;EACA,iBAAA;CxCixJH;AwC5wJG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CxC8wJL;AwCzwJC;;EAEE,eAAA;EACA,uBAAA;CxC2wJH;AwCxwJC;EACE,aAAA;CxC0wJH;AwCvwJC;EACE,kBAAA;CxCywJH;AwCtwJC;EACE,iBAAA;CxCwwJH;AyCl0JD;EACE,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,eAAA;EACA,0BAAA;CzCo0JD;AyCz0JD;;EASI,eAAA;CzCo0JH;AyC70JD;EAaI,oBAAA;EACA,gBAAA;EACA,iBAAA;CzCm0JH;AyCl1JD;EAmBI,0BAAA;CzCk0JH;AyC/zJC;;EAEE,mBAAA;EACA,mBAAA;EACA,oBAAA;CzCi0JH;AyC31JD;EA8BI,gBAAA;CzCg0JH;AyC9yJD;EACA;IAfI,kBAAA;IACA,qBAAA;GzCg0JD;EyC9zJC;;IAEE,mBAAA;IACA,oBAAA;GzCg0JH;EyCvzJH;;IAJM,gBAAA;GzC+zJH;CACF;A0C52JD;EACE,eAAA;EACA,aAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;ErCiLA,4CAAA;EACK,uCAAA;EACG,oCAAA;CL8rJT;A0Cx3JD;;EAaI,kBAAA;EACA,mBAAA;C1C+2JH;A0C32JC;;;EAGE,sBAAA;C1C62JH;A0Cl4JD;EA0BI,aAAA;EACA,eAAA;C1C22JH;A2Cp4JD;EACE,cAAA;EACA,oBAAA;EACA,8BAAA;EACA,mBAAA;C3Cs4JD;A2C14JD;EAQI,cAAA;EAEA,eAAA;C3Co4JH;A2C94JD;EAeI,kBAAA;C3Ck4JH;A2Cj5JD;;EAqBI,iBAAA;C3Cg4JH;A2Cr5JD;EAyBI,gBAAA;C3C+3JH;A2Cv3JD;;EAEE,oBAAA;C3Cy3JD;A2C33JD;;EAMI,mBAAA;EACA,UAAA;EACA,aAAA;EACA,eAAA;C3Cy3JH;A2Cj3JD;ECvDE,0BAAA;EACA,sBAAA;EACA,eAAA;C5C26JD;A2Ct3JD;EClDI,0BAAA;C5C26JH;A2Cz3JD;EC/CI,eAAA;C5C26JH;A2Cx3JD;EC3DE,0BAAA;EACA,sBAAA;EACA,eAAA;C5Cs7JD;A2C73JD;ECtDI,0BAAA;C5Cs7JH;A2Ch4JD;ECnDI,eAAA;C5Cs7JH;A2C/3JD;EC/DE,0BAAA;EACA,sBAAA;EACA,eAAA;C5Ci8JD;A2Cp4JD;EC1DI,0BAAA;C5Ci8JH;A2Cv4JD;ECvDI,eAAA;C5Ci8JH;A2Ct4JD;ECnEE,0BAAA;EACA,sBAAA;EACA,eAAA;C5C48JD;A2C34JD;EC9DI,0BAAA;C5C48JH;A2C94JD;EC3DI,eAAA;C5C48JH;A6C98JD;EACE;IAAQ,4BAAA;G7Ci9JP;E6Ch9JD;IAAQ,yBAAA;G7Cm9JP;CACF;A6Ch9JD;EACE;IAAQ,4BAAA;G7Cm9JP;E6Cl9JD;IAAQ,yBAAA;G7Cq9JP;CACF;A6Cx9JD;EACE;IAAQ,4BAAA;G7Cm9JP;E6Cl9JD;IAAQ,yBAAA;G7Cq9JP;CACF;A6C98JD;EACE,iBAAA;EACA,aAAA;EACA,oBAAA;EACA,0BAAA;EACA,mBAAA;ExCsCA,uDAAA;EACQ,+CAAA;CL26JT;A6C78JD;EACE,YAAA;EACA,UAAA;EACA,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,YAAA;EACA,mBAAA;EACA,0BAAA;ExCyBA,uDAAA;EACQ,+CAAA;EAyHR,oCAAA;EACK,+BAAA;EACG,4BAAA;CL+zJT;A6C18JD;;ECCI,8MAAA;EACA,yMAAA;EACA,sMAAA;EDAF,mCAAA;UAAA,2BAAA;C7C88JD;A6Cv8JD;;ExC5CE,2DAAA;EACK,sDAAA;EACG,mDAAA;CLu/JT;A6Cp8JD;EErEE,0BAAA;C/C4gKD;A+CzgKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C49JH;A6Cx8JD;EEzEE,0BAAA;C/CohKD;A+CjhKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9Co+JH;A6C58JD;EE7EE,0BAAA;C/C4hKD;A+CzhKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C4+JH;A6Ch9JD;EEjFE,0BAAA;C/CoiKD;A+CjiKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9Co/JH;AgD5iKD;EAEE,iBAAA;ChD6iKD;AgD3iKC;EACE,cAAA;ChD6iKH;AgDziKD;;EAEE,QAAA;EACA,iBAAA;ChD2iKD;AgDxiKD;EACE,eAAA;ChD0iKD;AgDviKD;EACE,eAAA;ChDyiKD;AgDtiKC;EACE,gBAAA;ChDwiKH;AgDpiKD;;EAEE,mBAAA;ChDsiKD;AgDniKD;;EAEE,oBAAA;ChDqiKD;AgDliKD;;;EAGE,oBAAA;EACA,oBAAA;ChDoiKD;AgDjiKD;EACE,uBAAA;ChDmiKD;AgDhiKD;EACE,uBAAA;ChDkiKD;AgD9hKD;EACE,cAAA;EACA,mBAAA;ChDgiKD;AgD1hKD;EACE,gBAAA;EACA,iBAAA;ChD4hKD;AiDnlKD;EAEE,oBAAA;EACA,gBAAA;CjDolKD;AiD5kKD;EACE,mBAAA;EACA,eAAA;EACA,mBAAA;EAEA,oBAAA;EACA,uBAAA;EACA,uBAAA;CjD6kKD;AiD1kKC;ErB3BA,6BAAA;EACC,4BAAA;C5BwmKF;AiD3kKC;EACE,iBAAA;ErBvBF,gCAAA;EACC,+BAAA;C5BqmKF;AiDpkKD;;EAEE,YAAA;CjDskKD;AiDxkKD;;EAKI,YAAA;CjDukKH;AiDnkKC;;;;EAEE,sBAAA;EACA,YAAA;EACA,0BAAA;CjDukKH;AiDnkKD;EACE,YAAA;EACA,iBAAA;CjDqkKD;AiDhkKC;;;EAGE,0BAAA;EACA,eAAA;EACA,oBAAA;CjDkkKH;AiDvkKC;;;EASI,eAAA;CjDmkKL;AiD5kKC;;;EAYI,eAAA;CjDqkKL;AiDhkKC;;;EAGE,WAAA;EACA,YAAA;EACA,0BAAA;EACA,sBAAA;CjDkkKH;AiDxkKC;;;;;;;;;EAYI,eAAA;CjDukKL;AiDnlKC;;;EAeI,eAAA;CjDykKL;AkD3qKC;EACE,eAAA;EACA,0BAAA;ClD6qKH;AkD3qKG;;EAEE,eAAA;ClD6qKL;AkD/qKG;;EAKI,eAAA;ClD8qKP;AkD3qKK;;;;EAEE,eAAA;EACA,0BAAA;ClD+qKP;AkD7qKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDkrKP;AkDxsKC;EACE,eAAA;EACA,0BAAA;ClD0sKH;AkDxsKG;;EAEE,eAAA;ClD0sKL;AkD5sKG;;EAKI,eAAA;ClD2sKP;AkDxsKK;;;;EAEE,eAAA;EACA,0BAAA;ClD4sKP;AkD1sKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD+sKP;AkDruKC;EACE,eAAA;EACA,0BAAA;ClDuuKH;AkDruKG;;EAEE,eAAA;ClDuuKL;AkDzuKG;;EAKI,eAAA;ClDwuKP;AkDruKK;;;;EAEE,eAAA;EACA,0BAAA;ClDyuKP;AkDvuKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD4uKP;AkDlwKC;EACE,eAAA;EACA,0BAAA;ClDowKH;AkDlwKG;;EAEE,eAAA;ClDowKL;AkDtwKG;;EAKI,eAAA;ClDqwKP;AkDlwKK;;;;EAEE,eAAA;EACA,0BAAA;ClDswKP;AkDpwKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDywKP;AiDxqKD;EACE,cAAA;EACA,mBAAA;CjD0qKD;AiDxqKD;EACE,iBAAA;EACA,iBAAA;CjD0qKD;AmDpyKD;EACE,oBAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;E9C0DA,kDAAA;EACQ,0CAAA;CL6uKT;AmDnyKD;EACE,cAAA;CnDqyKD;AmDhyKD;EACE,mBAAA;EACA,qCAAA;EvBpBA,6BAAA;EACC,4BAAA;C5BuzKF;AmDtyKD;EAMI,eAAA;CnDmyKH;AmD9xKD;EACE,cAAA;EACA,iBAAA;EACA,gBAAA;EACA,eAAA;CnDgyKD;AmDpyKD;;;;;EAWI,eAAA;CnDgyKH;AmD3xKD;EACE,mBAAA;EACA,0BAAA;EACA,2BAAA;EvBxCA,gCAAA;EACC,+BAAA;C5Bs0KF;AmDrxKD;;EAGI,iBAAA;CnDsxKH;AmDzxKD;;EAMM,oBAAA;EACA,iBAAA;CnDuxKL;AmDnxKG;;EAEI,cAAA;EvBvEN,6BAAA;EACC,4BAAA;C5B61KF;AmDjxKG;;EAEI,iBAAA;EvBvEN,gCAAA;EACC,+BAAA;C5B21KF;AmD1yKD;EvB1DE,2BAAA;EACC,0BAAA;C5Bu2KF;AmD7wKD;EAEI,oBAAA;CnD8wKH;AmD3wKD;EACE,oBAAA;CnD6wKD;AmDrwKD;;;EAII,iBAAA;CnDswKH;AmD1wKD;;;EAOM,mBAAA;EACA,oBAAA;CnDwwKL;AmDhxKD;;EvBzGE,6BAAA;EACC,4BAAA;C5B63KF;AmDrxKD;;;;EAmBQ,4BAAA;EACA,6BAAA;CnDwwKP;AmD5xKD;;;;;;;;EAwBU,4BAAA;CnD8wKT;AmDtyKD;;;;;;;;EA4BU,6BAAA;CnDoxKT;AmDhzKD;;EvBjGE,gCAAA;EACC,+BAAA;C5Bq5KF;AmDrzKD;;;;EAyCQ,+BAAA;EACA,gCAAA;CnDkxKP;AmD5zKD;;;;;;;;EA8CU,+BAAA;CnDwxKT;AmDt0KD;;;;;;;;EAkDU,gCAAA;CnD8xKT;AmDh1KD;;;;EA2DI,2BAAA;CnD2xKH;AmDt1KD;;EA+DI,cAAA;CnD2xKH;AmD11KD;;EAmEI,UAAA;CnD2xKH;AmD91KD;;;;;;;;;;;;EA0EU,eAAA;CnDkyKT;AmD52KD;;;;;;;;;;;;EA8EU,gBAAA;CnD4yKT;AmD13KD;;;;;;;;EAuFU,iBAAA;CnD6yKT;AmDp4KD;;;;;;;;EAgGU,iBAAA;CnD8yKT;AmD94KD;EAsGI,UAAA;EACA,iBAAA;CnD2yKH;AmDjyKD;EACE,oBAAA;CnDmyKD;AmDpyKD;EAKI,iBAAA;EACA,mBAAA;CnDkyKH;AmDxyKD;EASM,gBAAA;CnDkyKL;AmD3yKD;EAcI,iBAAA;CnDgyKH;AmD9yKD;;EAkBM,2BAAA;CnDgyKL;AmDlzKD;EAuBI,cAAA;CnD8xKH;AmDrzKD;EAyBM,8BAAA;CnD+xKL;AmDxxKD;EC1PE,mBAAA;CpDqhLD;AoDnhLC;EACE,eAAA;EACA,0BAAA;EACA,mBAAA;CpDqhLH;AoDxhLC;EAMI,uBAAA;CpDqhLL;AoD3hLC;EASI,eAAA;EACA,0BAAA;CpDqhLL;AoDlhLC;EAEI,0BAAA;CpDmhLL;AmDvyKD;EC7PE,sBAAA;CpDuiLD;AoDriLC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CpDuiLH;AoD1iLC;EAMI,0BAAA;CpDuiLL;AoD7iLC;EASI,eAAA;EACA,uBAAA;CpDuiLL;AoDpiLC;EAEI,6BAAA;CpDqiLL;AmDtzKD;EChQE,sBAAA;CpDyjLD;AoDvjLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpDyjLH;AoD5jLC;EAMI,0BAAA;CpDyjLL;AoD/jLC;EASI,eAAA;EACA,0BAAA;CpDyjLL;AoDtjLC;EAEI,6BAAA;CpDujLL;AmDr0KD;ECnQE,sBAAA;CpD2kLD;AoDzkLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD2kLH;AoD9kLC;EAMI,0BAAA;CpD2kLL;AoDjlLC;EASI,eAAA;EACA,0BAAA;CpD2kLL;AoDxkLC;EAEI,6BAAA;CpDykLL;AmDp1KD;ECtQE,sBAAA;CpD6lLD;AoD3lLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD6lLH;AoDhmLC;EAMI,0BAAA;CpD6lLL;AoDnmLC;EASI,eAAA;EACA,0BAAA;CpD6lLL;AoD1lLC;EAEI,6BAAA;CpD2lLL;AmDn2KD;ECzQE,sBAAA;CpD+mLD;AoD7mLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD+mLH;AoDlnLC;EAMI,0BAAA;CpD+mLL;AoDrnLC;EASI,eAAA;EACA,0BAAA;CpD+mLL;AoD5mLC;EAEI,6BAAA;CpD6mLL;AqD7nLD;EACE,mBAAA;EACA,eAAA;EACA,UAAA;EACA,WAAA;EACA,iBAAA;CrD+nLD;AqDpoLD;;;;;EAYI,mBAAA;EACA,OAAA;EACA,QAAA;EACA,UAAA;EACA,aAAA;EACA,YAAA;EACA,UAAA;CrD+nLH;AqD1nLD;EACE,uBAAA;CrD4nLD;AqDxnLD;EACE,oBAAA;CrD0nLD;AsDrpLD;EACE,iBAAA;EACA,cAAA;EACA,oBAAA;EACA,0BAAA;EACA,0BAAA;EACA,mBAAA;EjDwDA,wDAAA;EACQ,gDAAA;CLgmLT;AsD/pLD;EASI,mBAAA;EACA,kCAAA;CtDypLH;AsDppLD;EACE,cAAA;EACA,mBAAA;CtDspLD;AsDppLD;EACE,aAAA;EACA,mBAAA;CtDspLD;AuD5qLD;EACE,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,0BAAA;EjCRA,aAAA;EAGA,0BAAA;CtBqrLD;AuD7qLC;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;EjCfF,aAAA;EAGA,0BAAA;CtB6rLD;AuDzqLC;EACE,WAAA;EACA,gBAAA;EACA,wBAAA;EACA,UAAA;EACA,yBAAA;CvD2qLH;AwDhsLD;EACE,iBAAA;CxDksLD;AwD9rLD;EACE,cAAA;EACA,iBAAA;EACA,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,kCAAA;EAIA,WAAA;CxD6rLD;AwD1rLC;EnD+GA,sCAAA;EACI,kCAAA;EACC,iCAAA;EACG,8BAAA;EAkER,oDAAA;EAEK,0CAAA;EACG,oCAAA;CL6gLT;AwDhsLC;EnD2GA,mCAAA;EACI,+BAAA;EACC,8BAAA;EACG,2BAAA;CLwlLT;AwDpsLD;EACE,mBAAA;EACA,iBAAA;CxDssLD;AwDlsLD;EACE,mBAAA;EACA,YAAA;EACA,aAAA;CxDosLD;AwDhsLD;EACE,mBAAA;EACA,uBAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EnDaA,iDAAA;EACQ,yCAAA;EmDZR,qCAAA;UAAA,6BAAA;EAEA,WAAA;CxDksLD;AwD9rLD;EACE,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,uBAAA;CxDgsLD;AwD9rLC;ElCrEA,WAAA;EAGA,yBAAA;CtBowLD;AwDjsLC;ElCtEA,aAAA;EAGA,0BAAA;CtBwwLD;AwDhsLD;EACE,cAAA;EACA,iCAAA;CxDksLD;AwD9rLD;EACE,iBAAA;CxDgsLD;AwD5rLD;EACE,UAAA;EACA,wBAAA;CxD8rLD;AwDzrLD;EACE,mBAAA;EACA,cAAA;CxD2rLD;AwDvrLD;EACE,cAAA;EACA,kBAAA;EACA,8BAAA;CxDyrLD;AwD5rLD;EAQI,iBAAA;EACA,iBAAA;CxDurLH;AwDhsLD;EAaI,kBAAA;CxDsrLH;AwDnsLD;EAiBI,eAAA;CxDqrLH;AwDhrLD;EACE,mBAAA;EACA,aAAA;EACA,YAAA;EACA,aAAA;EACA,iBAAA;CxDkrLD;AwDhqLD;EAZE;IACE,aAAA;IACA,kBAAA;GxD+qLD;EwD7qLD;InDvEA,kDAAA;IACQ,0CAAA;GLuvLP;EwD5qLD;IAAY,aAAA;GxD+qLX;CACF;AwD1qLD;EAFE;IAAY,aAAA;GxDgrLX;CACF;AyD/zLD;EACE,mBAAA;EACA,cAAA;EACA,eAAA;ECRA,4DAAA;EAEA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;EDHA,gBAAA;EnCVA,WAAA;EAGA,yBAAA;CtBs1LD;AyD30LC;EnCdA,aAAA;EAGA,0BAAA;CtB01LD;AyD90LC;EAAW,iBAAA;EAAmB,eAAA;CzDk1L/B;AyDj1LC;EAAW,iBAAA;EAAmB,eAAA;CzDq1L/B;AyDp1LC;EAAW,gBAAA;EAAmB,eAAA;CzDw1L/B;AyDv1LC;EAAW,kBAAA;EAAmB,eAAA;CzD21L/B;AyDv1LD;EACE,iBAAA;EACA,iBAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,mBAAA;CzDy1LD;AyDr1LD;EACE,mBAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;CzDu1LD;AyDn1LC;EACE,UAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,uBAAA;CzDq1LH;AyDn1LC;EACE,UAAA;EACA,WAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzDq1LH;AyDn1LC;EACE,UAAA;EACA,UAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzDq1LH;AyDn1LC;EACE,SAAA;EACA,QAAA;EACA,iBAAA;EACA,4BAAA;EACA,yBAAA;CzDq1LH;AyDn1LC;EACE,SAAA;EACA,SAAA;EACA,iBAAA;EACA,4BAAA;EACA,wBAAA;CzDq1LH;AyDn1LC;EACE,OAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,0BAAA;CzDq1LH;AyDn1LC;EACE,OAAA;EACA,WAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzDq1LH;AyDn1LC;EACE,OAAA;EACA,UAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzDq1LH;A2Dl7LD;EACE,mBAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,iBAAA;EACA,aAAA;EDXA,4DAAA;EAEA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;ECAA,gBAAA;EAEA,uBAAA;EACA,qCAAA;UAAA,6BAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EtD8CA,kDAAA;EACQ,0CAAA;CLk5LT;A2D77LC;EAAY,kBAAA;C3Dg8Lb;A2D/7LC;EAAY,kBAAA;C3Dk8Lb;A2Dj8LC;EAAY,iBAAA;C3Do8Lb;A2Dn8LC;EAAY,mBAAA;C3Ds8Lb;A2Dn8LD;EACE,UAAA;EACA,kBAAA;EACA,gBAAA;EACA,0BAAA;EACA,iCAAA;EACA,2BAAA;C3Dq8LD;A2Dl8LD;EACE,kBAAA;C3Do8LD;A2D57LC;;EAEE,mBAAA;EACA,eAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;C3D87LH;A2D37LD;EACE,mBAAA;C3D67LD;A2D37LD;EACE,mBAAA;EACA,YAAA;C3D67LD;A2Dz7LC;EACE,UAAA;EACA,mBAAA;EACA,uBAAA;EACA,0BAAA;EACA,sCAAA;EACA,cAAA;C3D27LH;A2D17LG;EACE,aAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,uBAAA;C3D47LL;A2Dz7LC;EACE,SAAA;EACA,YAAA;EACA,kBAAA;EACA,qBAAA;EACA,4BAAA;EACA,wCAAA;C3D27LH;A2D17LG;EACE,aAAA;EACA,UAAA;EACA,cAAA;EACA,qBAAA;EACA,yBAAA;C3D47LL;A2Dz7LC;EACE,UAAA;EACA,mBAAA;EACA,oBAAA;EACA,6BAAA;EACA,yCAAA;EACA,WAAA;C3D27LH;A2D17LG;EACE,aAAA;EACA,SAAA;EACA,mBAAA;EACA,oBAAA;EACA,0BAAA;C3D47LL;A2Dx7LC;EACE,SAAA;EACA,aAAA;EACA,kBAAA;EACA,sBAAA;EACA,2BAAA;EACA,uCAAA;C3D07LH;A2Dz7LG;EACE,aAAA;EACA,WAAA;EACA,sBAAA;EACA,wBAAA;EACA,cAAA;C3D27LL;A4DpjMD;EACE,mBAAA;C5DsjMD;A4DnjMD;EACE,mBAAA;EACA,iBAAA;EACA,YAAA;C5DqjMD;A4DxjMD;EAMI,cAAA;EACA,mBAAA;EvD6KF,0CAAA;EACK,qCAAA;EACG,kCAAA;CLy4LT;A4D/jMD;;EAcM,eAAA;C5DqjML;A4D3hMC;EA4NF;IvD3DE,uDAAA;IAEK,6CAAA;IACG,uCAAA;IA7JR,oCAAA;IAEQ,4BAAA;IA+GR,4BAAA;IAEQ,oBAAA;GL86LP;E4DzjMG;;IvDmHJ,2CAAA;IACQ,mCAAA;IuDjHF,QAAA;G5D4jML;E4D1jMG;;IvD8GJ,4CAAA;IACQ,oCAAA;IuD5GF,QAAA;G5D6jML;E4D3jMG;;;IvDyGJ,wCAAA;IACQ,gCAAA;IuDtGF,QAAA;G5D8jML;CACF;A4DpmMD;;;EA6CI,eAAA;C5D4jMH;A4DzmMD;EAiDI,QAAA;C5D2jMH;A4D5mMD;;EAsDI,mBAAA;EACA,OAAA;EACA,YAAA;C5D0jMH;A4DlnMD;EA4DI,WAAA;C5DyjMH;A4DrnMD;EA+DI,YAAA;C5DyjMH;A4DxnMD;;EAmEI,QAAA;C5DyjMH;A4D5nMD;EAuEI,YAAA;C5DwjMH;A4D/nMD;EA0EI,WAAA;C5DwjMH;A4DhjMD;EACE,mBAAA;EACA,OAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EtC9FA,aAAA;EAGA,0BAAA;EsC6FA,gBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;EACA,mCAAA;C5DmjMD;A4D9iMC;EdnGE,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,4BAAA;EACA,uHAAA;C9CopMH;A4DljMC;EACE,WAAA;EACA,SAAA;EdxGA,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,4BAAA;EACA,uHAAA;C9C6pMH;A4DpjMC;;EAEE,WAAA;EACA,YAAA;EACA,sBAAA;EtCvHF,aAAA;EAGA,0BAAA;CtB4qMD;A4DtlMD;;;;EAuCI,mBAAA;EACA,SAAA;EACA,kBAAA;EACA,WAAA;EACA,sBAAA;C5DqjMH;A4DhmMD;;EA+CI,UAAA;EACA,mBAAA;C5DqjMH;A4DrmMD;;EAoDI,WAAA;EACA,oBAAA;C5DqjMH;A4D1mMD;;EAyDI,YAAA;EACA,aAAA;EACA,eAAA;EACA,mBAAA;C5DqjMH;A4DhjMG;EACE,iBAAA;C5DkjML;A4D9iMG;EACE,iBAAA;C5DgjML;A4DtiMD;EACE,mBAAA;EACA,aAAA;EACA,UAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;C5DwiMD;A4DjjMD;EAYI,sBAAA;EACA,YAAA;EACA,aAAA;EACA,YAAA;EACA,oBAAA;EACA,uBAAA;EACA,oBAAA;EACA,gBAAA;EAWA,0BAAA;EACA,mCAAA;C5D8hMH;A4D7jMD;EAkCI,UAAA;EACA,YAAA;EACA,aAAA;EACA,uBAAA;C5D8hMH;A4DvhMD;EACE,mBAAA;EACA,UAAA;EACA,WAAA;EACA,aAAA;EACA,YAAA;EACA,kBAAA;EACA,qBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;C5DyhMD;A4DxhMC;EACE,kBAAA;C5D0hMH;A4Dj/LD;EAhCE;;;;IAKI,YAAA;IACA,aAAA;IACA,kBAAA;IACA,gBAAA;G5DmhMH;E4D3hMD;;IAYI,mBAAA;G5DmhMH;E4D/hMD;;IAgBI,oBAAA;G5DmhMH;E4D9gMD;IACE,UAAA;IACA,WAAA;IACA,qBAAA;G5DghMD;E4D5gMD;IACE,aAAA;G5D8gMD;CACF;A6D7wMC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEE,aAAA;EACA,eAAA;C7D6yMH;A6D3yMC;;;;;;;;;;;;;;;;EACE,YAAA;C7D4zMH;AiCp0MD;E6BRE,eAAA;EACA,kBAAA;EACA,mBAAA;C9D+0MD;AiCt0MD;EACE,wBAAA;CjCw0MD;AiCt0MD;EACE,uBAAA;CjCw0MD;AiCh0MD;EACE,yBAAA;CjCk0MD;AiCh0MD;EACE,0BAAA;CjCk0MD;AiCh0MD;EACE,mBAAA;CjCk0MD;AiCh0MD;E8BzBE,YAAA;EACA,mBAAA;EACA,kBAAA;EACA,8BAAA;EACA,UAAA;C/D41MD;AiC9zMD;EACE,yBAAA;CjCg0MD;AiCzzMD;EACE,gBAAA;CjC2zMD;AgE51MD;EACE,oBAAA;ChE81MD;AgEx1MD;;;;ECdE,yBAAA;CjE42MD;AgEv1MD;;;;;;;;;;;;EAYE,yBAAA;ChEy1MD;AgEl1MD;EA6IA;IC7LE,0BAAA;GjEs4MC;EiEr4MD;IAAU,0BAAA;GjEw4MT;EiEv4MD;IAAU,8BAAA;GjE04MT;EiEz4MD;;IACU,+BAAA;GjE44MT;CACF;AgE51MD;EAwIA;IA1II,0BAAA;GhEk2MD;CACF;AgE51MD;EAmIA;IArII,2BAAA;GhEk2MD;CACF;AgE51MD;EA8HA;IAhII,iCAAA;GhEk2MD;CACF;AgE31MD;EAwHA;IC7LE,0BAAA;GjEo6MC;EiEn6MD;IAAU,0BAAA;GjEs6MT;EiEr6MD;IAAU,8BAAA;GjEw6MT;EiEv6MD;;IACU,+BAAA;GjE06MT;CACF;AgEr2MD;EAmHA;IArHI,0BAAA;GhE22MD;CACF;AgEr2MD;EA8GA;IAhHI,2BAAA;GhE22MD;CACF;AgEr2MD;EAyGA;IA3GI,iCAAA;GhE22MD;CACF;AgEp2MD;EAmGA;IC7LE,0BAAA;GjEk8MC;EiEj8MD;IAAU,0BAAA;GjEo8MT;EiEn8MD;IAAU,8BAAA;GjEs8MT;EiEr8MD;;IACU,+BAAA;GjEw8MT;CACF;AgE92MD;EA8FA;IAhGI,0BAAA;GhEo3MD;CACF;AgE92MD;EAyFA;IA3FI,2BAAA;GhEo3MD;CACF;AgE92MD;EAoFA;IAtFI,iCAAA;GhEo3MD;CACF;AgE72MD;EA8EA;IC7LE,0BAAA;GjEg+MC;EiE/9MD;IAAU,0BAAA;GjEk+MT;EiEj+MD;IAAU,8BAAA;GjEo+MT;EiEn+MD;;IACU,+BAAA;GjEs+MT;CACF;AgEv3MD;EAyEA;IA3EI,0BAAA;GhE63MD;CACF;AgEv3MD;EAoEA;IAtEI,2BAAA;GhE63MD;CACF;AgEv3MD;EA+DA;IAjEI,iCAAA;GhE63MD;CACF;AgEt3MD;EAyDA;ICrLE,yBAAA;GjEs/MC;CACF;AgEt3MD;EAoDA;ICrLE,yBAAA;GjE2/MC;CACF;AgEt3MD;EA+CA;ICrLE,yBAAA;GjEggNC;CACF;AgEt3MD;EA0CA;ICrLE,yBAAA;GjEqgNC;CACF;AgEn3MD;ECnJE,yBAAA;CjEygND;AgEh3MD;EA4BA;IC7LE,0BAAA;GjEqhNC;EiEphND;IAAU,0BAAA;GjEuhNT;EiEthND;IAAU,8BAAA;GjEyhNT;EiExhND;;IACU,+BAAA;GjE2hNT;CACF;AgE93MD;EACE,yBAAA;ChEg4MD;AgE33MD;EAqBA;IAvBI,0BAAA;GhEi4MD;CACF;AgE/3MD;EACE,yBAAA;ChEi4MD;AgE53MD;EAcA;IAhBI,2BAAA;GhEk4MD;CACF;AgEh4MD;EACE,yBAAA;ChEk4MD;AgE73MD;EAOA;IATI,iCAAA;GhEm4MD;CACF;AgE53MD;EACA;ICrLE,yBAAA;GjEojNC;CACF","file":"bootstrap.css","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('../fonts/glyphicons-halflings-regular.eot');\n src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\002a\";\n}\n.glyphicon-plus:before {\n content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-btc:before {\n content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\n[role=\"button\"] {\n cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n background-color: #fcf8e3;\n padding: .2em;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #fff;\n background-color: #333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n word-break: break-all;\n word-wrap: break-word;\n color: #333333;\n background-color: #f5f5f5;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #ddd;\n}\n.table .table {\n background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #ddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n min-width: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #fff;\n background-image: none;\n border: 1px solid #ccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999;\n}\n.form-control::-ms-expand {\n border: 0;\n background-color: transparent;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: #eeeeee;\n opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"].form-control,\n input[type=\"time\"].form-control,\n input[type=\"datetime-local\"].form-control,\n input[type=\"month\"].form-control {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n min-height: 34px;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.form-group-sm select.form-control {\n height: 30px;\n line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n min-height: 32px;\n padding: 6px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.form-group-lg select.form-control {\n height: 46px;\n line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n min-height: 38px;\n padding: 11px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n border-color: #3c763d;\n background-color: #dff0d8;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n border-color: #8a6d3b;\n background-color: #fcf8e3;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n border-color: #a94442;\n background-color: #f2dede;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 11px;\n font-size: 18px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n font-size: 12px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n outline: 0;\n background-image: none;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n opacity: 0.65;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n pointer-events: none;\n}\n.btn-default {\n color: #333;\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n color: #333;\n background-color: #e6e6e6;\n border-color: #8c8c8c;\n}\n.btn-default:hover {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n color: #333;\n background-color: #d4d4d4;\n border-color: #8c8c8c;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default .badge {\n color: #fff;\n background-color: #333;\n}\n.btn-primary {\n color: #fff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n color: #fff;\n background-color: #286090;\n border-color: #122b40;\n}\n.btn-primary:hover {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n color: #fff;\n background-color: #204d74;\n border-color: #122b40;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.btn-success {\n color: #fff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n color: #fff;\n background-color: #449d44;\n border-color: #255625;\n}\n.btn-success:hover {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n color: #fff;\n background-color: #398439;\n border-color: #255625;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #fff;\n}\n.btn-info {\n color: #fff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n color: #fff;\n background-color: #31b0d5;\n border-color: #1b6d85;\n}\n.btn-info:hover {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n color: #fff;\n background-color: #269abc;\n border-color: #1b6d85;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #fff;\n}\n.btn-warning {\n color: #fff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n color: #fff;\n background-color: #ec971f;\n border-color: #985f0d;\n}\n.btn-warning:hover {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n color: #fff;\n background-color: #d58512;\n border-color: #985f0d;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #fff;\n}\n.btn-danger {\n color: #fff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n color: #fff;\n background-color: #c9302c;\n border-color: #761c19;\n}\n.btn-danger:hover {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n color: #fff;\n background-color: #ac2925;\n border-color: #761c19;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #fff;\n}\n.btn-link {\n color: #337ab7;\n font-weight: normal;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n}\n.collapse.in {\n display: block;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px dashed;\n border-top: 4px solid \\9;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #fff;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n background-clip: padding-box;\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n text-decoration: none;\n color: #262626;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n background-color: #337ab7;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n border-top: 0;\n border-bottom: 4px dashed;\n border-bottom: 4px solid \\9;\n content: \"\";\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group .form-control:focus {\n z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n z-index: 2;\n margin-left: -1px;\n}\n.nav {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n background-color: #fff;\n border: 1px solid #ddd;\n border-bottom-color: transparent;\n cursor: default;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #fff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-left: 0;\n padding-right: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: 15px;\n padding: 9px 10px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n margin-left: -15px;\n margin-right: -15px;\n padding: 10px 15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-left: 15px;\n margin-right: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777;\n}\n.navbar-default .navbar-link:hover {\n color: #333;\n}\n.navbar-default .btn-link {\n color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #ccc;\n}\n.navbar-inverse {\n background-color: #222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #fff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #fff;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #fff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #fff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n content: \"/\\00a0\";\n padding: 0 5px;\n color: #ccc;\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #fff;\n border: 1px solid #ddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n z-index: 2;\n color: #23527c;\n background-color: #eeeeee;\n border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 3;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n background-color: #fff;\n border-color: #ddd;\n cursor: not-allowed;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n background-color: #fff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n color: #fff;\n line-height: 1;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding-top: 30px;\n padding-bottom: 30px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n border-radius: 6px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding-top: 48px;\n padding-bottom: 48px;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-left: 60px;\n padding-right: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-left: auto;\n margin-right: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #fff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-object.img-thumbnail {\n max-width: none;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n margin-bottom: 20px;\n padding-left: 0;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item,\nbutton.list-group-item {\n color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n text-decoration: none;\n color: #555;\n background-color: #f5f5f5;\n}\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #fff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #ddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-left-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n border: 0;\n margin-bottom: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #ddd;\n}\n.panel-default {\n border-color: #ddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n}\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #fff;\n border: 1px solid #999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n background-clip: padding-box;\n outline: 0;\n}\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n.modal-backdrop.fade {\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 12px;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n bottom: 0;\n right: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 14px;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n margin: 0;\n padding: 8px 14px;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #fff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #fff;\n}\n.popover.bottom > .arrow {\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\n border-top-width: 0;\n border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #fff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000px;\n -moz-perspective: 1000px;\n perspective: 1000px;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\n font-size: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #fff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n margin-top: -10px;\n z-index: 5;\n display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n line-height: 1;\n font-family: serif;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #fff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #fff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -10px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -10px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -10px;\n }\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n content: \" \";\n display: table;\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table !important;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table !important;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table !important;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table !important;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table !important;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS and IE text size adjust after device orientation change,\n// without disabling user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability of focused elements when they are also in an\n// active/hover state.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome.\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n box-sizing: content-box; //2\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// Star\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\002a\"; } }\n.glyphicon-plus { &:before { content: \"\\002b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-btc { &:before { content: \"\\e227\"; } }\n.glyphicon-xbt { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-jpy { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-rub { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n\n\n// iOS \"clickable elements\" fix for role=\"button\"\n//\n// Fixes \"clickability\" issue (and more generally, the firing of events such as focus as well)\n// for traditionally non-focusable elements with role=\"button\"\n// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n\n[role=\"button\"] {\n cursor: pointer;\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // WebKit-specific. Other browsers will keep their default outline style.\n // (Initially tried to also force default via `outline: initial`,\n // but that seems to erroneously remove the outline in Firefox altogether.)\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @dl-horizontal-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n .text-uppercase();\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover,\n a&:focus {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover,\n a&:focus {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: floor((@gutter / 2));\n padding-right: ceil((@gutter / 2));\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: ceil((@gutter / -2));\n margin-right: floor((@gutter / -2));\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: ceil((@grid-gutter-width / 2));\n padding-right: floor((@grid-gutter-width / 2));\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Unstyle the caret on ``\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n .opacity(.65);\n .box-shadow(none);\n }\n\n a& {\n &.disabled,\n fieldset[disabled] & {\n pointer-events: none; // Future-proof disabling of clicks on `` elements\n }\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 25%);\n }\n &:hover {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n\n &:hover,\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 17%);\n border-color: darken(@border, 25%);\n }\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus,\n &.focus {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n\n &.in { display: block; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base dashed;\n border-top: @caret-width-base solid ~\"\\9\"; // IE8\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base dashed;\n border-bottom: @caret-width-base solid ~\"\\9\"; // IE8\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn,\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n .border-top-radius(@btn-border-radius-base);\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n .border-top-radius(0);\n .border-bottom-radius(@btn-border-radius-base);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n\n &:focus {\n z-index: 3;\n }\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @input-border-radius;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @input-border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @input-border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n z-index: 2;\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n }\n > .active {\n display: block;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n","// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 3;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n","// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n\n .btn-xs &,\n .btn-group-xs > .btn & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n\n .list-group-item > & {\n float: right;\n }\n\n .list-group-item > & + & {\n margin-right: 5px;\n }\n\n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding-top: @jumbotron-padding;\n padding-bottom: @jumbotron-padding;\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n\n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding-top: (@jumbotron-padding * 1.6);\n padding-bottom: (@jumbotron-padding * 1.6);\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: @jumbotron-heading-font-size;\n }\n }\n}\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n\n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n\n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n",".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n\n // Fix collapse in webkit from max-width: 100% and display: table-cell.\n &.img-thumbnail {\n max-width: none;\n }\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on
    ,
      , or
      .\n\n.list-group {\n // No need to set list-style: none; since .list-group-item is block level\n margin-bottom: 20px;\n padding-left: 0; // reset padding because ul and ol\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -1px;\n background-color: @list-group-bg;\n border: 1px solid @list-group-border;\n\n // Round the first and last items\n &:first-child {\n .border-top-radius(@list-group-border-radius);\n }\n &:last-child {\n margin-bottom: 0;\n .border-bottom-radius(@list-group-border-radius);\n }\n}\n\n\n// Interactive list items\n//\n// Use anchor or button elements instead of `li`s or `div`s to create interactive items.\n// Includes an extra `.active` modifier class for showing selected items.\n\na.list-group-item,\nbutton.list-group-item {\n color: @list-group-link-color;\n\n .list-group-item-heading {\n color: @list-group-link-heading-color;\n }\n\n // Hover state\n &:hover,\n &:focus {\n text-decoration: none;\n color: @list-group-link-hover-color;\n background-color: @list-group-hover-bg;\n }\n}\n\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n\n.list-group-item {\n // Disabled state\n &.disabled,\n &.disabled:hover,\n &.disabled:focus {\n background-color: @list-group-disabled-bg;\n color: @list-group-disabled-color;\n cursor: @cursor-disabled;\n\n // Force color to inherit for custom content\n .list-group-item-heading {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-disabled-text-color;\n }\n }\n\n // Active class on item itself, not parent\n &.active,\n &.active:hover,\n &.active:focus {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: @list-group-active-color;\n background-color: @list-group-active-bg;\n border-color: @list-group-active-border;\n\n // Force color to inherit for custom content\n .list-group-item-heading,\n .list-group-item-heading > small,\n .list-group-item-heading > .small {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-active-text-color;\n }\n }\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n.list-group-item-variant(success; @state-success-bg; @state-success-text);\n.list-group-item-variant(info; @state-info-bg; @state-info-text);\n.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);\n.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);\n\n\n// Custom content options\n//\n// Extra classes for creating well-formatted content within `.list-group-item`s.\n\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n","// List Groups\n\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a&,\n button& {\n color: @color;\n\n .list-group-item-heading {\n color: inherit;\n }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n","//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n margin-bottom: @line-height-computed;\n background-color: @panel-bg;\n border: 1px solid transparent;\n border-radius: @panel-border-radius;\n .box-shadow(0 1px 1px rgba(0,0,0,.05));\n}\n\n// Panel contents\n.panel-body {\n padding: @panel-body-padding;\n &:extend(.clearfix all);\n}\n\n// Optional heading\n.panel-heading {\n padding: @panel-heading-padding;\n border-bottom: 1px solid transparent;\n .border-top-radius((@panel-border-radius - 1));\n\n > .dropdown .dropdown-toggle {\n color: inherit;\n }\n}\n\n// Within heading, strip any `h*` tag of its default margins for spacing.\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: ceil((@font-size-base * 1.125));\n color: inherit;\n\n > a,\n > small,\n > .small,\n > small > a,\n > .small > a {\n color: inherit;\n }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n padding: @panel-footer-padding;\n background-color: @panel-footer-bg;\n border-top: 1px solid @panel-inner-border;\n .border-bottom-radius((@panel-border-radius - 1));\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n > .list-group,\n > .panel-collapse > .list-group {\n margin-bottom: 0;\n\n .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n }\n\n // Add border top radius for first one\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n .border-top-radius((@panel-border-radius - 1));\n }\n }\n\n // Add border bottom radius for last one\n &:last-child {\n .list-group-item:last-child {\n border-bottom: 0;\n .border-bottom-radius((@panel-border-radius - 1));\n }\n }\n }\n > .panel-heading + .panel-collapse > .list-group {\n .list-group-item:first-child {\n .border-top-radius(0);\n }\n }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n .list-group-item:first-child {\n border-top-width: 0;\n }\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n > .table,\n > .table-responsive > .table,\n > .panel-collapse > .table {\n margin-bottom: 0;\n\n caption {\n padding-left: @panel-body-padding;\n padding-right: @panel-body-padding;\n }\n }\n // Add border top radius for first one\n > .table:first-child,\n > .table-responsive:first-child > .table:first-child {\n .border-top-radius((@panel-border-radius - 1));\n\n > thead:first-child,\n > tbody:first-child {\n > tr:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n border-top-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-top-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n // Add border bottom radius for last one\n > .table:last-child,\n > .table-responsive:last-child > .table:last-child {\n .border-bottom-radius((@panel-border-radius - 1));\n\n > tbody:last-child,\n > tfoot:last-child {\n > tr:last-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n border-bottom-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n > .panel-body + .table,\n > .panel-body + .table-responsive,\n > .table + .panel-body,\n > .table-responsive + .panel-body {\n border-top: 1px solid @table-border-color;\n }\n > .table > tbody:first-child > tr:first-child th,\n > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n }\n > .table-bordered,\n > .table-responsive > .table-bordered {\n border: 0;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n > thead,\n > tbody {\n > tr:first-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n > tbody,\n > tfoot {\n > tr:last-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n }\n > .table-responsive {\n border: 0;\n margin-bottom: 0;\n }\n}\n\n\n// Collapsible panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n margin-bottom: @line-height-computed;\n\n // Tighten up margin so it's only between panels\n .panel {\n margin-bottom: 0;\n border-radius: @panel-border-radius;\n\n + .panel {\n margin-top: 5px;\n }\n }\n\n .panel-heading {\n border-bottom: 0;\n\n + .panel-collapse > .panel-body,\n + .panel-collapse > .list-group {\n border-top: 1px solid @panel-inner-border;\n }\n }\n\n .panel-footer {\n border-top: 0;\n + .panel-collapse .panel-body {\n border-bottom: 1px solid @panel-inner-border;\n }\n }\n}\n\n\n// Contextual variations\n.panel-default {\n .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-info {\n .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n.panel-warning {\n .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n","// Panels\n\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse > .panel-body {\n border-top-color: @border;\n }\n .badge {\n color: @heading-bg-color;\n background-color: @heading-text-color;\n }\n }\n & > .panel-footer {\n + .panel-collapse > .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n","// Embeds responsive\n//\n// Credit: Nicolas Gallagher and SUIT CSS.\n\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n\n .embed-responsive-item,\n iframe,\n embed,\n object,\n video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n }\n}\n\n// Modifier class for 16:9 aspect ratio\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n\n// Modifier class for 4:3 aspect ratio\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n","//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: @well-bg;\n border: 1px solid @well-border;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));\n blockquote {\n border-color: #ddd;\n border-color: rgba(0,0,0,.15);\n }\n}\n\n// Sizes\n.well-lg {\n padding: 24px;\n border-radius: @border-radius-large;\n}\n.well-sm {\n padding: 9px;\n border-radius: @border-radius-small;\n}\n","//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n float: right;\n font-size: (@font-size-base * 1.5);\n font-weight: @close-font-weight;\n line-height: 1;\n color: @close-color;\n text-shadow: @close-text-shadow;\n .opacity(.2);\n\n &:hover,\n &:focus {\n color: @close-color;\n text-decoration: none;\n cursor: pointer;\n .opacity(.5);\n }\n\n // Additional properties for button version\n // iOS requires the button element instead of an anchor tag.\n // If you want the anchor version, it requires `href=\"#\"`.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n button& {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n }\n}\n","//\n// Modals\n// --------------------------------------------------\n\n// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n overflow: hidden;\n}\n\n// Container that the modal scrolls within\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal;\n -webkit-overflow-scrolling: touch;\n\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n\n // When fading in the modal, animate it to slide down\n &.fade .modal-dialog {\n .translate(0, -25%);\n .transition-transform(~\"0.3s ease-out\");\n }\n &.in .modal-dialog { .translate(0, 0) }\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n background-color: @modal-content-bg;\n border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n border: 1px solid @modal-content-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 3px 9px rgba(0,0,0,.5));\n background-clip: padding-box;\n // Remove focus outline from opened modal\n outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal-background;\n background-color: @modal-backdrop-bg;\n // Fade for backdrop\n &.fade { .opacity(0); }\n &.in { .opacity(@modal-backdrop-opacity); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n padding: @modal-title-padding;\n border-bottom: 1px solid @modal-header-border-color;\n &:extend(.clearfix all);\n}\n// Close icon\n.modal-header .close {\n margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n margin: 0;\n line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n padding: @modal-inner-padding;\n text-align: right; // right align buttons\n border-top: 1px solid @modal-footer-border-color;\n &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons\n\n // Properly space out buttons\n .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n }\n // but override that for button groups\n .btn-group .btn + .btn {\n margin-left: -1px;\n }\n // and override it for block buttons as well\n .btn-block + .btn-block {\n margin-left: 0;\n }\n}\n\n// Measure scrollbar width for padding body during modal show/hide\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n// Scale up the modal\n@media (min-width: @screen-sm-min) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n width: @modal-md;\n margin: 30px auto;\n }\n .modal-content {\n .box-shadow(0 5px 15px rgba(0,0,0,.5));\n }\n\n // Modal sizes\n .modal-sm { width: @modal-sm; }\n}\n\n@media (min-width: @screen-md-min) {\n .modal-lg { width: @modal-lg; }\n}\n","//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n position: absolute;\n z-index: @zindex-tooltip;\n display: block;\n // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n .reset-text();\n font-size: @font-size-small;\n\n .opacity(0);\n\n &.in { .opacity(@tooltip-opacity); }\n &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }\n &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }\n &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }\n &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: @tooltip-max-width;\n padding: 3px 8px;\n color: @tooltip-color;\n text-align: center;\n background-color: @tooltip-bg;\n border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1\n.tooltip {\n &.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-left .tooltip-arrow {\n bottom: 0;\n right: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-right .tooltip-arrow {\n bottom: 0;\n left: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n border-right-color: @tooltip-arrow-color;\n }\n &.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-left-color: @tooltip-arrow-color;\n }\n &.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-left .tooltip-arrow {\n top: 0;\n right: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-right .tooltip-arrow {\n top: 0;\n left: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n}\n",".reset-text() {\n font-family: @font-family-base;\n // We deliberately do NOT reset font-size.\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: @line-height-base;\n text-align: left; // Fallback for where `start` is not supported\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n}\n","//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: @zindex-popover;\n display: none;\n max-width: @popover-max-width;\n padding: 1px;\n // Our parent element can be arbitrary since popovers are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n .reset-text();\n font-size: @font-size-base;\n\n background-color: @popover-bg;\n background-clip: padding-box;\n border: 1px solid @popover-fallback-border-color;\n border: 1px solid @popover-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 5px 10px rgba(0,0,0,.2));\n\n // Offset the popover to account for the popover arrow\n &.top { margin-top: -@popover-arrow-width; }\n &.right { margin-left: @popover-arrow-width; }\n &.bottom { margin-top: @popover-arrow-width; }\n &.left { margin-left: -@popover-arrow-width; }\n}\n\n.popover-title {\n margin: 0; // reset heading margin\n padding: 8px 14px;\n font-size: @font-size-base;\n background-color: @popover-title-bg;\n border-bottom: 1px solid darken(@popover-title-bg, 5%);\n border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;\n}\n\n.popover-content {\n padding: 9px 14px;\n}\n\n// Arrows\n//\n// .arrow is outer, .arrow:after is inner\n\n.popover > .arrow {\n &,\n &:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n }\n}\n.popover > .arrow {\n border-width: @popover-arrow-outer-width;\n}\n.popover > .arrow:after {\n border-width: @popover-arrow-width;\n content: \"\";\n}\n\n.popover {\n &.top > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-top-color: @popover-arrow-outer-color;\n bottom: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n bottom: 1px;\n margin-left: -@popover-arrow-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-color;\n }\n }\n &.right > .arrow {\n top: 50%;\n left: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-right-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n left: 1px;\n bottom: -@popover-arrow-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-color;\n }\n }\n &.bottom > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-bottom-color: @popover-arrow-outer-color;\n top: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n top: 1px;\n margin-left: -@popover-arrow-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-color;\n }\n }\n\n &.left > .arrow {\n top: 50%;\n right: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-width: 0;\n border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-left-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: @popover-arrow-color;\n bottom: -@popover-arrow-width;\n }\n }\n}\n","//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n\n > .item {\n display: none;\n position: relative;\n .transition(.6s ease-in-out left);\n\n // Account for jankitude on images\n > img,\n > a > img {\n &:extend(.img-responsive);\n line-height: 1;\n }\n\n // WebKit CSS3 transforms for supported devices\n @media all and (transform-3d), (-webkit-transform-3d) {\n .transition-transform(~'0.6s ease-in-out');\n .backface-visibility(~'hidden');\n .perspective(1000px);\n\n &.next,\n &.active.right {\n .translate3d(100%, 0, 0);\n left: 0;\n }\n &.prev,\n &.active.left {\n .translate3d(-100%, 0, 0);\n left: 0;\n }\n &.next.left,\n &.prev.right,\n &.active {\n .translate3d(0, 0, 0);\n left: 0;\n }\n }\n }\n\n > .active,\n > .next,\n > .prev {\n display: block;\n }\n\n > .active {\n left: 0;\n }\n\n > .next,\n > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n }\n\n > .next {\n left: 100%;\n }\n > .prev {\n left: -100%;\n }\n > .next.left,\n > .prev.right {\n left: 0;\n }\n\n > .active.left {\n left: -100%;\n }\n > .active.right {\n left: 100%;\n }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: @carousel-control-width;\n .opacity(@carousel-control-opacity);\n font-size: @carousel-control-font-size;\n color: @carousel-control-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n background-color: rgba(0, 0, 0, 0); // Fix IE9 click-thru bug\n // We can't have this transition here because WebKit cancels the carousel\n // animation if you trip this while in the middle of another animation.\n\n // Set gradients for backgrounds\n &.left {\n #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n }\n &.right {\n left: auto;\n right: 0;\n #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n }\n\n // Hover/focus state\n &:hover,\n &:focus {\n outline: 0;\n color: @carousel-control-color;\n text-decoration: none;\n .opacity(.9);\n }\n\n // Toggles\n .icon-prev,\n .icon-next,\n .glyphicon-chevron-left,\n .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n margin-top: -10px;\n z-index: 5;\n display: inline-block;\n }\n .icon-prev,\n .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n }\n .icon-next,\n .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n }\n .icon-prev,\n .icon-next {\n width: 20px;\n height: 20px;\n line-height: 1;\n font-family: serif;\n }\n\n\n .icon-prev {\n &:before {\n content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n }\n }\n .icon-next {\n &:before {\n content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n }\n }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n\n li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid @carousel-indicator-border-color;\n border-radius: 10px;\n cursor: pointer;\n\n // IE8-9 hack for event handling\n //\n // Internet Explorer 8-9 does not support clicks on elements without a set\n // `background-color`. We cannot use `filter` since that's not viewed as a\n // background color by the browser. Thus, a hack is needed.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer\n //\n // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n // set alpha transparency for the best results possible.\n background-color: #000 \\9; // IE8\n background-color: rgba(0,0,0,0); // IE9\n }\n .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: @carousel-indicator-active-bg;\n }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: @carousel-caption-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n & .btn {\n text-shadow: none; // No shadow for button elements in carousel-caption\n }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n // Scale up the controls a smidge\n .carousel-control {\n .glyphicon-chevron-left,\n .glyphicon-chevron-right,\n .icon-prev,\n .icon-next {\n width: (@carousel-control-font-size * 1.5);\n height: (@carousel-control-font-size * 1.5);\n margin-top: (@carousel-control-font-size / -2);\n font-size: (@carousel-control-font-size * 1.5);\n }\n .glyphicon-chevron-left,\n .icon-prev {\n margin-left: (@carousel-control-font-size / -2);\n }\n .glyphicon-chevron-right,\n .icon-next {\n margin-right: (@carousel-control-font-size / -2);\n }\n }\n\n // Show and left align the captions\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n\n // Move up the indicators\n .carousel-indicators {\n bottom: 20px;\n }\n}\n","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n","// Center-align a block level element\n\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n","// CSS image replacement\n//\n// Heads up! v3 launched with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (has been removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n","//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 in Windows (Phone) 8\n//\n// Support for responsive views via media queries is kind of borked in IE10, for\n// Surface/desktop in split view and for Windows Phone 8. This particular fix\n// must be accompanied by a snippet of JavaScript to sniff the user agent and\n// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at\n// our Getting Started page for more information on this bug.\n//\n// For more information, see the following:\n//\n// Issue: https://github.com/twbs/bootstrap/issues/10497\n// Docs: http://getbootstrap.com/getting-started/#support-ie10-width\n// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/\n// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n\n@-ms-viewport {\n width: device-width;\n}\n\n\n// Visibility utilities\n// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n .responsive-invisibility();\n}\n\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n\n.visible-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-visibility();\n }\n}\n.visible-xs-block {\n @media (max-width: @screen-xs-max) {\n display: block !important;\n }\n}\n.visible-xs-inline {\n @media (max-width: @screen-xs-max) {\n display: inline !important;\n }\n}\n.visible-xs-inline-block {\n @media (max-width: @screen-xs-max) {\n display: inline-block !important;\n }\n}\n\n.visible-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-visibility();\n }\n}\n.visible-sm-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: block !important;\n }\n}\n.visible-sm-inline {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline !important;\n }\n}\n.visible-sm-inline-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline-block !important;\n }\n}\n\n.visible-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-visibility();\n }\n}\n.visible-md-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: block !important;\n }\n}\n.visible-md-inline {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline !important;\n }\n}\n.visible-md-inline-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline-block !important;\n }\n}\n\n.visible-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-visibility();\n }\n}\n.visible-lg-block {\n @media (min-width: @screen-lg-min) {\n display: block !important;\n }\n}\n.visible-lg-inline {\n @media (min-width: @screen-lg-min) {\n display: inline !important;\n }\n}\n.visible-lg-inline-block {\n @media (min-width: @screen-lg-min) {\n display: inline-block !important;\n }\n}\n\n.hidden-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-invisibility();\n }\n}\n.hidden-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-invisibility();\n }\n}\n.hidden-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-invisibility();\n }\n}\n.hidden-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-invisibility();\n }\n}\n\n\n// Print utilities\n//\n// Media queries are placed on the inside to be mixin-friendly.\n\n// Note: Deprecated .visible-print as of v3.2.0\n.visible-print {\n .responsive-invisibility();\n\n @media print {\n .responsive-visibility();\n }\n}\n.visible-print-block {\n display: none !important;\n\n @media print {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n\n @media print {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n\n @media print {\n display: inline-block !important;\n }\n}\n\n.hidden-print {\n @media print {\n .responsive-invisibility();\n }\n}\n","// Responsive utilities\n\n//\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table !important; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n"]} \ No newline at end of file diff --git a/histview2/static/common/css/bootstrap.min.css b/histview2/static/common/css/bootstrap.min.css new file mode 100644 index 0000000..778a882 --- /dev/null +++ b/histview2/static/common/css/bootstrap.min.css @@ -0,0 +1,12 @@ +/*! + * Bootswatch v4.3.1 + * Homepage: https://bootswatch.com + * Copyright 2012-2019 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*//*! + * Bootstrap v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */:root{--blue: #375a7f;--indigo: #6610f2;--purple: #6f42c1;--pink: #e83e8c;--red: #E74C3C;--orange: #fd7e14;--yellow: #F39C12;--green: #00bc8c;--teal: #20c997;--cyan: #3498DB;--white: #fff;--gray: #999;--gray-dark: #303030;--primary: #375a7f;--secondary: #444;--success: #00bc8c;--info: #3498DB;--warning: #F39C12;--danger: #E74C3C;--light: #999;--dark: #303030;--breakpoint-xs: 0;--breakpoint-sm: 576px;--breakpoint-md: 768px;--breakpoint-lg: 992px;--breakpoint-xl: 1200px;--font-family-sans-serif:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}*,*::before,*::after{-webkit-box-sizing:border-box;box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-size:0.9375rem;font-weight:400;line-height:1.5;color:#fff;text-align:left;background-color:#222}[tabindex="-1"]:focus{outline:0 !important}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0.5rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-original-title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#00bc8c;text-decoration:none;background-color:transparent}a:hover{color:#007053;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):hover,a:not([href]):not([tabindex]):focus{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre,code,kbd,samp{font-family:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:0.75rem;padding-bottom:0.75rem;color:#999;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:0.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button:not(:disabled),[type="button"]:not(:disabled),[type="reset"]:not(:disabled),[type="submit"]:not(:disabled){cursor:pointer}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{padding:0;border-style:none}input[type="radio"],input[type="checkbox"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{outline-offset:-2px;-webkit-appearance:none}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none !important}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{margin-bottom:0.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:3rem}h2,.h2{font-size:2.5rem}h3,.h3{font-size:2rem}h4,.h4{font-size:1.40625rem}h5,.h5{font-size:1.171875rem}h6,.h6{font-size:0.9375rem}.lead{font-size:1.171875rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,0.1)}small,.small{font-size:80%;font-weight:400}mark,.mark{padding:0.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:0.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.171875rem}.blockquote-footer{display:block;font-size:80%;color:#999}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:0.25rem;background-color:#222;border:1px solid #dee2e6;border-radius:0.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:0.5rem;line-height:1}.figure-caption{font-size:90%;color:#999}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:0.2rem 0.4rem;font-size:87.5%;color:#fff;background-color:#222;border-radius:0.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:inherit}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width: 576px){.container{max-width:540px}}@media (min-width: 768px){.container{max-width:720px}}@media (min-width: 992px){.container{max-width:960px}}@media (min-width: 1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*="col-"]{padding-right:0;padding-left:0}.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col,.col-auto,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm,.col-sm-auto,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md,.col-md-auto,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg,.col-lg-auto,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-5{margin-left:41.6666666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333333333%}.offset-8{margin-left:66.6666666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333333333%}.offset-11{margin-left:91.6666666667%}@media (min-width: 576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.3333333333%}.offset-sm-2{margin-left:16.6666666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333333333%}.offset-sm-5{margin-left:41.6666666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333333333%}.offset-sm-8{margin-left:66.6666666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333333333%}.offset-sm-11{margin-left:91.6666666667%}}@media (min-width: 768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.3333333333%}.offset-md-2{margin-left:16.6666666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333333333%}.offset-md-5{margin-left:41.6666666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333333333%}.offset-md-8{margin-left:66.6666666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333333333%}.offset-md-11{margin-left:91.6666666667%}}@media (min-width: 992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.3333333333%}.offset-lg-2{margin-left:16.6666666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333333333%}.offset-lg-5{margin-left:41.6666666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333333333%}.offset-lg-8{margin-left:66.6666666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333333333%}.offset-lg-11{margin-left:91.6666666667%}}@media (min-width: 1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.3333333333%}.offset-xl-2{margin-left:16.6666666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333333333%}.offset-xl-5{margin-left:41.6666666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333333333%}.offset-xl-8{margin-left:66.6666666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333333333%}.offset-xl-11{margin-left:91.6666666667%}}.table{width:100%;margin-bottom:1rem;color:#fff}.table th,.table td{padding:0.75rem;vertical-align:top;border-top:1px solid #444}.table thead th{vertical-align:bottom;border-bottom:2px solid #444}.table tbody+tbody{border-top:2px solid #444}.table-sm th,.table-sm td{padding:0.3rem}.table-bordered{border:1px solid #444}.table-bordered th,.table-bordered td{border:1px solid #444}.table-bordered thead th,.table-bordered thead td{border-bottom-width:2px}.table-borderless th,.table-borderless td,.table-borderless thead th,.table-borderless tbody+tbody{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:#303030}.table-hover tbody tr:hover{color:#fff;background-color:rgba(0,0,0,0.075)}.table-primary,.table-primary>th,.table-primary>td{background-color:#c7d1db}.table-primary th,.table-primary td,.table-primary thead th,.table-primary tbody+tbody{border-color:#97a9bc}.table-hover .table-primary:hover{background-color:#b7c4d1}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#b7c4d1}.table-secondary,.table-secondary>th,.table-secondary>td{background-color:#cbcbcb}.table-secondary th,.table-secondary td,.table-secondary thead th,.table-secondary tbody+tbody{border-color:#9e9e9e}.table-hover .table-secondary:hover{background-color:#bebebe}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#bebebe}.table-success,.table-success>th,.table-success>td{background-color:#b8ecdf}.table-success th,.table-success td,.table-success thead th,.table-success tbody+tbody{border-color:#7adcc3}.table-hover .table-success:hover{background-color:#a4e7d6}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#a4e7d6}.table-info,.table-info>th,.table-info>td{background-color:#c6e2f5}.table-info th,.table-info td,.table-info thead th,.table-info tbody+tbody{border-color:#95c9ec}.table-hover .table-info:hover{background-color:#b0d7f1}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#b0d7f1}.table-warning,.table-warning>th,.table-warning>td{background-color:#fce3bd}.table-warning th,.table-warning td,.table-warning thead th,.table-warning tbody+tbody{border-color:#f9cc84}.table-hover .table-warning:hover{background-color:#fbd9a5}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#fbd9a5}.table-danger,.table-danger>th,.table-danger>td{background-color:#f8cdc8}.table-danger th,.table-danger td,.table-danger thead th,.table-danger tbody+tbody{border-color:#f3a29a}.table-hover .table-danger:hover{background-color:#f5b8b1}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f5b8b1}.table-light,.table-light>th,.table-light>td{background-color:#e2e2e2}.table-light th,.table-light td,.table-light thead th,.table-light tbody+tbody{border-color:#cacaca}.table-hover .table-light:hover{background-color:#d5d5d5}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#d5d5d5}.table-dark,.table-dark>th,.table-dark>td{background-color:#c5c5c5}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#939393}.table-hover .table-dark:hover{background-color:#b8b8b8}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b8b8b8}.table-active,.table-active>th,.table-active>td{background-color:rgba(0,0,0,0.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,0.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,0.075)}.table .thead-dark th{color:#fff;background-color:#303030;border-color:#434343}.table .thead-light th{color:#444;background-color:#ebebeb;border-color:#444}.table-dark{color:#fff;background-color:#303030}.table-dark th,.table-dark td,.table-dark thead th{border-color:#434343}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,0.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,0.075)}@media (max-width: 575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width: 767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width: 991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width: 1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + 0.75rem + 2px);padding:0.375rem 0.75rem;font-size:0.9375rem;font-weight:400;line-height:1.5;color:#444;background-color:#fff;background-clip:padding-box;border:1px solid transparent;border-radius:0.25rem;-webkit-transition:border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control{-webkit-transition:none;transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#444;background-color:#fff;border-color:#739ac2;outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.form-control::-webkit-input-placeholder{color:#999;opacity:1}.form-control::-ms-input-placeholder{color:#999;opacity:1}.form-control::placeholder{color:#999;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#ebebeb;opacity:1}select.form-control:focus::-ms-value{color:#444;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.171875rem;line-height:1.5}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.8203125rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:0.375rem;padding-bottom:0.375rem;margin-bottom:0;line-height:1.5;color:#fff;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + 0.5rem + 2px);padding:0.25rem 0.5rem;font-size:0.8203125rem;line-height:1.5;border-radius:0.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:0.5rem 1rem;font-size:1.171875rem;line-height:1.5;border-radius:0.3rem}select.form-control[size],select.form-control[multiple]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:0.25rem}.form-row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*="col-"]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:0.3rem;margin-left:-1.25rem}.form-check-input:disabled ~ .form-check-label{color:#999}.form-check-label{margin-bottom:0}.form-check-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:0.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:0.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:0.25rem;font-size:80%;color:#00bc8c}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:0.25rem 0.5rem;margin-top:.1rem;font-size:0.8203125rem;line-height:1.5;color:#fff;background-color:rgba(0,188,140,0.9);border-radius:0.25rem}.was-validated .form-control:valid,.form-control.is-valid{border-color:#00bc8c;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2300bc8c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(0.375em + 0.1875rem);background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#00bc8c;-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25)}.was-validated .form-control:valid ~ .valid-feedback,.was-validated .form-control:valid ~ .valid-tooltip,.form-control.is-valid ~ .valid-feedback,.form-control.is-valid ~ .valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .custom-select:valid,.custom-select.is-valid{border-color:#00bc8c;padding-right:calc((1em + 0.75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23303030' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2300bc8c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .custom-select:valid:focus,.custom-select.is-valid:focus{border-color:#00bc8c;-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25)}.was-validated .custom-select:valid ~ .valid-feedback,.was-validated .custom-select:valid ~ .valid-tooltip,.custom-select.is-valid ~ .valid-feedback,.custom-select.is-valid ~ .valid-tooltip{display:block}.was-validated .form-control-file:valid ~ .valid-feedback,.was-validated .form-control-file:valid ~ .valid-tooltip,.form-control-file.is-valid ~ .valid-feedback,.form-control-file.is-valid ~ .valid-tooltip{display:block}.was-validated .form-check-input:valid ~ .form-check-label,.form-check-input.is-valid ~ .form-check-label{color:#00bc8c}.was-validated .form-check-input:valid ~ .valid-feedback,.was-validated .form-check-input:valid ~ .valid-tooltip,.form-check-input.is-valid ~ .valid-feedback,.form-check-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-control-input:valid ~ .custom-control-label,.custom-control-input.is-valid ~ .custom-control-label{color:#00bc8c}.was-validated .custom-control-input:valid ~ .custom-control-label::before,.custom-control-input.is-valid ~ .custom-control-label::before{border-color:#00bc8c}.was-validated .custom-control-input:valid ~ .valid-feedback,.was-validated .custom-control-input:valid ~ .valid-tooltip,.custom-control-input.is-valid ~ .valid-feedback,.custom-control-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before,.custom-control-input.is-valid:checked ~ .custom-control-label::before{border-color:#00efb2;background-color:#00efb2}.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before,.custom-control-input.is-valid:focus ~ .custom-control-label::before{-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25)}.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before,.custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before{border-color:#00bc8c}.was-validated .custom-file-input:valid ~ .custom-file-label,.custom-file-input.is-valid ~ .custom-file-label{border-color:#00bc8c}.was-validated .custom-file-input:valid ~ .valid-feedback,.was-validated .custom-file-input:valid ~ .valid-tooltip,.custom-file-input.is-valid ~ .valid-feedback,.custom-file-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-file-input:valid:focus ~ .custom-file-label,.custom-file-input.is-valid:focus ~ .custom-file-label{border-color:#00bc8c;-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25)}.invalid-feedback{display:none;width:100%;margin-top:0.25rem;font-size:80%;color:#E74C3C}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:0.25rem 0.5rem;margin-top:.1rem;font-size:0.8203125rem;line-height:1.5;color:#fff;background-color:rgba(231,76,60,0.9);border-radius:0.25rem}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#E74C3C;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23E74C3C' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23E74C3C' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(0.375em + 0.1875rem);background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#E74C3C;-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25)}.was-validated .form-control:invalid ~ .invalid-feedback,.was-validated .form-control:invalid ~ .invalid-tooltip,.form-control.is-invalid ~ .invalid-feedback,.form-control.is-invalid ~ .invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .custom-select:invalid,.custom-select.is-invalid{border-color:#E74C3C;padding-right:calc((1em + 0.75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23303030' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23E74C3C' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23E74C3C' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .custom-select:invalid:focus,.custom-select.is-invalid:focus{border-color:#E74C3C;-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25)}.was-validated .custom-select:invalid ~ .invalid-feedback,.was-validated .custom-select:invalid ~ .invalid-tooltip,.custom-select.is-invalid ~ .invalid-feedback,.custom-select.is-invalid ~ .invalid-tooltip{display:block}.was-validated .form-control-file:invalid ~ .invalid-feedback,.was-validated .form-control-file:invalid ~ .invalid-tooltip,.form-control-file.is-invalid ~ .invalid-feedback,.form-control-file.is-invalid ~ .invalid-tooltip{display:block}.was-validated .form-check-input:invalid ~ .form-check-label,.form-check-input.is-invalid ~ .form-check-label{color:#E74C3C}.was-validated .form-check-input:invalid ~ .invalid-feedback,.was-validated .form-check-input:invalid ~ .invalid-tooltip,.form-check-input.is-invalid ~ .invalid-feedback,.form-check-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-control-input:invalid ~ .custom-control-label,.custom-control-input.is-invalid ~ .custom-control-label{color:#E74C3C}.was-validated .custom-control-input:invalid ~ .custom-control-label::before,.custom-control-input.is-invalid ~ .custom-control-label::before{border-color:#E74C3C}.was-validated .custom-control-input:invalid ~ .invalid-feedback,.was-validated .custom-control-input:invalid ~ .invalid-tooltip,.custom-control-input.is-invalid ~ .invalid-feedback,.custom-control-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before,.custom-control-input.is-invalid:checked ~ .custom-control-label::before{border-color:#ed7669;background-color:#ed7669}.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before,.custom-control-input.is-invalid:focus ~ .custom-control-label::before{-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25)}.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before,.custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before{border-color:#E74C3C}.was-validated .custom-file-input:invalid ~ .custom-file-label,.custom-file-input.is-invalid ~ .custom-file-label{border-color:#E74C3C}.was-validated .custom-file-input:invalid ~ .invalid-feedback,.was-validated .custom-file-input:invalid ~ .invalid-tooltip,.custom-file-input.is-invalid ~ .invalid-feedback,.custom-file-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-file-input:invalid:focus ~ .custom-file-label,.custom-file-input.is-invalid:focus ~ .custom-file-label{border-color:#E74C3C;-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25)}.form-inline{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width: 576px){.form-inline label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group,.form-inline .custom-select{width:auto}.form-inline .form-check{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:0.25rem;margin-left:0}.form-inline .custom-control{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#fff;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:0.375rem 0.75rem;font-size:0.9375rem;line-height:1.5;border-radius:0.25rem;-webkit-transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.btn{-webkit-transition:none;transition:none}}.btn:hover{color:#fff;text-decoration:none}.btn:focus,.btn.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.btn.disabled,.btn:disabled{opacity:0.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-primary:hover{color:#fff;background-color:#2b4764;border-color:#28415b}.btn-primary:focus,.btn-primary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(85,115,146,0.5);box-shadow:0 0 0 0.2rem rgba(85,115,146,0.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-primary:not(:disabled):not(.disabled):active,.btn-primary:not(:disabled):not(.disabled).active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#28415b;border-color:#243a53}.btn-primary:not(:disabled):not(.disabled):active:focus,.btn-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-primary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(85,115,146,0.5);box-shadow:0 0 0 0.2rem rgba(85,115,146,0.5)}.btn-secondary{color:#fff;background-color:#444;border-color:#444}.btn-secondary:hover{color:#fff;background-color:#313131;border-color:#2b2a2a}.btn-secondary:focus,.btn-secondary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(96,96,96,0.5);box-shadow:0 0 0 0.2rem rgba(96,96,96,0.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#444;border-color:#444}.btn-secondary:not(:disabled):not(.disabled):active,.btn-secondary:not(:disabled):not(.disabled).active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#2b2a2a;border-color:#242424}.btn-secondary:not(:disabled):not(.disabled):active:focus,.btn-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-secondary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(96,96,96,0.5);box-shadow:0 0 0 0.2rem rgba(96,96,96,0.5)}.btn-success{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-success:hover{color:#fff;background-color:#009670;border-color:#008966}.btn-success:focus,.btn-success.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(38,198,157,0.5);box-shadow:0 0 0 0.2rem rgba(38,198,157,0.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-success:not(:disabled):not(.disabled):active,.btn-success:not(:disabled):not(.disabled).active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#008966;border-color:#007c5d}.btn-success:not(:disabled):not(.disabled):active:focus,.btn-success:not(:disabled):not(.disabled).active:focus,.show>.btn-success.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(38,198,157,0.5);box-shadow:0 0 0 0.2rem rgba(38,198,157,0.5)}.btn-info{color:#fff;background-color:#3498DB;border-color:#3498DB}.btn-info:hover{color:#fff;background-color:#2384c6;border-color:#217dbb}.btn-info:focus,.btn-info.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(82,167,224,0.5);box-shadow:0 0 0 0.2rem rgba(82,167,224,0.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#3498DB;border-color:#3498DB}.btn-info:not(:disabled):not(.disabled):active,.btn-info:not(:disabled):not(.disabled).active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#217dbb;border-color:#1f76b0}.btn-info:not(:disabled):not(.disabled):active:focus,.btn-info:not(:disabled):not(.disabled).active:focus,.show>.btn-info.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(82,167,224,0.5);box-shadow:0 0 0 0.2rem rgba(82,167,224,0.5)}.btn-warning{color:#fff;background-color:#F39C12;border-color:#F39C12}.btn-warning:hover{color:#fff;background-color:#d4860b;border-color:#c87f0a}.btn-warning:focus,.btn-warning.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(245,171,54,0.5);box-shadow:0 0 0 0.2rem rgba(245,171,54,0.5)}.btn-warning.disabled,.btn-warning:disabled{color:#fff;background-color:#F39C12;border-color:#F39C12}.btn-warning:not(:disabled):not(.disabled):active,.btn-warning:not(:disabled):not(.disabled).active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#c87f0a;border-color:#bc770a}.btn-warning:not(:disabled):not(.disabled):active:focus,.btn-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-warning.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(245,171,54,0.5);box-shadow:0 0 0 0.2rem rgba(245,171,54,0.5)}.btn-danger{color:#fff;background-color:#E74C3C;border-color:#E74C3C}.btn-danger:hover{color:#fff;background-color:#e12e1c;border-color:#d62c1a}.btn-danger:focus,.btn-danger.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(235,103,89,0.5);box-shadow:0 0 0 0.2rem rgba(235,103,89,0.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#E74C3C;border-color:#E74C3C}.btn-danger:not(:disabled):not(.disabled):active,.btn-danger:not(:disabled):not(.disabled).active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#d62c1a;border-color:#ca2a19}.btn-danger:not(:disabled):not(.disabled):active:focus,.btn-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-danger.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(235,103,89,0.5);box-shadow:0 0 0 0.2rem rgba(235,103,89,0.5)}.btn-light{color:#fff;background-color:#999;border-color:#999}.btn-light:hover{color:#fff;background-color:#868686;border-color:#807f7f}.btn-light:focus,.btn-light.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(168,168,168,0.5);box-shadow:0 0 0 0.2rem rgba(168,168,168,0.5)}.btn-light.disabled,.btn-light:disabled{color:#fff;background-color:#999;border-color:#999}.btn-light:not(:disabled):not(.disabled):active,.btn-light:not(:disabled):not(.disabled).active,.show>.btn-light.dropdown-toggle{color:#fff;background-color:#807f7f;border-color:#797979}.btn-light:not(:disabled):not(.disabled):active:focus,.btn-light:not(:disabled):not(.disabled).active:focus,.show>.btn-light.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(168,168,168,0.5);box-shadow:0 0 0 0.2rem rgba(168,168,168,0.5)}.btn-dark{color:#fff;background-color:#303030;border-color:#303030}.btn-dark:hover{color:#fff;background-color:#1d1d1d;border-color:#171616}.btn-dark:focus,.btn-dark.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(79,79,79,0.5);box-shadow:0 0 0 0.2rem rgba(79,79,79,0.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#303030;border-color:#303030}.btn-dark:not(:disabled):not(.disabled):active,.btn-dark:not(:disabled):not(.disabled).active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#171616;border-color:#101010}.btn-dark:not(:disabled):not(.disabled):active:focus,.btn-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-dark.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(79,79,79,0.5);box-shadow:0 0 0 0.2rem rgba(79,79,79,0.5)}.btn-outline-primary{color:#375a7f;border-color:#375a7f}.btn-outline-primary:hover{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-outline-primary:focus,.btn-outline-primary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#375a7f;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled):active,.btn-outline-primary:not(:disabled):not(.disabled).active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5)}.btn-outline-secondary{color:#444;border-color:#444}.btn-outline-secondary:hover{color:#fff;background-color:#444;border-color:#444}.btn-outline-secondary:focus,.btn-outline-secondary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5);box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#444;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled):active,.btn-outline-secondary:not(:disabled):not(.disabled).active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#444;border-color:#444}.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5);box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5)}.btn-outline-success{color:#00bc8c;border-color:#00bc8c}.btn-outline-success:hover{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-outline-success:focus,.btn-outline-success.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#00bc8c;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled):active,.btn-outline-success:not(:disabled):not(.disabled).active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-outline-success:not(:disabled):not(.disabled):active:focus,.btn-outline-success:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-success.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5)}.btn-outline-info{color:#3498DB;border-color:#3498DB}.btn-outline-info:hover{color:#fff;background-color:#3498DB;border-color:#3498DB}.btn-outline-info:focus,.btn-outline-info.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5);box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#3498DB;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled):active,.btn-outline-info:not(:disabled):not(.disabled).active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#3498DB;border-color:#3498DB}.btn-outline-info:not(:disabled):not(.disabled):active:focus,.btn-outline-info:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-info.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5);box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5)}.btn-outline-warning{color:#F39C12;border-color:#F39C12}.btn-outline-warning:hover{color:#fff;background-color:#F39C12;border-color:#F39C12}.btn-outline-warning:focus,.btn-outline-warning.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5);box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#F39C12;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled):active,.btn-outline-warning:not(:disabled):not(.disabled).active,.show>.btn-outline-warning.dropdown-toggle{color:#fff;background-color:#F39C12;border-color:#F39C12}.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5);box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5)}.btn-outline-danger{color:#E74C3C;border-color:#E74C3C}.btn-outline-danger:hover{color:#fff;background-color:#E74C3C;border-color:#E74C3C}.btn-outline-danger:focus,.btn-outline-danger.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#E74C3C;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled):active,.btn-outline-danger:not(:disabled):not(.disabled).active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#E74C3C;border-color:#E74C3C}.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5)}.btn-outline-light{color:#999;border-color:#999}.btn-outline-light:hover{color:#fff;background-color:#999;border-color:#999}.btn-outline-light:focus,.btn-outline-light.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(153,153,153,0.5);box-shadow:0 0 0 0.2rem rgba(153,153,153,0.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#999;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled):active,.btn-outline-light:not(:disabled):not(.disabled).active,.show>.btn-outline-light.dropdown-toggle{color:#fff;background-color:#999;border-color:#999}.btn-outline-light:not(:disabled):not(.disabled):active:focus,.btn-outline-light:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-light.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(153,153,153,0.5);box-shadow:0 0 0 0.2rem rgba(153,153,153,0.5)}.btn-outline-dark{color:#303030;border-color:#303030}.btn-outline-dark:hover{color:#fff;background-color:#303030;border-color:#303030}.btn-outline-dark:focus,.btn-outline-dark.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5);box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#303030;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled):active,.btn-outline-dark:not(:disabled):not(.disabled).active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#303030;border-color:#303030}.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5);box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5)}.btn-link{font-weight:400;color:#00bc8c;text-decoration:none}.btn-link:hover{color:#007053;text-decoration:underline}.btn-link:focus,.btn-link.focus{text-decoration:underline;-webkit-box-shadow:none;box-shadow:none}.btn-link:disabled,.btn-link.disabled{color:#999;pointer-events:none}.btn-lg,.btn-group-lg>.btn{padding:0.5rem 1rem;font-size:1.171875rem;line-height:1.5;border-radius:0.3rem}.btn-sm,.btn-group-sm>.btn{padding:0.25rem 0.5rem;font-size:0.8203125rem;line-height:1.5;border-radius:0.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:0.5rem}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{-webkit-transition:opacity 0.15s linear;transition:opacity 0.15s linear}@media (prefers-reduced-motion: reduce){.fade{-webkit-transition:none;transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;transition:height 0.35s ease}@media (prefers-reduced-motion: reduce){.collapsing{-webkit-transition:none;transition:none}}.dropup,.dropright,.dropdown,.dropleft{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:"";border-top:0.3em solid;border-right:0.3em solid transparent;border-bottom:0;border-left:0.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:0.5rem 0;margin:0.125rem 0 0;font-size:0.9375rem;color:#fff;text-align:left;list-style:none;background-color:#222;background-clip:padding-box;border:1px solid #444;border-radius:0.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width: 576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width: 768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width: 992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width: 1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:0.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:"";border-top:0;border-right:0.3em solid transparent;border-bottom:0.3em solid;border-left:0.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:0.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:"";border-top:0.3em solid transparent;border-right:0;border-bottom:0.3em solid transparent;border-left:0.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:0.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:0.255em;vertical-align:0.255em;content:"";border-top:0.3em solid transparent;border-right:0.3em solid;border-bottom:0.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^="top"],.dropdown-menu[x-placement^="right"],.dropdown-menu[x-placement^="bottom"],.dropdown-menu[x-placement^="left"]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:0.5rem 0;overflow:hidden;border-top:1px solid #444}.dropdown-item{display:block;width:100%;padding:0.25rem 1.5rem;clear:both;font-weight:400;color:#fff;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:hover,.dropdown-item:focus{color:#fff;text-decoration:none;background-color:#375a7f}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#375a7f}.dropdown-item.disabled,.dropdown-item:disabled{color:#999;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:0.5rem 1.5rem;margin-bottom:0;font-size:0.8203125rem;color:#999;white-space:nowrap}.dropdown-item-text{display:block;padding:0.25rem 1.5rem;color:#fff}.btn-group,.btn-group-vertical{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover{z-index:1}.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:0.5625rem;padding-left:0.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:0.375rem;padding-left:0.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:0.75rem;padding-left:0.75rem}.btn-group-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type="radio"],.btn-group-toggle>.btn input[type="checkbox"],.btn-group-toggle>.btn-group>.btn input[type="radio"],.btn-group-toggle>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-control-plaintext,.input-group>.custom-select,.input-group>.custom-file{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.form-control+.form-control,.input-group>.form-control+.custom-select,.input-group>.form-control+.custom-file,.input-group>.form-control-plaintext+.form-control,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.custom-file,.input-group>.custom-select+.form-control,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.custom-file,.input-group>.custom-file+.form-control,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.custom-file{margin-left:-1px}.input-group>.form-control:focus,.input-group>.custom-select:focus,.input-group>.custom-file .custom-file-input:focus ~ .custom-file-label{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.form-control:not(:last-child),.input-group>.custom-select:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.form-control:not(:first-child),.input-group>.custom-select:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-prepend,.input-group-append{display:-webkit-box;display:-ms-flexbox;display:flex}.input-group-prepend .btn,.input-group-append .btn{position:relative;z-index:2}.input-group-prepend .btn:focus,.input-group-append .btn:focus{z-index:3}.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.input-group-text,.input-group-append .input-group-text+.btn{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0.375rem 0.75rem;margin-bottom:0;font-size:0.9375rem;font-weight:400;line-height:1.5;color:#adb5bd;text-align:center;white-space:nowrap;background-color:#444;border:1px solid transparent;border-radius:0.25rem}.input-group-text input[type="radio"],.input-group-text input[type="checkbox"]{margin-top:0}.input-group-lg>.form-control:not(textarea),.input-group-lg>.custom-select{height:calc(1.5em + 1rem + 2px)}.input-group-lg>.form-control,.input-group-lg>.custom-select,.input-group-lg>.input-group-prepend>.input-group-text,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-append>.btn{padding:0.5rem 1rem;font-size:1.171875rem;line-height:1.5;border-radius:0.3rem}.input-group-sm>.form-control:not(textarea),.input-group-sm>.custom-select{height:calc(1.5em + 0.5rem + 2px)}.input-group-sm>.form-control,.input-group-sm>.custom-select,.input-group-sm>.input-group-prepend>.input-group-text,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-append>.btn{padding:0.25rem 0.5rem;font-size:0.8203125rem;line-height:1.5;border-radius:0.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text,.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.40625rem;padding-left:1.5rem}.custom-control-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked ~ .custom-control-label::before{color:#fff;border-color:#375a7f;background-color:#375a7f}.custom-control-input:focus ~ .custom-control-label::before{-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-control-input:focus:not(:checked) ~ .custom-control-label::before{border-color:#739ac2}.custom-control-input:not(:disabled):active ~ .custom-control-label::before{color:#fff;background-color:#97b3d2;border-color:#97b3d2}.custom-control-input:disabled ~ .custom-control-label{color:#999}.custom-control-input:disabled ~ .custom-control-label::before{background-color:#ebebeb}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:0.203125rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:0.203125rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50% / 50% 50%}.custom-checkbox .custom-control-label::before{border-radius:0.25rem}.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before{border-color:#375a7f;background-color:#375a7f}.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(55,90,127,0.5)}.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before{background-color:rgba(55,90,127,0.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(55,90,127,0.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:0.5rem}.custom-switch .custom-control-label::after{top:calc(0.203125rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:0.5rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.custom-switch .custom-control-label::after{-webkit-transition:none;transition:none}}.custom-switch .custom-control-input:checked ~ .custom-control-label::after{background-color:#fff;-webkit-transform:translateX(0.75rem);transform:translateX(0.75rem)}.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(55,90,127,0.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + 0.75rem + 2px);padding:0.375rem 1.75rem 0.375rem 0.75rem;font-size:0.9375rem;font-weight:400;line-height:1.5;color:#444;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23303030' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px;background-color:#fff;border:1px solid transparent;border-radius:0.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#739ac2;outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-select:focus::-ms-value{color:#444;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:0.75rem;background-image:none}.custom-select:disabled{color:#999;background-color:#ebebeb}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + 0.5rem + 2px);padding-top:0.25rem;padding-bottom:0.25rem;padding-left:0.5rem;font-size:0.8203125rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:0.5rem;padding-bottom:0.5rem;padding-left:1rem;font-size:1.171875rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + 0.75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + 0.75rem + 2px);margin:0;opacity:0}.custom-file-input:focus ~ .custom-file-label{border-color:#739ac2;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-file-input:disabled ~ .custom-file-label{background-color:#ebebeb}.custom-file-input:lang(en) ~ .custom-file-label::after{content:"Browse"}.custom-file-input ~ .custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + 0.75rem + 2px);padding:0.375rem 0.75rem;font-weight:400;line-height:1.5;color:#adb5bd;background-color:#fff;border:1px solid #444;border-radius:0.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + 0.75rem);padding:0.375rem 0.75rem;line-height:1.5;color:#adb5bd;content:"Browse";background-color:#444;border-left:inherit;border-radius:0 0.25rem 0.25rem 0}.custom-range{width:100%;height:calc(1rem + 0.4rem);padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:none}.custom-range:focus::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 1px #222,0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 1px #222,0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #222,0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #222,0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#375a7f;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#97b3d2}.custom-range::-webkit-slider-runnable-track{width:100%;height:0.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#375a7f;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-moz-range-thumb{-webkit-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#97b3d2}.custom-range::-moz-range-track{width:100%;height:0.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:0.2rem;margin-left:0.2rem;background-color:#375a7f;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-ms-thumb{-webkit-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#97b3d2}.custom-range::-ms-track{width:100%;height:0.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:0.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.custom-control-label::before,.custom-file-label,.custom-select{-webkit-transition:none;transition:none}}.nav{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:0.5rem 2rem}.nav-link:hover,.nav-link:focus{text-decoration:none}.nav-link.disabled{color:#adb5bd;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #444}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#444 #444 transparent}.nav-tabs .nav-link.disabled{color:#adb5bd;background-color:transparent;border-color:transparent}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#fff;background-color:#222;border-color:#444 #444 transparent}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:0.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#375a7f}.nav-fill .nav-item{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:0.32421875rem;padding-bottom:0.32421875rem;margin-right:1rem;font-size:1.171875rem;line-height:inherit;white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:0.5rem;padding-bottom:0.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:0.25rem 0.75rem;font-size:1.171875rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:0.25rem}.navbar-toggler:hover,.navbar-toggler:focus{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width: 575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 576px){.navbar-expand-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width: 767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 768px){.navbar-expand-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width: 991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 992px){.navbar-expand-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width: 1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 1200px){.navbar-expand-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:#fff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fff}.navbar-light .navbar-nav .nav-link{color:#fff}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:#00bc8c}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(255,255,255,0.3)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .nav-link.active{color:#fff}.navbar-light .navbar-toggler{color:#fff;border-color:rgba(255,255,255,0.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#fff}.navbar-light .navbar-text a{color:#fff}.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fff}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,0.6)}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:#fff}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,0.25)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .nav-link.active{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,0.6);border-color:rgba(255,255,255,0.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.6)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,0.6)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fff}.card{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#303030;background-clip:border-box;border:1px solid rgba(0,0,0,0.125);border-radius:0.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:0.25rem;border-bottom-left-radius:0.25rem}.card-body{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:0.75rem}.card-subtitle{margin-top:-0.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:0.75rem 1.25rem;margin-bottom:0;background-color:#444;border-bottom:1px solid rgba(0,0,0,0.125)}.card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:0.75rem 1.25rem;background-color:#444;border-top:1px solid rgba(0,0,0,0.125)}.card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.card-header-tabs{margin-right:-0.625rem;margin-bottom:-0.75rem;margin-left:-0.625rem;border-bottom:0}.card-header-pills{margin-right:-0.625rem;margin-left:-0.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(0.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card-deck{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width: 576px){.card-deck{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width: 576px){.card-group{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:0.75rem}@media (min-width: 576px){.card-columns{-webkit-column-count:3;column-count:3;-webkit-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#444;border-radius:0.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:0.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:0.5rem;color:#999;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#999}.pagination{display:-webkit-box;display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:0.25rem}.page-link{position:relative;display:block;padding:0.5rem 0.75rem;margin-left:0;line-height:1.25;color:#fff;background-color:#00bc8c;border:0 solid transparent}.page-link:hover{z-index:2;color:#fff;text-decoration:none;background-color:#00efb2;border-color:transparent}.page-link:focus{z-index:2;outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem}.page-item:last-child .page-link{border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#00efb2;border-color:transparent}.page-item.disabled .page-link{color:#fff;pointer-events:none;cursor:auto;background-color:#007053;border-color:transparent}.pagination-lg .page-link{padding:0.75rem 1.5rem;font-size:1.171875rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:0.3rem;border-bottom-left-radius:0.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:0.3rem;border-bottom-right-radius:0.3rem}.pagination-sm .page-link{padding:0.25rem 0.5rem;font-size:0.8203125rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:0.2rem;border-bottom-left-radius:0.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:0.2rem;border-bottom-right-radius:0.2rem}.badge{display:inline-block;padding:0.25em 0.4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:0.25rem;-webkit-transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.badge{-webkit-transition:none;transition:none}}a.badge:hover,a.badge:focus{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:0.6em;padding-left:0.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#375a7f}a.badge-primary:hover,a.badge-primary:focus{color:#fff;background-color:#28415b}a.badge-primary:focus,a.badge-primary.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5)}.badge-secondary{color:#fff;background-color:#444}a.badge-secondary:hover,a.badge-secondary:focus{color:#fff;background-color:#2b2a2a}a.badge-secondary:focus,a.badge-secondary.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5);box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5)}.badge-success{color:#fff;background-color:#00bc8c}a.badge-success:hover,a.badge-success:focus{color:#fff;background-color:#008966}a.badge-success:focus,a.badge-success.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5)}.badge-info{color:#fff;background-color:#3498DB}a.badge-info:hover,a.badge-info:focus{color:#fff;background-color:#217dbb}a.badge-info:focus,a.badge-info.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5);box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5)}.badge-warning{color:#fff;background-color:#F39C12}a.badge-warning:hover,a.badge-warning:focus{color:#fff;background-color:#c87f0a}a.badge-warning:focus,a.badge-warning.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5);box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5)}.badge-danger{color:#fff;background-color:#E74C3C}a.badge-danger:hover,a.badge-danger:focus{color:#fff;background-color:#d62c1a}a.badge-danger:focus,a.badge-danger.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5)}.badge-light{color:#fff;background-color:#999}a.badge-light:hover,a.badge-light:focus{color:#fff;background-color:#807f7f}a.badge-light:focus,a.badge-light.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(153,153,153,0.5);box-shadow:0 0 0 0.2rem rgba(153,153,153,0.5)}.badge-dark{color:#fff;background-color:#303030}a.badge-dark:hover,a.badge-dark:focus{color:#fff;background-color:#171616}a.badge-dark:focus,a.badge-dark.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5);box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#303030;border-radius:0.3rem}@media (min-width: 576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:0.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:0.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3.90625rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:0.75rem 1.25rem;color:inherit}.alert-primary{color:#1d2f42;background-color:#d7dee5;border-color:#c7d1db}.alert-primary hr{border-top-color:#b7c4d1}.alert-primary .alert-link{color:#0d161f}.alert-secondary{color:#232323;background-color:#dadada;border-color:#cbcbcb}.alert-secondary hr{border-top-color:#bebebe}.alert-secondary .alert-link{color:#0a0909}.alert-success{color:#006249;background-color:#ccf2e8;border-color:#b8ecdf}.alert-success hr{border-top-color:#a4e7d6}.alert-success .alert-link{color:#002f23}.alert-info{color:#1b4f72;background-color:#d6eaf8;border-color:#c6e2f5}.alert-info hr{border-top-color:#b0d7f1}.alert-info .alert-link{color:#113249}.alert-warning{color:#7e5109;background-color:#fdebd0;border-color:#fce3bd}.alert-warning hr{border-top-color:#fbd9a5}.alert-warning .alert-link{color:#4e3206}.alert-danger{color:#78281f;background-color:#fadbd8;border-color:#f8cdc8}.alert-danger hr{border-top-color:#f5b8b1}.alert-danger .alert-link{color:#4f1a15}.alert-light{color:#505050;background-color:#ebebeb;border-color:#e2e2e2}.alert-light hr{border-top-color:#d5d5d5}.alert-light .alert-link{color:#373636}.alert-dark{color:#191919;background-color:#d6d6d6;border-color:#c5c5c5}.alert-dark hr{border-top-color:#b8b8b8}.alert-dark .alert-link{color:black}@-webkit-keyframes progress-bar-stripes{from{background-position:0.625rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:0.625rem 0}to{background-position:0 0}}.progress{display:-webkit-box;display:-ms-flexbox;display:flex;height:0.625rem;overflow:hidden;font-size:0.625rem;background-color:#444;border-radius:0.25rem}.progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#375a7f;-webkit-transition:width 0.6s ease;transition:width 0.6s ease}@media (prefers-reduced-motion: reduce){.progress-bar{-webkit-transition:none;transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:0.625rem 0.625rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion: reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.media-body{-webkit-box-flex:1;-ms-flex:1;flex:1}.list-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#444;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#444;text-decoration:none;background-color:#444}.list-group-item-action:active{color:#fff;background-color:#ebebeb}.list-group-item{position:relative;display:block;padding:0.75rem 1.25rem;margin-bottom:-1px;background-color:#303030;border:1px solid #444}.list-group-item:first-child{border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#999;pointer-events:none;background-color:#303030}.list-group-item.active{z-index:2;color:#fff;background-color:#375a7f;border-color:#375a7f}.list-group-horizontal{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}@media (min-width: 576px){.list-group-horizontal-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}}@media (min-width: 768px){.list-group-horizontal-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}}@media (min-width: 992px){.list-group-horizontal-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}}@media (min-width: 1200px){.list-group-horizontal-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#1d2f42;background-color:#c7d1db}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#1d2f42;background-color:#b7c4d1}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#1d2f42;border-color:#1d2f42}.list-group-item-secondary{color:#232323;background-color:#cbcbcb}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#232323;background-color:#bebebe}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#232323;border-color:#232323}.list-group-item-success{color:#006249;background-color:#b8ecdf}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#006249;background-color:#a4e7d6}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#006249;border-color:#006249}.list-group-item-info{color:#1b4f72;background-color:#c6e2f5}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#1b4f72;background-color:#b0d7f1}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#1b4f72;border-color:#1b4f72}.list-group-item-warning{color:#7e5109;background-color:#fce3bd}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#7e5109;background-color:#fbd9a5}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#7e5109;border-color:#7e5109}.list-group-item-danger{color:#78281f;background-color:#f8cdc8}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#78281f;background-color:#f5b8b1}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#78281f;border-color:#78281f}.list-group-item-light{color:#505050;background-color:#e2e2e2}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#505050;background-color:#d5d5d5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#505050;border-color:#505050}.list-group-item-dark{color:#191919;background-color:#c5c5c5}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#191919;background-color:#b8b8b8}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#191919;border-color:#191919}.close{float:right;font-size:1.40625rem;font-weight:700;line-height:1;color:#fff;text-shadow:none;opacity:.5}.close:hover{color:#fff;text-decoration:none}.close:not(:disabled):not(.disabled):hover,.close:not(:disabled):not(.disabled):focus{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:0.875rem;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border:1px solid rgba(0,0,0,0.1);-webkit-box-shadow:0 0.25rem 0.75rem rgba(0,0,0,0.1);box-shadow:0 0.25rem 0.75rem rgba(0,0,0,0.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:0.25rem}.toast:not(:last-child){margin-bottom:0.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0.25rem 0.75rem;color:#999;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,0.05)}.toast-body{padding:0.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:0.5rem;pointer-events:none}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform 0.3s ease-out;transition:-webkit-transform 0.3s ease-out;transition:transform 0.3s ease-out;transition:transform 0.3s ease-out, -webkit-transform 0.3s ease-out;-webkit-transform:translate(0, -50px);transform:translate(0, -50px)}@media (prefers-reduced-motion: reduce){.modal.fade .modal-dialog{-webkit-transition:none;transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal-dialog-scrollable{display:-webkit-box;display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-header,.modal-dialog-scrollable .modal-footer{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#303030;background-clip:padding-box;border:1px solid #444;border-radius:0.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:0.5}.modal-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #444;border-top-left-radius:0.3rem;border-top-right-radius:0.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #444;border-bottom-right-radius:0.3rem;border-bottom-left-radius:0.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width: 1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.8203125rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:0.9}.tooltip .arrow{position:absolute;display:block;width:0.8rem;height:0.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[x-placement^="top"]{padding:0.4rem 0}.bs-tooltip-top .arrow,.bs-tooltip-auto[x-placement^="top"] .arrow{bottom:0}.bs-tooltip-top .arrow::before,.bs-tooltip-auto[x-placement^="top"] .arrow::before{top:0;border-width:0.4rem 0.4rem 0;border-top-color:#000}.bs-tooltip-right,.bs-tooltip-auto[x-placement^="right"]{padding:0 0.4rem}.bs-tooltip-right .arrow,.bs-tooltip-auto[x-placement^="right"] .arrow{left:0;width:0.4rem;height:0.8rem}.bs-tooltip-right .arrow::before,.bs-tooltip-auto[x-placement^="right"] .arrow::before{right:0;border-width:0.4rem 0.4rem 0.4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[x-placement^="bottom"]{padding:0.4rem 0}.bs-tooltip-bottom .arrow,.bs-tooltip-auto[x-placement^="bottom"] .arrow{top:0}.bs-tooltip-bottom .arrow::before,.bs-tooltip-auto[x-placement^="bottom"] .arrow::before{bottom:0;border-width:0 0.4rem 0.4rem;border-bottom-color:#000}.bs-tooltip-left,.bs-tooltip-auto[x-placement^="left"]{padding:0 0.4rem}.bs-tooltip-left .arrow,.bs-tooltip-auto[x-placement^="left"] .arrow{right:0;width:0.4rem;height:0.8rem}.bs-tooltip-left .arrow::before,.bs-tooltip-auto[x-placement^="left"] .arrow::before{left:0;border-width:0.4rem 0 0.4rem 0.4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:0.25rem 0.5rem;color:#fff;text-align:center;background-color:#000;border-radius:0.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.8203125rem;word-wrap:break-word;background-color:#303030;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:0.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:0.5rem;margin:0 0.3rem}.popover .arrow::before,.popover .arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-top,.bs-popover-auto[x-placement^="top"]{margin-bottom:0.5rem}.bs-popover-top>.arrow,.bs-popover-auto[x-placement^="top"]>.arrow{bottom:calc((0.5rem + 1px) * -1)}.bs-popover-top>.arrow::before,.bs-popover-auto[x-placement^="top"]>.arrow::before{bottom:0;border-width:0.5rem 0.5rem 0;border-top-color:rgba(0,0,0,0.25)}.bs-popover-top>.arrow::after,.bs-popover-auto[x-placement^="top"]>.arrow::after{bottom:1px;border-width:0.5rem 0.5rem 0;border-top-color:#303030}.bs-popover-right,.bs-popover-auto[x-placement^="right"]{margin-left:0.5rem}.bs-popover-right>.arrow,.bs-popover-auto[x-placement^="right"]>.arrow{left:calc((0.5rem + 1px) * -1);width:0.5rem;height:1rem;margin:0.3rem 0}.bs-popover-right>.arrow::before,.bs-popover-auto[x-placement^="right"]>.arrow::before{left:0;border-width:0.5rem 0.5rem 0.5rem 0;border-right-color:rgba(0,0,0,0.25)}.bs-popover-right>.arrow::after,.bs-popover-auto[x-placement^="right"]>.arrow::after{left:1px;border-width:0.5rem 0.5rem 0.5rem 0;border-right-color:#303030}.bs-popover-bottom,.bs-popover-auto[x-placement^="bottom"]{margin-top:0.5rem}.bs-popover-bottom>.arrow,.bs-popover-auto[x-placement^="bottom"]>.arrow{top:calc((0.5rem + 1px) * -1)}.bs-popover-bottom>.arrow::before,.bs-popover-auto[x-placement^="bottom"]>.arrow::before{top:0;border-width:0 0.5rem 0.5rem 0.5rem;border-bottom-color:rgba(0,0,0,0.25)}.bs-popover-bottom>.arrow::after,.bs-popover-auto[x-placement^="bottom"]>.arrow::after{top:1px;border-width:0 0.5rem 0.5rem 0.5rem;border-bottom-color:#303030}.bs-popover-bottom .popover-header::before,.bs-popover-auto[x-placement^="bottom"] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #444}.bs-popover-left,.bs-popover-auto[x-placement^="left"]{margin-right:0.5rem}.bs-popover-left>.arrow,.bs-popover-auto[x-placement^="left"]>.arrow{right:calc((0.5rem + 1px) * -1);width:0.5rem;height:1rem;margin:0.3rem 0}.bs-popover-left>.arrow::before,.bs-popover-auto[x-placement^="left"]>.arrow::before{right:0;border-width:0.5rem 0 0.5rem 0.5rem;border-left-color:rgba(0,0,0,0.25)}.bs-popover-left>.arrow::after,.bs-popover-auto[x-placement^="left"]>.arrow::after{right:1px;border-width:0.5rem 0 0.5rem 0.5rem;border-left-color:#303030}.popover-header{padding:0.5rem 0.75rem;margin-bottom:0;font-size:0.9375rem;background-color:#444;border-bottom:1px solid #373737;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:0.5rem 0.75rem;color:#fff}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition:-webkit-transform 0.6s ease-in-out;transition:-webkit-transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out}@media (prefers-reduced-motion: reduce){.carousel-item{-webkit-transition:none;transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-left),.active.carousel-item-right{-webkit-transform:translateX(100%);transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-right),.active.carousel-item-left{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;-webkit-transition-property:opacity;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;-webkit-transition:0s 0.6s opacity;transition:0s 0.6s opacity}@media (prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{-webkit-transition:none;transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:0.5;-webkit-transition:opacity 0.15s ease;transition:opacity 0.15s ease}@media (prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{-webkit-transition:none;transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:0.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50% / 100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;-webkit-transition:opacity 0.6s ease;transition:opacity 0.6s ease}@media (prefers-reduced-motion: reduce){.carousel-indicators li{-webkit-transition:none;transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:0.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:0.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.bg-primary{background-color:#375a7f !important}a.bg-primary:hover,a.bg-primary:focus,button.bg-primary:hover,button.bg-primary:focus{background-color:#28415b !important}.bg-secondary{background-color:#444 !important}a.bg-secondary:hover,a.bg-secondary:focus,button.bg-secondary:hover,button.bg-secondary:focus{background-color:#2b2a2a !important}.bg-success{background-color:#00bc8c !important}a.bg-success:hover,a.bg-success:focus,button.bg-success:hover,button.bg-success:focus{background-color:#008966 !important}.bg-info{background-color:#3498DB !important}a.bg-info:hover,a.bg-info:focus,button.bg-info:hover,button.bg-info:focus{background-color:#217dbb !important}.bg-warning{background-color:#F39C12 !important}a.bg-warning:hover,a.bg-warning:focus,button.bg-warning:hover,button.bg-warning:focus{background-color:#c87f0a !important}.bg-danger{background-color:#E74C3C !important}a.bg-danger:hover,a.bg-danger:focus,button.bg-danger:hover,button.bg-danger:focus{background-color:#d62c1a !important}.bg-light{background-color:#999 !important}a.bg-light:hover,a.bg-light:focus,button.bg-light:hover,button.bg-light:focus{background-color:#807f7f !important}.bg-dark{background-color:#303030 !important}a.bg-dark:hover,a.bg-dark:focus,button.bg-dark:hover,button.bg-dark:focus{background-color:#171616 !important}.bg-white{background-color:#fff !important}.bg-transparent{background-color:transparent !important}.border{border:1px solid #dee2e6 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-right{border-right:1px solid #dee2e6 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-left{border-left:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.border-primary{border-color:#375a7f !important}.border-secondary{border-color:#444 !important}.border-success{border-color:#00bc8c !important}.border-info{border-color:#3498DB !important}.border-warning{border-color:#F39C12 !important}.border-danger{border-color:#E74C3C !important}.border-light{border-color:#999 !important}.border-dark{border-color:#303030 !important}.border-white{border-color:#fff !important}.rounded-sm{border-radius:0.2rem !important}.rounded{border-radius:0.25rem !important}.rounded-top{border-top-left-radius:0.25rem !important;border-top-right-radius:0.25rem !important}.rounded-right{border-top-right-radius:0.25rem !important;border-bottom-right-radius:0.25rem !important}.rounded-bottom{border-bottom-right-radius:0.25rem !important;border-bottom-left-radius:0.25rem !important}.rounded-left{border-top-left-radius:0.25rem !important;border-bottom-left-radius:0.25rem !important}.rounded-lg{border-radius:0.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-0{border-radius:0 !important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}@media (min-width: 576px){.d-sm-none{display:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-sm-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media (min-width: 768px){.d-md-none{display:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-md-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media (min-width: 992px){.d-lg-none{display:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-lg-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media (min-width: 1200px){.d-xl-none{display:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-xl-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media print{.d-print-none{display:none !important}.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-print-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.8571428571%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}@media (min-width: 576px){.flex-sm-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-sm-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-sm-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-sm-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-sm-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-sm-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-sm-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-sm-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-sm-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-sm-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-sm-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-sm-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-sm-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-sm-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-sm-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-sm-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-sm-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-sm-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-sm-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-sm-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-sm-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-sm-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-sm-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-sm-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-sm-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-sm-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-sm-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-sm-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-sm-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-sm-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-sm-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-sm-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-sm-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}@media (min-width: 768px){.flex-md-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-md-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-md-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-md-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-md-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-md-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-md-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-md-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-md-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-md-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-md-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-md-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-md-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-md-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-md-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-md-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-md-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-md-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-md-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-md-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-md-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-md-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-md-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-md-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-md-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-md-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-md-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-md-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-md-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-md-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-md-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-md-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-md-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}@media (min-width: 992px){.flex-lg-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-lg-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-lg-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-lg-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-lg-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-lg-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-lg-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-lg-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-lg-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-lg-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-lg-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-lg-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-lg-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-lg-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-lg-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-lg-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-lg-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-lg-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-lg-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-lg-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-lg-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-lg-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-lg-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-lg-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-lg-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-lg-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-lg-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-lg-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-lg-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-lg-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-lg-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-lg-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-lg-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}@media (min-width: 1200px){.flex-xl-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-xl-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-xl-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-xl-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-xl-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-xl-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-xl-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-xl-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-xl-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-xl-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-xl-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-xl-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-xl-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-xl-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-xl-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-xl-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-xl-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-xl-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-xl-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-xl-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-xl-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-xl-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-xl-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-xl-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-xl-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-xl-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-xl-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-xl-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-xl-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-xl-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-xl-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-xl-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-xl-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}.float-left{float:left !important}.float-right{float:right !important}.float-none{float:none !important}@media (min-width: 576px){.float-sm-left{float:left !important}.float-sm-right{float:right !important}.float-sm-none{float:none !important}}@media (min-width: 768px){.float-md-left{float:left !important}.float-md-right{float:right !important}.float-md-none{float:none !important}}@media (min-width: 992px){.float-lg-left{float:left !important}.float-lg-right{float:right !important}.float-lg-none{float:none !important}}@media (min-width: 1200px){.float-xl-left{float:left !important}.float-xl-right{float:right !important}.float-xl-none{float:none !important}}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:-webkit-sticky !important;position:sticky !important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports (position: -webkit-sticky) or (position: sticky){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{-webkit-box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important;box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important}.shadow{-webkit-box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important;box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.shadow-lg{-webkit-box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important;box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important}.shadow-none{-webkit-box-shadow:none !important;box-shadow:none !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.min-vw-100{min-width:100vw !important}.min-vh-100{min-height:100vh !important}.vw-100{width:100vw !important}.vh-100{height:100vh !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0 !important}.mt-0,.my-0{margin-top:0 !important}.mr-0,.mx-0{margin-right:0 !important}.mb-0,.my-0{margin-bottom:0 !important}.ml-0,.mx-0{margin-left:0 !important}.m-1{margin:0.25rem !important}.mt-1,.my-1{margin-top:0.25rem !important}.mr-1,.mx-1{margin-right:0.25rem !important}.mb-1,.my-1{margin-bottom:0.25rem !important}.ml-1,.mx-1{margin-left:0.25rem !important}.m-2{margin:0.5rem !important}.mt-2,.my-2{margin-top:0.5rem !important}.mr-2,.mx-2{margin-right:0.5rem !important}.mb-2,.my-2{margin-bottom:0.5rem !important}.ml-2,.mx-2{margin-left:0.5rem !important}.m-3{margin:1rem !important}.mt-3,.my-3{margin-top:1rem !important}.mr-3,.mx-3{margin-right:1rem !important}.mb-3,.my-3{margin-bottom:1rem !important}.ml-3,.mx-3{margin-left:1rem !important}.m-4{margin:1.5rem !important}.mt-4,.my-4{margin-top:1.5rem !important}.mr-4,.mx-4{margin-right:1.5rem !important}.mb-4,.my-4{margin-bottom:1.5rem !important}.ml-4,.mx-4{margin-left:1.5rem !important}.m-5{margin:3rem !important}.mt-5,.my-5{margin-top:3rem !important}.mr-5,.mx-5{margin-right:3rem !important}.mb-5,.my-5{margin-bottom:3rem !important}.ml-5,.mx-5{margin-left:3rem !important}.p-0{padding:0 !important}.pt-0,.py-0{padding-top:0 !important}.pr-0,.px-0{padding-right:0 !important}.pb-0,.py-0{padding-bottom:0 !important}.pl-0,.px-0{padding-left:0 !important}.p-1{padding:0.25rem !important}.pt-1,.py-1{padding-top:0.25rem !important}.pr-1,.px-1{padding-right:0.25rem !important}.pb-1,.py-1{padding-bottom:0.25rem !important}.pl-1,.px-1{padding-left:0.25rem !important}.p-2{padding:0.5rem !important}.pt-2,.py-2{padding-top:0.5rem !important}.pr-2,.px-2{padding-right:0.5rem !important}.pb-2,.py-2{padding-bottom:0.5rem !important}.pl-2,.px-2{padding-left:0.5rem !important}.p-3{padding:1rem !important}.pt-3,.py-3{padding-top:1rem !important}.pr-3,.px-3{padding-right:1rem !important}.pb-3,.py-3{padding-bottom:1rem !important}.pl-3,.px-3{padding-left:1rem !important}.p-4{padding:1.5rem !important}.pt-4,.py-4{padding-top:1.5rem !important}.pr-4,.px-4{padding-right:1.5rem !important}.pb-4,.py-4{padding-bottom:1.5rem !important}.pl-4,.px-4{padding-left:1.5rem !important}.p-5{padding:3rem !important}.pt-5,.py-5{padding-top:3rem !important}.pr-5,.px-5{padding-right:3rem !important}.pb-5,.py-5{padding-bottom:3rem !important}.pl-5,.px-5{padding-left:3rem !important}.m-n1{margin:-0.25rem !important}.mt-n1,.my-n1{margin-top:-0.25rem !important}.mr-n1,.mx-n1{margin-right:-0.25rem !important}.mb-n1,.my-n1{margin-bottom:-0.25rem !important}.ml-n1,.mx-n1{margin-left:-0.25rem !important}.m-n2{margin:-0.5rem !important}.mt-n2,.my-n2{margin-top:-0.5rem !important}.mr-n2,.mx-n2{margin-right:-0.5rem !important}.mb-n2,.my-n2{margin-bottom:-0.5rem !important}.ml-n2,.mx-n2{margin-left:-0.5rem !important}.m-n3{margin:-1rem !important}.mt-n3,.my-n3{margin-top:-1rem !important}.mr-n3,.mx-n3{margin-right:-1rem !important}.mb-n3,.my-n3{margin-bottom:-1rem !important}.ml-n3,.mx-n3{margin-left:-1rem !important}.m-n4{margin:-1.5rem !important}.mt-n4,.my-n4{margin-top:-1.5rem !important}.mr-n4,.mx-n4{margin-right:-1.5rem !important}.mb-n4,.my-n4{margin-bottom:-1.5rem !important}.ml-n4,.mx-n4{margin-left:-1.5rem !important}.m-n5{margin:-3rem !important}.mt-n5,.my-n5{margin-top:-3rem !important}.mr-n5,.mx-n5{margin-right:-3rem !important}.mb-n5,.my-n5{margin-bottom:-3rem !important}.ml-n5,.mx-n5{margin-left:-3rem !important}.m-auto{margin:auto !important}.mt-auto,.my-auto{margin-top:auto !important}.mr-auto,.mx-auto{margin-right:auto !important}.mb-auto,.my-auto{margin-bottom:auto !important}.ml-auto,.mx-auto{margin-left:auto !important}@media (min-width: 576px){.m-sm-0{margin:0 !important}.mt-sm-0,.my-sm-0{margin-top:0 !important}.mr-sm-0,.mx-sm-0{margin-right:0 !important}.mb-sm-0,.my-sm-0{margin-bottom:0 !important}.ml-sm-0,.mx-sm-0{margin-left:0 !important}.m-sm-1{margin:0.25rem !important}.mt-sm-1,.my-sm-1{margin-top:0.25rem !important}.mr-sm-1,.mx-sm-1{margin-right:0.25rem !important}.mb-sm-1,.my-sm-1{margin-bottom:0.25rem !important}.ml-sm-1,.mx-sm-1{margin-left:0.25rem !important}.m-sm-2{margin:0.5rem !important}.mt-sm-2,.my-sm-2{margin-top:0.5rem !important}.mr-sm-2,.mx-sm-2{margin-right:0.5rem !important}.mb-sm-2,.my-sm-2{margin-bottom:0.5rem !important}.ml-sm-2,.mx-sm-2{margin-left:0.5rem !important}.m-sm-3{margin:1rem !important}.mt-sm-3,.my-sm-3{margin-top:1rem !important}.mr-sm-3,.mx-sm-3{margin-right:1rem !important}.mb-sm-3,.my-sm-3{margin-bottom:1rem !important}.ml-sm-3,.mx-sm-3{margin-left:1rem !important}.m-sm-4{margin:1.5rem !important}.mt-sm-4,.my-sm-4{margin-top:1.5rem !important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem !important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem !important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem !important}.m-sm-5{margin:3rem !important}.mt-sm-5,.my-sm-5{margin-top:3rem !important}.mr-sm-5,.mx-sm-5{margin-right:3rem !important}.mb-sm-5,.my-sm-5{margin-bottom:3rem !important}.ml-sm-5,.mx-sm-5{margin-left:3rem !important}.p-sm-0{padding:0 !important}.pt-sm-0,.py-sm-0{padding-top:0 !important}.pr-sm-0,.px-sm-0{padding-right:0 !important}.pb-sm-0,.py-sm-0{padding-bottom:0 !important}.pl-sm-0,.px-sm-0{padding-left:0 !important}.p-sm-1{padding:0.25rem !important}.pt-sm-1,.py-sm-1{padding-top:0.25rem !important}.pr-sm-1,.px-sm-1{padding-right:0.25rem !important}.pb-sm-1,.py-sm-1{padding-bottom:0.25rem !important}.pl-sm-1,.px-sm-1{padding-left:0.25rem !important}.p-sm-2{padding:0.5rem !important}.pt-sm-2,.py-sm-2{padding-top:0.5rem !important}.pr-sm-2,.px-sm-2{padding-right:0.5rem !important}.pb-sm-2,.py-sm-2{padding-bottom:0.5rem !important}.pl-sm-2,.px-sm-2{padding-left:0.5rem !important}.p-sm-3{padding:1rem !important}.pt-sm-3,.py-sm-3{padding-top:1rem !important}.pr-sm-3,.px-sm-3{padding-right:1rem !important}.pb-sm-3,.py-sm-3{padding-bottom:1rem !important}.pl-sm-3,.px-sm-3{padding-left:1rem !important}.p-sm-4{padding:1.5rem !important}.pt-sm-4,.py-sm-4{padding-top:1.5rem !important}.pr-sm-4,.px-sm-4{padding-right:1.5rem !important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem !important}.pl-sm-4,.px-sm-4{padding-left:1.5rem !important}.p-sm-5{padding:3rem !important}.pt-sm-5,.py-sm-5{padding-top:3rem !important}.pr-sm-5,.px-sm-5{padding-right:3rem !important}.pb-sm-5,.py-sm-5{padding-bottom:3rem !important}.pl-sm-5,.px-sm-5{padding-left:3rem !important}.m-sm-n1{margin:-0.25rem !important}.mt-sm-n1,.my-sm-n1{margin-top:-0.25rem !important}.mr-sm-n1,.mx-sm-n1{margin-right:-0.25rem !important}.mb-sm-n1,.my-sm-n1{margin-bottom:-0.25rem !important}.ml-sm-n1,.mx-sm-n1{margin-left:-0.25rem !important}.m-sm-n2{margin:-0.5rem !important}.mt-sm-n2,.my-sm-n2{margin-top:-0.5rem !important}.mr-sm-n2,.mx-sm-n2{margin-right:-0.5rem !important}.mb-sm-n2,.my-sm-n2{margin-bottom:-0.5rem !important}.ml-sm-n2,.mx-sm-n2{margin-left:-0.5rem !important}.m-sm-n3{margin:-1rem !important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem !important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem !important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem !important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem !important}.m-sm-n4{margin:-1.5rem !important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem !important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem !important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem !important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem !important}.m-sm-n5{margin:-3rem !important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem !important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem !important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem !important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem !important}.m-sm-auto{margin:auto !important}.mt-sm-auto,.my-sm-auto{margin-top:auto !important}.mr-sm-auto,.mx-sm-auto{margin-right:auto !important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto !important}.ml-sm-auto,.mx-sm-auto{margin-left:auto !important}}@media (min-width: 768px){.m-md-0{margin:0 !important}.mt-md-0,.my-md-0{margin-top:0 !important}.mr-md-0,.mx-md-0{margin-right:0 !important}.mb-md-0,.my-md-0{margin-bottom:0 !important}.ml-md-0,.mx-md-0{margin-left:0 !important}.m-md-1{margin:0.25rem !important}.mt-md-1,.my-md-1{margin-top:0.25rem !important}.mr-md-1,.mx-md-1{margin-right:0.25rem !important}.mb-md-1,.my-md-1{margin-bottom:0.25rem !important}.ml-md-1,.mx-md-1{margin-left:0.25rem !important}.m-md-2{margin:0.5rem !important}.mt-md-2,.my-md-2{margin-top:0.5rem !important}.mr-md-2,.mx-md-2{margin-right:0.5rem !important}.mb-md-2,.my-md-2{margin-bottom:0.5rem !important}.ml-md-2,.mx-md-2{margin-left:0.5rem !important}.m-md-3{margin:1rem !important}.mt-md-3,.my-md-3{margin-top:1rem !important}.mr-md-3,.mx-md-3{margin-right:1rem !important}.mb-md-3,.my-md-3{margin-bottom:1rem !important}.ml-md-3,.mx-md-3{margin-left:1rem !important}.m-md-4{margin:1.5rem !important}.mt-md-4,.my-md-4{margin-top:1.5rem !important}.mr-md-4,.mx-md-4{margin-right:1.5rem !important}.mb-md-4,.my-md-4{margin-bottom:1.5rem !important}.ml-md-4,.mx-md-4{margin-left:1.5rem !important}.m-md-5{margin:3rem !important}.mt-md-5,.my-md-5{margin-top:3rem !important}.mr-md-5,.mx-md-5{margin-right:3rem !important}.mb-md-5,.my-md-5{margin-bottom:3rem !important}.ml-md-5,.mx-md-5{margin-left:3rem !important}.p-md-0{padding:0 !important}.pt-md-0,.py-md-0{padding-top:0 !important}.pr-md-0,.px-md-0{padding-right:0 !important}.pb-md-0,.py-md-0{padding-bottom:0 !important}.pl-md-0,.px-md-0{padding-left:0 !important}.p-md-1{padding:0.25rem !important}.pt-md-1,.py-md-1{padding-top:0.25rem !important}.pr-md-1,.px-md-1{padding-right:0.25rem !important}.pb-md-1,.py-md-1{padding-bottom:0.25rem !important}.pl-md-1,.px-md-1{padding-left:0.25rem !important}.p-md-2{padding:0.5rem !important}.pt-md-2,.py-md-2{padding-top:0.5rem !important}.pr-md-2,.px-md-2{padding-right:0.5rem !important}.pb-md-2,.py-md-2{padding-bottom:0.5rem !important}.pl-md-2,.px-md-2{padding-left:0.5rem !important}.p-md-3{padding:1rem !important}.pt-md-3,.py-md-3{padding-top:1rem !important}.pr-md-3,.px-md-3{padding-right:1rem !important}.pb-md-3,.py-md-3{padding-bottom:1rem !important}.pl-md-3,.px-md-3{padding-left:1rem !important}.p-md-4{padding:1.5rem !important}.pt-md-4,.py-md-4{padding-top:1.5rem !important}.pr-md-4,.px-md-4{padding-right:1.5rem !important}.pb-md-4,.py-md-4{padding-bottom:1.5rem !important}.pl-md-4,.px-md-4{padding-left:1.5rem !important}.p-md-5{padding:3rem !important}.pt-md-5,.py-md-5{padding-top:3rem !important}.pr-md-5,.px-md-5{padding-right:3rem !important}.pb-md-5,.py-md-5{padding-bottom:3rem !important}.pl-md-5,.px-md-5{padding-left:3rem !important}.m-md-n1{margin:-0.25rem !important}.mt-md-n1,.my-md-n1{margin-top:-0.25rem !important}.mr-md-n1,.mx-md-n1{margin-right:-0.25rem !important}.mb-md-n1,.my-md-n1{margin-bottom:-0.25rem !important}.ml-md-n1,.mx-md-n1{margin-left:-0.25rem !important}.m-md-n2{margin:-0.5rem !important}.mt-md-n2,.my-md-n2{margin-top:-0.5rem !important}.mr-md-n2,.mx-md-n2{margin-right:-0.5rem !important}.mb-md-n2,.my-md-n2{margin-bottom:-0.5rem !important}.ml-md-n2,.mx-md-n2{margin-left:-0.5rem !important}.m-md-n3{margin:-1rem !important}.mt-md-n3,.my-md-n3{margin-top:-1rem !important}.mr-md-n3,.mx-md-n3{margin-right:-1rem !important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem !important}.ml-md-n3,.mx-md-n3{margin-left:-1rem !important}.m-md-n4{margin:-1.5rem !important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem !important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem !important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem !important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem !important}.m-md-n5{margin:-3rem !important}.mt-md-n5,.my-md-n5{margin-top:-3rem !important}.mr-md-n5,.mx-md-n5{margin-right:-3rem !important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem !important}.ml-md-n5,.mx-md-n5{margin-left:-3rem !important}.m-md-auto{margin:auto !important}.mt-md-auto,.my-md-auto{margin-top:auto !important}.mr-md-auto,.mx-md-auto{margin-right:auto !important}.mb-md-auto,.my-md-auto{margin-bottom:auto !important}.ml-md-auto,.mx-md-auto{margin-left:auto !important}}@media (min-width: 992px){.m-lg-0{margin:0 !important}.mt-lg-0,.my-lg-0{margin-top:0 !important}.mr-lg-0,.mx-lg-0{margin-right:0 !important}.mb-lg-0,.my-lg-0{margin-bottom:0 !important}.ml-lg-0,.mx-lg-0{margin-left:0 !important}.m-lg-1{margin:0.25rem !important}.mt-lg-1,.my-lg-1{margin-top:0.25rem !important}.mr-lg-1,.mx-lg-1{margin-right:0.25rem !important}.mb-lg-1,.my-lg-1{margin-bottom:0.25rem !important}.ml-lg-1,.mx-lg-1{margin-left:0.25rem !important}.m-lg-2{margin:0.5rem !important}.mt-lg-2,.my-lg-2{margin-top:0.5rem !important}.mr-lg-2,.mx-lg-2{margin-right:0.5rem !important}.mb-lg-2,.my-lg-2{margin-bottom:0.5rem !important}.ml-lg-2,.mx-lg-2{margin-left:0.5rem !important}.m-lg-3{margin:1rem !important}.mt-lg-3,.my-lg-3{margin-top:1rem !important}.mr-lg-3,.mx-lg-3{margin-right:1rem !important}.mb-lg-3,.my-lg-3{margin-bottom:1rem !important}.ml-lg-3,.mx-lg-3{margin-left:1rem !important}.m-lg-4{margin:1.5rem !important}.mt-lg-4,.my-lg-4{margin-top:1.5rem !important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem !important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem !important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem !important}.m-lg-5{margin:3rem !important}.mt-lg-5,.my-lg-5{margin-top:3rem !important}.mr-lg-5,.mx-lg-5{margin-right:3rem !important}.mb-lg-5,.my-lg-5{margin-bottom:3rem !important}.ml-lg-5,.mx-lg-5{margin-left:3rem !important}.p-lg-0{padding:0 !important}.pt-lg-0,.py-lg-0{padding-top:0 !important}.pr-lg-0,.px-lg-0{padding-right:0 !important}.pb-lg-0,.py-lg-0{padding-bottom:0 !important}.pl-lg-0,.px-lg-0{padding-left:0 !important}.p-lg-1{padding:0.25rem !important}.pt-lg-1,.py-lg-1{padding-top:0.25rem !important}.pr-lg-1,.px-lg-1{padding-right:0.25rem !important}.pb-lg-1,.py-lg-1{padding-bottom:0.25rem !important}.pl-lg-1,.px-lg-1{padding-left:0.25rem !important}.p-lg-2{padding:0.5rem !important}.pt-lg-2,.py-lg-2{padding-top:0.5rem !important}.pr-lg-2,.px-lg-2{padding-right:0.5rem !important}.pb-lg-2,.py-lg-2{padding-bottom:0.5rem !important}.pl-lg-2,.px-lg-2{padding-left:0.5rem !important}.p-lg-3{padding:1rem !important}.pt-lg-3,.py-lg-3{padding-top:1rem !important}.pr-lg-3,.px-lg-3{padding-right:1rem !important}.pb-lg-3,.py-lg-3{padding-bottom:1rem !important}.pl-lg-3,.px-lg-3{padding-left:1rem !important}.p-lg-4{padding:1.5rem !important}.pt-lg-4,.py-lg-4{padding-top:1.5rem !important}.pr-lg-4,.px-lg-4{padding-right:1.5rem !important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem !important}.pl-lg-4,.px-lg-4{padding-left:1.5rem !important}.p-lg-5{padding:3rem !important}.pt-lg-5,.py-lg-5{padding-top:3rem !important}.pr-lg-5,.px-lg-5{padding-right:3rem !important}.pb-lg-5,.py-lg-5{padding-bottom:3rem !important}.pl-lg-5,.px-lg-5{padding-left:3rem !important}.m-lg-n1{margin:-0.25rem !important}.mt-lg-n1,.my-lg-n1{margin-top:-0.25rem !important}.mr-lg-n1,.mx-lg-n1{margin-right:-0.25rem !important}.mb-lg-n1,.my-lg-n1{margin-bottom:-0.25rem !important}.ml-lg-n1,.mx-lg-n1{margin-left:-0.25rem !important}.m-lg-n2{margin:-0.5rem !important}.mt-lg-n2,.my-lg-n2{margin-top:-0.5rem !important}.mr-lg-n2,.mx-lg-n2{margin-right:-0.5rem !important}.mb-lg-n2,.my-lg-n2{margin-bottom:-0.5rem !important}.ml-lg-n2,.mx-lg-n2{margin-left:-0.5rem !important}.m-lg-n3{margin:-1rem !important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem !important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem !important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem !important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem !important}.m-lg-n4{margin:-1.5rem !important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem !important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem !important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem !important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem !important}.m-lg-n5{margin:-3rem !important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem !important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem !important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem !important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem !important}.m-lg-auto{margin:auto !important}.mt-lg-auto,.my-lg-auto{margin-top:auto !important}.mr-lg-auto,.mx-lg-auto{margin-right:auto !important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto !important}.ml-lg-auto,.mx-lg-auto{margin-left:auto !important}}@media (min-width: 1200px){.m-xl-0{margin:0 !important}.mt-xl-0,.my-xl-0{margin-top:0 !important}.mr-xl-0,.mx-xl-0{margin-right:0 !important}.mb-xl-0,.my-xl-0{margin-bottom:0 !important}.ml-xl-0,.mx-xl-0{margin-left:0 !important}.m-xl-1{margin:0.25rem !important}.mt-xl-1,.my-xl-1{margin-top:0.25rem !important}.mr-xl-1,.mx-xl-1{margin-right:0.25rem !important}.mb-xl-1,.my-xl-1{margin-bottom:0.25rem !important}.ml-xl-1,.mx-xl-1{margin-left:0.25rem !important}.m-xl-2{margin:0.5rem !important}.mt-xl-2,.my-xl-2{margin-top:0.5rem !important}.mr-xl-2,.mx-xl-2{margin-right:0.5rem !important}.mb-xl-2,.my-xl-2{margin-bottom:0.5rem !important}.ml-xl-2,.mx-xl-2{margin-left:0.5rem !important}.m-xl-3{margin:1rem !important}.mt-xl-3,.my-xl-3{margin-top:1rem !important}.mr-xl-3,.mx-xl-3{margin-right:1rem !important}.mb-xl-3,.my-xl-3{margin-bottom:1rem !important}.ml-xl-3,.mx-xl-3{margin-left:1rem !important}.m-xl-4{margin:1.5rem !important}.mt-xl-4,.my-xl-4{margin-top:1.5rem !important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem !important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem !important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem !important}.m-xl-5{margin:3rem !important}.mt-xl-5,.my-xl-5{margin-top:3rem !important}.mr-xl-5,.mx-xl-5{margin-right:3rem !important}.mb-xl-5,.my-xl-5{margin-bottom:3rem !important}.ml-xl-5,.mx-xl-5{margin-left:3rem !important}.p-xl-0{padding:0 !important}.pt-xl-0,.py-xl-0{padding-top:0 !important}.pr-xl-0,.px-xl-0{padding-right:0 !important}.pb-xl-0,.py-xl-0{padding-bottom:0 !important}.pl-xl-0,.px-xl-0{padding-left:0 !important}.p-xl-1{padding:0.25rem !important}.pt-xl-1,.py-xl-1{padding-top:0.25rem !important}.pr-xl-1,.px-xl-1{padding-right:0.25rem !important}.pb-xl-1,.py-xl-1{padding-bottom:0.25rem !important}.pl-xl-1,.px-xl-1{padding-left:0.25rem !important}.p-xl-2{padding:0.5rem !important}.pt-xl-2,.py-xl-2{padding-top:0.5rem !important}.pr-xl-2,.px-xl-2{padding-right:0.5rem !important}.pb-xl-2,.py-xl-2{padding-bottom:0.5rem !important}.pl-xl-2,.px-xl-2{padding-left:0.5rem !important}.p-xl-3{padding:1rem !important}.pt-xl-3,.py-xl-3{padding-top:1rem !important}.pr-xl-3,.px-xl-3{padding-right:1rem !important}.pb-xl-3,.py-xl-3{padding-bottom:1rem !important}.pl-xl-3,.px-xl-3{padding-left:1rem !important}.p-xl-4{padding:1.5rem !important}.pt-xl-4,.py-xl-4{padding-top:1.5rem !important}.pr-xl-4,.px-xl-4{padding-right:1.5rem !important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem !important}.pl-xl-4,.px-xl-4{padding-left:1.5rem !important}.p-xl-5{padding:3rem !important}.pt-xl-5,.py-xl-5{padding-top:3rem !important}.pr-xl-5,.px-xl-5{padding-right:3rem !important}.pb-xl-5,.py-xl-5{padding-bottom:3rem !important}.pl-xl-5,.px-xl-5{padding-left:3rem !important}.m-xl-n1{margin:-0.25rem !important}.mt-xl-n1,.my-xl-n1{margin-top:-0.25rem !important}.mr-xl-n1,.mx-xl-n1{margin-right:-0.25rem !important}.mb-xl-n1,.my-xl-n1{margin-bottom:-0.25rem !important}.ml-xl-n1,.mx-xl-n1{margin-left:-0.25rem !important}.m-xl-n2{margin:-0.5rem !important}.mt-xl-n2,.my-xl-n2{margin-top:-0.5rem !important}.mr-xl-n2,.mx-xl-n2{margin-right:-0.5rem !important}.mb-xl-n2,.my-xl-n2{margin-bottom:-0.5rem !important}.ml-xl-n2,.mx-xl-n2{margin-left:-0.5rem !important}.m-xl-n3{margin:-1rem !important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem !important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem !important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem !important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem !important}.m-xl-n4{margin:-1.5rem !important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem !important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem !important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem !important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem !important}.m-xl-n5{margin:-3rem !important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem !important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem !important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem !important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem !important}.m-xl-auto{margin:auto !important}.mt-xl-auto,.my-xl-auto{margin-top:auto !important}.mr-xl-auto,.mx-xl-auto{margin-right:auto !important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto !important}.ml-xl-auto,.mx-xl-auto{margin-left:auto !important}}.text-monospace{font-family:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important}.text-justify{text-align:justify !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}@media (min-width: 576px){.text-sm-left{text-align:left !important}.text-sm-right{text-align:right !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.text-md-left{text-align:left !important}.text-md-right{text-align:right !important}.text-md-center{text-align:center !important}}@media (min-width: 992px){.text-lg-left{text-align:left !important}.text-lg-right{text-align:right !important}.text-lg-center{text-align:center !important}}@media (min-width: 1200px){.text-xl-left{text-align:left !important}.text-xl-right{text-align:right !important}.text-xl-center{text-align:center !important}}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.font-weight-light{font-weight:300 !important}.font-weight-lighter{font-weight:lighter !important}.font-weight-normal{font-weight:400 !important}.font-weight-bold{font-weight:700 !important}.font-weight-bolder{font-weight:bolder !important}.font-italic{font-style:italic !important}.text-white{color:#fff !important}.text-primary{color:#375a7f !important}a.text-primary:hover,a.text-primary:focus{color:#20344a !important}.text-secondary{color:#444 !important}a.text-secondary:hover,a.text-secondary:focus{color:#1e1e1e !important}.text-success{color:#00bc8c !important}a.text-success:hover,a.text-success:focus{color:#007053 !important}.text-info{color:#3498DB !important}a.text-info:hover,a.text-info:focus{color:#1d6fa5 !important}.text-warning{color:#F39C12 !important}a.text-warning:hover,a.text-warning:focus{color:#b06f09 !important}.text-danger{color:#E74C3C !important}a.text-danger:hover,a.text-danger:focus{color:#bf2718 !important}.text-light{color:#999 !important}a.text-light:hover,a.text-light:focus{color:#737373 !important}.text-dark{color:#303030 !important}a.text-dark:hover,a.text-dark:focus{color:#0a0a0a !important}.text-body{color:#fff !important}.text-muted{color:#999 !important}.text-black-50{color:rgba(0,0,0,0.5) !important}.text-white-50{color:rgba(255,255,255,0.5) !important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none !important}.text-break{word-break:break-word !important;overflow-wrap:break-word !important}.text-reset{color:inherit !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media print{*,*::before,*::after{text-shadow:none !important;-webkit-box-shadow:none !important;box-shadow:none !important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap !important}pre,blockquote{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px !important}.container{min-width:992px !important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #dee2e6 !important}.table-dark{color:inherit}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#444}.table .thead-dark th{color:inherit;border-color:#444}}.bg-primary .navbar-nav .active>.nav-link{color:#00bc8c !important}.bg-light.navbar{background-color:#00bc8c !important}.bg-light.navbar-light .navbar-nav .nav-link:focus,.bg-light.navbar-light .navbar-nav .nav-link:hover,.bg-light.navbar-light .navbar-nav .active>.nav-link{color:#375a7f !important}.blockquote-footer{color:#999}.table-primary,.table-primary>th,.table-primary>td{background-color:#375a7f}.table-secondary,.table-secondary>th,.table-secondary>td{background-color:#444}.table-light,.table-light>th,.table-light>td{background-color:#999}.table-dark,.table-dark>th,.table-dark>td{background-color:#303030}.table-success,.table-success>th,.table-success>td{background-color:#00bc8c}.table-info,.table-info>th,.table-info>td{background-color:#3498DB}.table-danger,.table-danger>th,.table-danger>td{background-color:#E74C3C}.table-warning,.table-warning>th,.table-warning>td{background-color:#F39C12}.table-active,.table-active>th,.table-active>td{background-color:rgba(0,0,0,0.075)}.table-hover .table-primary:hover,.table-hover .table-primary:hover>th,.table-hover .table-primary:hover>td{background-color:#2f4d6d}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>th,.table-hover .table-secondary:hover>td{background-color:#373737}.table-hover .table-light:hover,.table-hover .table-light:hover>th,.table-hover .table-light:hover>td{background-color:#8c8c8c}.table-hover .table-dark:hover,.table-hover .table-dark:hover>th,.table-hover .table-dark:hover>td{background-color:#232323}.table-hover .table-success:hover,.table-hover .table-success:hover>th,.table-hover .table-success:hover>td{background-color:#00a379}.table-hover .table-info:hover,.table-hover .table-info:hover>th,.table-hover .table-info:hover>td{background-color:#258cd1}.table-hover .table-danger:hover,.table-hover .table-danger:hover>th,.table-hover .table-danger:hover>td{background-color:#e43725}.table-hover .table-warning:hover,.table-hover .table-warning:hover>th,.table-hover .table-warning:hover>td{background-color:#e08e0b}.table-hover .table-active:hover,.table-hover .table-active:hover>th,.table-hover .table-active:hover>td{background-color:rgba(0,0,0,0.075)}.input-group-addon{color:#fff}.nav-tabs .nav-link,.nav-tabs .nav-link.active,.nav-tabs .nav-link.active:focus,.nav-tabs .nav-link.active:hover,.nav-tabs .nav-item.open .nav-link,.nav-tabs .nav-item.open .nav-link:focus,.nav-tabs .nav-item.open .nav-link:hover,.nav-pills .nav-link,.nav-pills .nav-link.active,.nav-pills .nav-link.active:focus,.nav-pills .nav-link.active:hover,.nav-pills .nav-item.open .nav-link,.nav-pills .nav-item.open .nav-link:focus,.nav-pills .nav-item.open .nav-link:hover{color:#fff}.breadcrumb a{color:#fff}.pagination a:hover{text-decoration:none}.close{opacity:0.4}.close:hover,.close:focus{opacity:1}.alert{border:none;color:#fff}.alert a,.alert .alert-link{color:#fff;text-decoration:underline}.alert-primary{background-color:#375a7f}.alert-secondary{background-color:#444}.alert-success{background-color:#00bc8c}.alert-info{background-color:#3498DB}.alert-warning{background-color:#F39C12}.alert-danger{background-color:#E74C3C}.alert-light{background-color:#999}.alert-dark{background-color:#303030}.list-group-item-action{color:#fff}.list-group-item-action:hover,.list-group-item-action:focus{background-color:#444;color:#fff}.list-group-item-action .list-group-item-heading{color:#fff} diff --git a/histview2/static/common/css/bootstrap.min.css.map b/histview2/static/common/css/bootstrap.min.css.map new file mode 100644 index 0000000..6c7fa40 --- /dev/null +++ b/histview2/static/common/css/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["less/normalize.less","less/print.less","bootstrap.css","dist/css/bootstrap.css","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/mixins/reset-text.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":";;;;4EAQA,KACE,YAAA,WACA,yBAAA,KACA,qBAAA,KAOF,KACE,OAAA,EAaF,QAAA,MAAA,QAAA,WAAA,OAAA,OAAA,OAAA,OAAA,KAAA,KAAA,IAAA,QAAA,QAaE,QAAA,MAQF,MAAA,OAAA,SAAA,MAIE,QAAA,aACA,eAAA,SAQF,sBACE,QAAA,KACA,OAAA,EAQF,SAAA,SAEE,QAAA,KAUF,EACE,iBAAA,YAQF,SAAA,QAEE,QAAA,EAUF,YACE,cAAA,IAAA,OAOF,EAAA,OAEE,YAAA,IAOF,IACE,WAAA,OAQF,GACE,OAAA,MAAA,EACA,UAAA,IAOF,KACE,MAAA,KACA,WAAA,KAOF,MACE,UAAA,IAOF,IAAA,IAEE,SAAA,SACA,UAAA,IACA,YAAA,EACA,eAAA,SAGF,IACE,IAAA,MAGF,IACE,OAAA,OAUF,IACE,OAAA,EAOF,eACE,SAAA,OAUF,OACE,OAAA,IAAA,KAOF,GACE,OAAA,EAAA,mBAAA,YAAA,gBAAA,YACA,WAAA,YAOF,IACE,SAAA,KAOF,KAAA,IAAA,IAAA,KAIE,YAAA,UAAA,UACA,UAAA,IAkBF,OAAA,MAAA,SAAA,OAAA,SAKE,OAAA,EACA,KAAA,QACA,MAAA,QAOF,OACE,SAAA,QAUF,OAAA,OAEE,eAAA,KAWF,OAAA,wBAAA,kBAAA,mBAIE,mBAAA,OACA,OAAA,QAOF,iBAAA,qBAEE,OAAA,QAOF,yBAAA,wBAEE,QAAA,EACA,OAAA,EAQF,MACE,YAAA,OAWF,qBAAA,kBAEE,mBAAA,WAAA,gBAAA,WAAA,WAAA,WACA,QAAA,EASF,8CAAA,8CAEE,OAAA,KAQF,mBACE,mBAAA,YACA,gBAAA,YAAA,WAAA,YAAA,mBAAA,UASF,iDAAA,8CAEE,mBAAA,KAOF,SACE,QAAA,MAAA,OAAA,MACA,OAAA,EAAA,IACA,OAAA,IAAA,MAAA,OAQF,OACE,QAAA,EACA,OAAA,EAOF,SACE,SAAA,KAQF,SACE,YAAA,IAUF,MACE,eAAA,EACA,gBAAA,SAGF,GAAA,GAEE,QAAA,uFCjUF,aA7FI,EAAA,OAAA,QAGI,MAAA,eACA,YAAA,eACA,WAAA,cAAA,mBAAA,eACA,WAAA,eAGJ,EAAA,UAEI,gBAAA,UAGJ,cACI,QAAA,KAAA,WAAA,IAGJ,kBACI,QAAA,KAAA,YAAA,IAKJ,6BAAA,mBAEI,QAAA,GAGJ,WAAA,IAEI,OAAA,IAAA,MAAA,KC4KL,kBAAA,MDvKK,MC0KL,QAAA,mBDrKK,IE8KN,GDLC,kBAAA,MDrKK,ICwKL,UAAA,eCUD,GF5KM,GE2KN,EF1KM,QAAA,ECuKL,OAAA,ECSD,GF3KM,GCsKL,iBAAA,MD/JK,QCkKL,QAAA,KCSD,YFtKU,oBCiKT,iBAAA,eD7JK,OCgKL,OAAA,IAAA,MAAA,KD5JK,OC+JL,gBAAA,mBCSD,UFpKU,UC+JT,iBAAA,eDzJS,mBEkKV,mBDLC,OAAA,IAAA,MAAA,gBEjPD,WACA,YAAA,uBFsPD,IAAA,+CE7OC,IAAK,sDAAuD,4BAA6B,iDAAkD,gBAAiB,gDAAiD,eAAgB,+CAAgD,mBAAoB,2EAA4E,cAE7W,WACA,SAAA,SACA,IAAA,IACA,QAAA,aACA,YAAA,uBACA,WAAA,OACA,YAAA,IACA,YAAA,EAIkC,uBAAA,YAAW,wBAAA,UACX,2BAAW,QAAA,QAEX,uBDuPlC,QAAS,QCtPyB,sBFiPnC,uBEjP8C,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,2BAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,6BAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,2BAAW,QAAA,QACX,qBAAW,QAAA,QACX,0BAAW,QAAA,QACX,qBAAW,QAAA,QACX,yBAAW,QAAA,QACX,0BAAW,QAAA,QACX,2BAAW,QAAA,QACX,sBAAW,QAAA,QACX,yBAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,+BAAW,QAAA,QACX,2BAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,8BAAW,QAAA,QACX,yBAAW,QAAA,QACX,0BAAW,QAAA,QACX,2BAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,6BAAW,QAAA,QACX,6BAAW,QAAA,QACX,8BAAW,QAAA,QACX,4BAAW,QAAA,QACX,yBAAW,QAAA,QACX,0BAAW,QAAA,QACX,sBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,2BAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,yBAAW,QAAA,QACX,8BAAW,QAAA,QACX,6BAAW,QAAA,QACX,6BAAW,QAAA,QACX,+BAAW,QAAA,QACX,8BAAW,QAAA,QACX,gCAAW,QAAA,QACX,uBAAW,QAAA,QACX,8BAAW,QAAA,QACX,+BAAW,QAAA,QACX,iCAAW,QAAA,QACX,0BAAW,QAAA,QACX,6BAAW,QAAA,QACX,yBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,gCAAW,QAAA,QACX,gCAAW,QAAA,QACX,2BAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,0BAAW,QAAA,QACX,+BAAW,QAAA,QACX,+BAAW,QAAA,QACX,wBAAW,QAAA,QACX,+BAAW,QAAA,QACX,gCAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,8BAAW,QAAA,QACX,0BAAW,QAAA,QACX,gCAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,gCAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,6BAAW,QAAA,QACX,8BAAW,QAAA,QACX,2BAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QACX,8BAAW,QAAA,QACX,+BAAW,QAAA,QACX,mCAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,2BAAW,QAAA,QACX,4BAAW,QAAA,QACX,+BAAW,QAAA,QACX,wBAAW,QAAA,QACX,2BAAW,QAAA,QACX,yBAAW,QAAA,QACX,0BAAW,QAAA,QACX,yBAAW,QAAA,QACX,6BAAW,QAAA,QACX,+BAAW,QAAA,QACX,0BAAW,QAAA,QACX,gCAAW,QAAA,QACX,+BAAW,QAAA,QACX,8BAAW,QAAA,QACX,kCAAW,QAAA,QACX,oCAAW,QAAA,QACX,sBAAW,QAAA,QACX,2BAAW,QAAA,QACX,uBAAW,QAAA,QACX,8BAAW,QAAA,QACX,4BAAW,QAAA,QACX,8BAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QACX,0BAAW,QAAA,QACX,4BAAW,QAAA,QACX,qCAAW,QAAA,QACX,oCAAW,QAAA,QACX,kCAAW,QAAA,QACX,oCAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,8BAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,0BAAW,QAAA,QACX,sBAAW,QAAA,QACX,sBAAW,QAAA,QACX,uBAAW,QAAA,QACX,mCAAW,QAAA,QACX,uCAAW,QAAA,QACX,gCAAW,QAAA,QACX,oCAAW,QAAA,QACX,qCAAW,QAAA,QACX,yCAAW,QAAA,QACX,4BAAW,QAAA,QACX,yBAAW,QAAA,QACX,gCAAW,QAAA,QACX,8BAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,0BAAW,QAAA,QACX,6BAAW,QAAA,QACX,yBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,yBAAW,QAAA,QACX,uBAAW,QAAA,QACX,8BAAW,QAAA,QACX,+BAAW,QAAA,QACX,gCAAW,QAAA,QACX,8BAAW,QAAA,QACX,8BAAW,QAAA,QACX,8BAAW,QAAA,QACX,2BAAW,QAAA,QACX,0BAAW,QAAA,QACX,yBAAW,QAAA,QACX,6BAAW,QAAA,QACX,2BAAW,QAAA,QACX,4BAAW,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,2BAAW,QAAA,QACX,2BAAW,QAAA,QACX,4BAAW,QAAA,QACX,+BAAW,QAAA,QACX,8BAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,iCAAW,QAAA,QACX,oCAAW,QAAA,QACX,iCAAW,QAAA,QACX,+BAAW,QAAA,QACX,+BAAW,QAAA,QACX,iCAAW,QAAA,QACX,qBAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,2BAAW,QAAA,QACX,uBAAW,QAAA,QASX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,4BAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,yBAAW,QAAA,QACX,yBAAW,QAAA,QACX,+BAAW,QAAA,QACX,uBAAW,QAAA,QACX,6BAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,4BAAW,QAAA,QACX,uBAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,2BAAW,QAAA,QACX,0BAAW,QAAA,QACX,sBAAW,QAAA,QACX,sBAAW,QAAA,QACX,sBAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,4BAAW,QAAA,QACX,mCAAW,QAAA,QACX,4BAAW,QAAA,QACX,oCAAW,QAAA,QACX,kCAAW,QAAA,QACX,iCAAW,QAAA,QACX,+BAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,kCAAW,QAAA,QACX,mCAAW,QAAA,QACX,sCAAW,QAAA,QACX,0CAAW,QAAA,QACX,oCAAW,QAAA,QACX,wCAAW,QAAA,QACX,qCAAW,QAAA,QACX,iCAAW,QAAA,QACX,gCAAW,QAAA,QACX,kCAAW,QAAA,QACX,+BAAW,QAAA,QACX,0BAAW,QAAA,QACX,8BAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QCtS/C,0BCgEE,QAAA,QHi+BF,EDNC,mBAAA,WGxhCI,gBAAiB,WFiiCZ,WAAY,WGl+BZ,OADL,QJg+BJ,mBAAA,WGthCI,gBAAiB,WACpB,WAAA,WHyhCD,KGrhCC,UAAW,KAEX,4BAAA,cAEA,KACA,YAAA,iBAAA,UAAA,MAAA,WHuhCD,UAAA,KGnhCC,YAAa,WF4hCb,MAAO,KACP,iBAAkB,KExhClB,OADA,MAEA,OHqhCD,SG/gCC,YAAa,QACb,UAAA,QACA,YAAA,QAEA,EFwhCA,MAAO,QEthCL,gBAAA,KAIF,QH8gCD,QKjkCC,MAAA,QACA,gBAAA,UF6DF,QACE,QAAA,IAAA,KAAA,yBHygCD,eAAA,KGlgCC,OHqgCD,OAAA,ECSD,IACE,eAAgB,ODDjB,4BM/kCC,0BLklCF,gBKnlCE,iBADA,eH4EA,QAAS,MACT,UAAA,KHugCD,OAAA,KGhgCC,aACA,cAAA,IAEA,eACA,QAAA,aC6FA,UAAA,KACK,OAAA,KACG,QAAA,IEvLR,YAAA,WACA,iBAAA,KACA,OAAA,IAAA,MAAA,KN+lCD,cAAA,IGjgCC,mBAAoB,IAAI,IAAI,YAC5B,cAAA,IAAA,IAAA,YHmgCD,WAAA,IAAA,IAAA,YG5/BC,YACA,cAAA,IAEA,GH+/BD,WAAA,KGv/BC,cAAe,KACf,OAAA,EACA,WAAA,IAAA,MAAA,KAEA,SACA,SAAA,SACA,MAAA,IACA,OAAA,IACA,QAAA,EHy/BD,OAAA,KGj/BC,SAAA,OF0/BA,KAAM,cEx/BJ,OAAA,EAEA,0BACA,yBACA,SAAA,OACA,MAAA,KHm/BH,OAAA,KGx+BC,OAAQ,EACR,SAAA,QH0+BD,KAAA,KCSD,cACE,OAAQ,QAQV,IACA,IMlpCE,IACA,IACA,IACA,INwoCF,GACA,GACA,GACA,GACA,GACA,GDAC,YAAA,QOlpCC,YAAa,IN2pCb,YAAa,IACb,MAAO,QAoBT,WAZA,UAaA,WAZA,UM5pCI,WN6pCJ,UM5pCI,WN6pCJ,UM5pCI,WN6pCJ,UDMC,WCLD,UACA,UAZA,SAaA,UAZA,SAaA,UAZA,SAaA,UAZA,SAaA,UAZA,SAaA,UAZA,SMppCE,YAAa,INwqCb,YAAa,EACb,MAAO,KAGT,IMxqCE,IAJF,IN2qCA,GAEA,GDLC,GCSC,WAAY,KACZ,cAAe,KASjB,WANA,UDCC,WCCD,UM5qCA,WN8qCA,UACA,UANA,SM5qCI,UN8qCJ,SM3qCA,UN6qCA,SAQE,UAAW,IAGb,IMprCE,IAJF,INurCA,GAEA,GDLC,GCSC,WAAY,KACZ,cAAe,KASjB,WANA,UDCC,WCCD,UMvrCA,WNyrCA,UACA,UANA,SMxrCI,UN0rCJ,SMtrCA,UNwrCA,SMxrCU,UAAA,IACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,KAOR,IADF,GPssCC,UAAA,KCSD,EMzsCE,OAAA,EAAA,EAAA,KAEA,MPosCD,cAAA,KO/rCC,UAAW,KAwOX,YAAa,IA1OX,YAAA,IPssCH,yBO7rCC,MNssCE,UAAW,MMjsCf,OAAA,MAEE,UAAA,IAKF,MP0rCC,KO1rCsB,QAAA,KP6rCtB,iBAAA,QO5rCsB,WP+rCtB,WAAA,KO9rCsB,YPisCtB,WAAA,MOhsCsB,aPmsCtB,WAAA,OOlsCsB,cPqsCtB,WAAA,QOlsCsB,aPqsCtB,YAAA,OOpsCsB,gBPusCtB,eAAA,UOtsCsB,gBPysCtB,eAAA,UOrsCC,iBPwsCD,eAAA,WQ3yCC,YR8yCD,MAAA,KCSD,cOpzCI,MAAA,QAHF,qBDwGF,qBP6sCC,MAAA,QCSD,cO3zCI,MAAA,QAHF,qBD2GF,qBPitCC,MAAA,QCSD,WOl0CI,MAAA,QAHF,kBD8GF,kBPqtCC,MAAA,QCSD,cOz0CI,MAAA,QAHF,qBDiHF,qBPytCC,MAAA,QCSD,aOh1CI,MAAA,QDwHF,oBAHF,oBExHE,MAAA,QACA,YR01CA,MAAO,KQx1CL,iBAAA,QAHF,mBF8HF,mBP2tCC,iBAAA,QCSD,YQ/1CI,iBAAA,QAHF,mBFiIF,mBP+tCC,iBAAA,QCSD,SQt2CI,iBAAA,QAHF,gBFoIF,gBPmuCC,iBAAA,QCSD,YQ72CI,iBAAA,QAHF,mBFuIF,mBPuuCC,iBAAA,QCSD,WQp3CI,iBAAA,QF6IF,kBADF,kBAEE,iBAAA,QPsuCD,aO7tCC,eAAgB,INsuChB,OAAQ,KAAK,EAAE,KMpuCf,cAAA,IAAA,MAAA,KAFF,GPkuCC,GCSC,WAAY,EACZ,cAAe,KM9tCf,MP0tCD,MO3tCD,MAPI,MASF,cAAA,EAIF,eALE,aAAA,EACA,WAAA,KPkuCD,aO9tCC,aAAc,EAKZ,YAAA,KACA,WAAA,KP6tCH,gBOvtCC,QAAS,aACT,cAAA,IACA,aAAA,IAEF,GNguCE,WAAY,EM9tCZ,cAAA,KAGA,GADF,GP0tCC,YAAA,WOttCC,GPytCD,YAAA,IOnnCD,GAvFM,YAAA,EAEA,yBACA,kBGtNJ,MAAA,KACA,MAAA,MACA,SAAA,OVq6CC,MAAA,KO7nCC,WAAY,MAhFV,cAAA,SPgtCH,YAAA,OOtsCD,kBNgtCE,YAAa,OM1sCjB,0BPssCC,YOrsCC,OAAA,KA9IqB,cAAA,IAAA,OAAA,KAmJvB,YACE,UAAA,IACA,eAAA,UAEA,WPssCD,QAAA,KAAA,KOjsCG,OAAA,EAAA,EAAA,KN0sCF,UAAW,OACX,YAAa,IAAI,MAAM,KMptCzB,yBP+sCC,wBO/sCD,yBNytCE,cAAe,EMnsCb,kBAFA,kBACA,iBPksCH,QAAA,MO/rCG,UAAA,INwsCF,YAAa,WACb,MAAO,KMhsCT,yBP2rCC,yBO3rCD,wBAEE,QAAA,cAEA,oBACA,sBACA,cAAA,KP6rCD,aAAA,EOvrCG,WAAA,MNgsCF,aAAc,IAAI,MAAM,KACxB,YAAa,EMhsCX,kCNksCJ,kCMnsCe,iCACX,oCNmsCJ,oCDLC,mCCUC,QAAS,GMjsCX,iCNmsCA,iCMzsCM,gCAOJ,mCNmsCF,mCDLC,kCO7rCC,QAAA,cPksCD,QWv+CC,cAAe,KVg/Cf,WAAY,OACZ,YAAa,WU7+Cb,KXy+CD,IWr+CD,IACE,KACA,YAAA,MAAA,OAAA,SAAA,cAAA,UAEA,KACA,QAAA,IAAA,IXu+CD,UAAA,IWn+CC,MAAO,QACP,iBAAA,QACA,cAAA,IAEA,IACA,QAAA,IAAA,IACA,UAAA,IV4+CA,MU5+CA,KXq+CD,iBAAA,KW3+CC,cAAe,IASb,mBAAA,MAAA,EAAA,KAAA,EAAA,gBACA,WAAA,MAAA,EAAA,KAAA,EAAA,gBAEA,QV6+CF,QU7+CE,EXq+CH,UAAA,KWh+CC,YAAa,IACb,mBAAA,KACA,WAAA,KAEA,IACA,QAAA,MACA,QAAA,MACA,OAAA,EAAA,EAAA,KACA,UAAA,KACA,YAAA,WACA,MAAA,KACA,WAAA,UXk+CD,UAAA,WW7+CC,iBAAkB,QAehB,OAAA,IAAA,MAAA,KACA,cAAA,IAEA,SACA,QAAA,EACA,UAAA,QXi+CH,MAAA,QW59CC,YAAa,SACb,iBAAA,YACA,cAAA,EC1DF,gBCHE,WAAA,MACA,WAAA,OAEA,Wb8hDD,cAAA,KYxhDC,aAAA,KAqEA,aAAc,KAvEZ,YAAA,KZ+hDH,yBY1hDC,WAkEE,MAAO,OZ69CV,yBY5hDC,WA+DE,MAAO,OZk+CV,0BYzhDC,WCvBA,MAAA,QAGA,iBbmjDD,cAAA,KYthDC,aAAc,KCvBd,aAAA,KACA,YAAA,KCAE,KACE,aAAA,MAEA,YAAA,MAGA,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UdgjDL,SAAA,SchiDG,WAAA,IACE,cAAA,KdkiDL,aAAA,Kc1hDG,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Ud6hDH,MAAA,Kc7hDG,WdgiDH,MAAA,KchiDG,WdmiDH,MAAA,acniDG,WdsiDH,MAAA,actiDG,UdyiDH,MAAA,IcziDG,Ud4iDH,MAAA,ac5iDG,Ud+iDH,MAAA,ac/iDG,UdkjDH,MAAA,IcljDG,UdqjDH,MAAA,acrjDG,UdwjDH,MAAA,acxjDG,Ud2jDH,MAAA,Ic3jDG,Ud8jDH,MAAA,ac/iDG,UdkjDH,MAAA,YcljDG,gBdqjDH,MAAA,KcrjDG,gBdwjDH,MAAA,acxjDG,gBd2jDH,MAAA,ac3jDG,ed8jDH,MAAA,Ic9jDG,edikDH,MAAA,acjkDG,edokDH,MAAA,acpkDG,edukDH,MAAA,IcvkDG,ed0kDH,MAAA,ac1kDG,ed6kDH,MAAA,ac7kDG,edglDH,MAAA,IchlDG,edmlDH,MAAA,ac9kDG,edilDH,MAAA,YchmDG,edmmDH,MAAA,KcnmDG,gBdsmDH,KAAA,KctmDG,gBdymDH,KAAA,aczmDG,gBd4mDH,KAAA,ac5mDG,ed+mDH,KAAA,Ic/mDG,edknDH,KAAA,aclnDG,edqnDH,KAAA,acrnDG,edwnDH,KAAA,IcxnDG,ed2nDH,KAAA,ac3nDG,ed8nDH,KAAA,ac9nDG,edioDH,KAAA,IcjoDG,edooDH,KAAA,ac/nDG,edkoDH,KAAA,YcnnDG,edsnDH,KAAA,KctnDG,kBdynDH,YAAA,KcznDG,kBd4nDH,YAAA,ac5nDG,kBd+nDH,YAAA,ac/nDG,iBdkoDH,YAAA,IcloDG,iBdqoDH,YAAA,acroDG,iBdwoDH,YAAA,acxoDG,iBd2oDH,YAAA,Ic3oDG,iBd8oDH,YAAA,ac9oDG,iBdipDH,YAAA,acjpDG,iBdopDH,YAAA,IcppDG,iBdupDH,YAAA,acvpDG,iBd0pDH,YAAA,Yc5rDG,iBACE,YAAA,EAOJ,yBACE,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Ud0rDD,MAAA,Kc1rDC,Wd6rDD,MAAA,Kc7rDC,WdgsDD,MAAA,achsDC,WdmsDD,MAAA,acnsDC,UdssDD,MAAA,IctsDC,UdysDD,MAAA,aczsDC,Ud4sDD,MAAA,ac5sDC,Ud+sDD,MAAA,Ic/sDC,UdktDD,MAAA,acltDC,UdqtDD,MAAA,acrtDC,UdwtDD,MAAA,IcxtDC,Ud2tDD,MAAA,ac5sDC,Ud+sDD,MAAA,Yc/sDC,gBdktDD,MAAA,KcltDC,gBdqtDD,MAAA,acrtDC,gBdwtDD,MAAA,acxtDC,ed2tDD,MAAA,Ic3tDC,ed8tDD,MAAA,ac9tDC,ediuDD,MAAA,acjuDC,edouDD,MAAA,IcpuDC,eduuDD,MAAA,acvuDC,ed0uDD,MAAA,ac1uDC,ed6uDD,MAAA,Ic7uDC,edgvDD,MAAA,ac3uDC,ed8uDD,MAAA,Yc7vDC,edgwDD,MAAA,KchwDC,gBdmwDD,KAAA,KcnwDC,gBdswDD,KAAA,actwDC,gBdywDD,KAAA,aczwDC,ed4wDD,KAAA,Ic5wDC,ed+wDD,KAAA,ac/wDC,edkxDD,KAAA,aclxDC,edqxDD,KAAA,IcrxDC,edwxDD,KAAA,acxxDC,ed2xDD,KAAA,ac3xDC,ed8xDD,KAAA,Ic9xDC,ediyDD,KAAA,ac5xDC,ed+xDD,KAAA,YchxDC,edmxDD,KAAA,KcnxDC,kBdsxDD,YAAA,KctxDC,kBdyxDD,YAAA,aczxDC,kBd4xDD,YAAA,ac5xDC,iBd+xDD,YAAA,Ic/xDC,iBdkyDD,YAAA,aclyDC,iBdqyDD,YAAA,acryDC,iBdwyDD,YAAA,IcxyDC,iBd2yDD,YAAA,ac3yDC,iBd8yDD,YAAA,ac9yDC,iBdizDD,YAAA,IcjzDC,iBdozDD,YAAA,acpzDC,iBduzDD,YAAA,YY9yDD,iBE3CE,YAAA,GAQF,yBACE,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Udw1DD,MAAA,Kcx1DC,Wd21DD,MAAA,Kc31DC,Wd81DD,MAAA,ac91DC,Wdi2DD,MAAA,acj2DC,Udo2DD,MAAA,Icp2DC,Udu2DD,MAAA,acv2DC,Ud02DD,MAAA,ac12DC,Ud62DD,MAAA,Ic72DC,Udg3DD,MAAA,ach3DC,Udm3DD,MAAA,acn3DC,Uds3DD,MAAA,Ict3DC,Udy3DD,MAAA,ac12DC,Ud62DD,MAAA,Yc72DC,gBdg3DD,MAAA,Kch3DC,gBdm3DD,MAAA,acn3DC,gBds3DD,MAAA,act3DC,edy3DD,MAAA,Icz3DC,ed43DD,MAAA,ac53DC,ed+3DD,MAAA,ac/3DC,edk4DD,MAAA,Icl4DC,edq4DD,MAAA,acr4DC,edw4DD,MAAA,acx4DC,ed24DD,MAAA,Ic34DC,ed84DD,MAAA,acz4DC,ed44DD,MAAA,Yc35DC,ed85DD,MAAA,Kc95DC,gBdi6DD,KAAA,Kcj6DC,gBdo6DD,KAAA,acp6DC,gBdu6DD,KAAA,acv6DC,ed06DD,KAAA,Ic16DC,ed66DD,KAAA,ac76DC,edg7DD,KAAA,ach7DC,edm7DD,KAAA,Icn7DC,eds7DD,KAAA,act7DC,edy7DD,KAAA,acz7DC,ed47DD,KAAA,Ic57DC,ed+7DD,KAAA,ac17DC,ed67DD,KAAA,Yc96DC,edi7DD,KAAA,Kcj7DC,kBdo7DD,YAAA,Kcp7DC,kBdu7DD,YAAA,acv7DC,kBd07DD,YAAA,ac17DC,iBd67DD,YAAA,Ic77DC,iBdg8DD,YAAA,ach8DC,iBdm8DD,YAAA,acn8DC,iBds8DD,YAAA,Ict8DC,iBdy8DD,YAAA,acz8DC,iBd48DD,YAAA,ac58DC,iBd+8DD,YAAA,Ic/8DC,iBdk9DD,YAAA,acl9DC,iBdq9DD,YAAA,YYz8DD,iBE9CE,YAAA,GAQF,0BACE,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Uds/DD,MAAA,Kct/DC,Wdy/DD,MAAA,Kcz/DC,Wd4/DD,MAAA,ac5/DC,Wd+/DD,MAAA,ac//DC,UdkgED,MAAA,IclgEC,UdqgED,MAAA,acrgEC,UdwgED,MAAA,acxgEC,Ud2gED,MAAA,Ic3gEC,Ud8gED,MAAA,ac9gEC,UdihED,MAAA,acjhEC,UdohED,MAAA,IcphEC,UduhED,MAAA,acxgEC,Ud2gED,MAAA,Yc3gEC,gBd8gED,MAAA,Kc9gEC,gBdihED,MAAA,acjhEC,gBdohED,MAAA,acphEC,eduhED,MAAA,IcvhEC,ed0hED,MAAA,ac1hEC,ed6hED,MAAA,ac7hEC,edgiED,MAAA,IchiEC,edmiED,MAAA,acniEC,edsiED,MAAA,actiEC,edyiED,MAAA,IcziEC,ed4iED,MAAA,acviEC,ed0iED,MAAA,YczjEC,ed4jED,MAAA,Kc5jEC,gBd+jED,KAAA,Kc/jEC,gBdkkED,KAAA,aclkEC,gBdqkED,KAAA,acrkEC,edwkED,KAAA,IcxkEC,ed2kED,KAAA,ac3kEC,ed8kED,KAAA,ac9kEC,edilED,KAAA,IcjlEC,edolED,KAAA,acplEC,edulED,KAAA,acvlEC,ed0lED,KAAA,Ic1lEC,ed6lED,KAAA,acxlEC,ed2lED,KAAA,Yc5kEC,ed+kED,KAAA,Kc/kEC,kBdklED,YAAA,KcllEC,kBdqlED,YAAA,acrlEC,kBdwlED,YAAA,acxlEC,iBd2lED,YAAA,Ic3lEC,iBd8lED,YAAA,ac9lEC,iBdimED,YAAA,acjmEC,iBdomED,YAAA,IcpmEC,iBdumED,YAAA,acvmEC,iBd0mED,YAAA,ac1mEC,iBd6mED,YAAA,Ic7mEC,iBdgnED,YAAA,achnEC,iBdmnED,YAAA,YetrED,iBACA,YAAA,GAGA,MACA,iBAAA,YAEA,QfyrED,YAAA,IevrEC,eAAgB,IAChB,MAAA,KfyrED,WAAA,KelrEC,GACA,WAAA,KfsrED,OexrEC,MAAO,KdmsEP,UAAW,KACX,cAAe,KcvrET,mBd0rER,mBczrEQ,mBAHA,mBACA,mBd0rER,mBDHC,QAAA,IensEC,YAAa,WAoBX,eAAA,IACA,WAAA,IAAA,MAAA,KArBJ,mBdktEE,eAAgB,OAChB,cAAe,IAAI,MAAM,KDJ1B,uCCMD,uCcrtEA,wCdstEA,wCclrEI,2CANI,2CforEP,WAAA,EezqEG,mBf4qEH,WAAA,IAAA,MAAA,KCWD,cACE,iBAAkB,Kc/pEpB,6BdkqEA,6BcjqEE,6BAZM,6BfsqEP,6BCMD,6BDHC,QAAA,ICWD,gBACE,OAAQ,IAAI,MAAM,Kc1qEpB,4Bd6qEA,4Bc7qEA,4BAQQ,4Bf8pEP,4BCMD,4Bc7pEM,OAAA,IAAA,MAAA,KAYF,4BAFJ,4BfopEC,oBAAA,IevoEG,yCf0oEH,iBAAA,QehoEC,4BACA,iBAAA,QfooED,uBe9nEG,SAAA,OdyoEF,QAAS,acxoEL,MAAA,KAEA,sBfioEL,sBgB7wEC,SAAA,OfwxEA,QAAS,WACT,MAAO,KAST,0BerxEE,0Bf+wEF,0BAGA,0BexxEM,0BAMJ,0BfgxEF,0BAGA,0BACA,0BDNC,0BCAD,0BAGA,0BASE,iBAAkB,QDLnB,sCgBlyEC,sCAAA,oCfyyEF,sCetxEM,sCf2xEJ,iBAAkB,QASpB,2Be1yEE,2BfoyEF,2BAGA,2Be7yEM,2BAMJ,2BfqyEF,2BAGA,2BACA,2BDNC,2BCAD,2BAGA,2BASE,iBAAkB,QDLnB,uCgBvzEC,uCAAA,qCf8zEF,uCe3yEM,uCfgzEJ,iBAAkB,QASpB,wBe/zEE,wBfyzEF,wBAGA,wBel0EM,wBAMJ,wBf0zEF,wBAGA,wBACA,wBDNC,wBCAD,wBAGA,wBASE,iBAAkB,QDLnB,oCgB50EC,oCAAA,kCfm1EF,oCeh0EM,oCfq0EJ,iBAAkB,QASpB,2Bep1EE,2Bf80EF,2BAGA,2Bev1EM,2BAMJ,2Bf+0EF,2BAGA,2BACA,2BDNC,2BCAD,2BAGA,2BASE,iBAAkB,QDLnB,uCgBj2EC,uCAAA,qCfw2EF,uCer1EM,uCf01EJ,iBAAkB,QASpB,0Bez2EE,0Bfm2EF,0BAGA,0Be52EM,0BAMJ,0Bfo2EF,0BAGA,0BACA,0BDNC,0BCAD,0BAGA,0BASE,iBAAkB,QDLnB,sCehtEC,sCADF,oCdwtEA,sCe12EM,sCDoJJ,iBAAA,QA6DF,kBACE,WAAY,KA3DV,WAAA,KAEA,oCACA,kBACA,MAAA,KfotED,cAAA,Ke7pEC,WAAY,OAnDV,mBAAA,yBfmtEH,OAAA,IAAA,MAAA,KCWD,yBACE,cAAe,Ec5qEjB,qCd+qEA,qCcjtEI,qCARM,qCfktET,qCCMD,qCDHC,YAAA,OCWD,kCACE,OAAQ,EcvrEV,0Dd0rEA,0Dc1rEA,0DAzBU,0Df4sET,0DCMD,0DAME,YAAa,Ec/rEf,yDdksEA,yDclsEA,yDArBU,yDfgtET,yDCMD,yDAME,aAAc,EDLjB,yDe1sEW,yDEzNV,yDjBk6EC,yDiBj6ED,cAAA,GAMA,SjBk6ED,UAAA,EiB/5EC,QAAS,EACT,OAAA,EACA,OAAA,EAEA,OACA,QAAA,MACA,MAAA,KACA,QAAA,EACA,cAAA,KACA,UAAA,KjBi6ED,YAAA,QiB95EC,MAAO,KACP,OAAA,EACA,cAAA,IAAA,MAAA,QAEA,MjBg6ED,QAAA,aiBr5EC,UAAW,Kb4BX,cAAA,IACG,YAAA,IJ63EJ,mBiBr5EC,mBAAoB,WhBg6EjB,gBAAiB,WgB95EpB,WAAA,WjBy5ED,qBiBv5EC,kBAGA,OAAQ,IAAI,EAAE,EACd,WAAA,MjBs5ED,YAAA,OiBj5EC,iBACA,QAAA,MAIF,kBhB25EE,QAAS,MgBz5ET,MAAA,KAIF,iBAAA,ahB05EE,OAAQ,KI99ER,uBY2EF,2BjB64EC,wBiB54EC,QAAA,IAAA,KAAA,yBACA,eAAA,KAEA,OACA,QAAA,MjB+4ED,YAAA,IiBr3EC,UAAW,KACX,YAAA,WACA,MAAA,KAEA,cACA,QAAA,MACA,MAAA,KACA,OAAA,KACA,QAAA,IAAA,KACA,UAAA,KACA,YAAA,WACA,MAAA,KbxDA,iBAAA,KACQ,iBAAA,KAyHR,OAAA,IAAA,MAAA,KACK,cAAA,IACG,mBAAA,MAAA,EAAA,IAAA,IAAA,iBJwzET,WAAA,MAAA,EAAA,IAAA,IAAA,iBkBh8EC,mBAAA,aAAA,YAAA,KAAA,mBAAA,YAAA,KACE,cAAA,aAAA,YAAA,KAAA,WAAA,YAAA,KACA,WAAA,aAAA,YAAA,KAAA,WAAA,YAAA,KdWM,oBJy7ET,aAAA,QIx5EC,QAAA,EACE,mBAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,qBACA,WAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,qBAEF,gCAA0B,MAAA,KJ25E3B,QAAA,EI15EiC,oCJ65EjC,MAAA,KiBh4EG,yCACA,MAAA,KAQF,0BhBs4EA,iBAAkB,YAClB,OAAQ,EgBn4EN,wBjB63EH,wBiB13EC,iChBq4EA,iBAAkB,KgBn4EhB,QAAA,EAIF,wBACE,iCjB03EH,OAAA,YiB72EC,sBjBg3ED,OAAA,KiB91EG,mBhB02EF,mBAAoB,KAEtB,qDgB32EM,8BjBo2EH,8BiBj2EC,wCAAA,+BhB62EA,YAAa,KgB32EX,iCjBy2EH,iCiBt2EC,2CAAA,kChB02EF,0BACA,0BACA,oCACA,2BAKE,YAAa,KgBh3EX,iCjB82EH,iCACF,2CiBp2EC,kChBu2EA,0BACA,0BACA,oCACA,2BgBz2EA,YAAA,MhBi3EF,YgBv2EE,cAAA,KAGA,UADA,OjBi2ED,SAAA,SiBr2EC,QAAS,MhBg3ET,WAAY,KgBx2EV,cAAA,KAGA,gBADA,aAEA,WAAA,KjBi2EH,aAAA,KiB91EC,cAAe,EhBy2Ef,YAAa,IACb,OAAQ,QgBp2ER,+BjBg2ED,sCiBl2EC,yBACA,gCAIA,SAAU,ShBw2EV,WAAY,MgBt2EZ,YAAA,MAIF,oBAAA,cAEE,WAAA,KAGA,iBADA,cAEA,SAAA,SACA,QAAA,aACA,aAAA,KjB61ED,cAAA,EiB31EC,YAAa,IhBs2Eb,eAAgB,OgBp2EhB,OAAA,QAUA,kCjBo1ED,4BCWC,WAAY,EACZ,YAAa,KgBv1Eb,wCAAA,qCjBm1ED,8BCOD,+BgBh2EI,2BhB+1EJ,4BAME,OAAQ,YDNT,0BiBv1EG,uBAMF,oCAAA,iChB61EA,OAAQ,YDNT,yBiBp1EK,sBAaJ,mCAFF,gCAGE,OAAA,YAGA,qBjBy0ED,WAAA,KiBv0EC,YAAA,IhBk1EA,eAAgB,IgBh1Ed,cAAA,EjB00EH,8BiB5zED,8BCnQE,cAAA,EACA,aAAA,EAEA,UACA,OAAA,KlBkkFD,QAAA,IAAA,KkBhkFC,UAAA,KACE,YAAA,IACA,cAAA,IAGF,gBjB0kFA,OAAQ,KiBxkFN,YAAA,KD2PA,0BAFJ,kBAGI,OAAA,KAEA,6BACA,OAAA,KjBy0EH,QAAA,IAAA,KiB/0EC,UAAW,KAST,YAAA,IACA,cAAA,IAVJ,mChB81EE,OAAQ,KgBh1EN,YAAA,KAGA,6CAjBJ,qCAkBI,OAAA,KAEA,oCACA,OAAA,KjBy0EH,WAAA,KiBr0EC,QAAS,IAAI,KC/Rb,UAAA,KACA,YAAA,IAEA,UACA,OAAA,KlBumFD,QAAA,KAAA,KkBrmFC,UAAA,KACE,YAAA,UACA,cAAA,IAGF,gBjB+mFA,OAAQ,KiB7mFN,YAAA,KDuRA,0BAFJ,kBAGI,OAAA,KAEA,6BACA,OAAA,KjBk1EH,QAAA,KAAA,KiBx1EC,UAAW,KAST,YAAA,UACA,cAAA,IAVJ,mChBu2EE,OAAQ,KgBz1EN,YAAA,KAGA,6CAjBJ,qCAkBI,OAAA,KAEA,oCACA,OAAA,KjBk1EH,WAAA,KiBz0EC,QAAS,KAAK,KAEd,UAAA,KjB00ED,YAAA,UiBt0EG,cjBy0EH,SAAA,SiBp0EC,4BACA,cAAA,OAEA,uBACA,SAAA,SACA,IAAA,EACA,MAAA,EACA,QAAA,EACA,QAAA,MACA,MAAA,KjBu0ED,OAAA,KiBr0EC,YAAa,KhBg1Eb,WAAY,OACZ,eAAgB,KDLjB,oDiBv0EC,uCADA,iCAGA,MAAO,KhBg1EP,OAAQ,KACR,YAAa,KDLd,oDiBv0EC,uCADA,iCAKA,MAAO,KhB80EP,OAAQ,KACR,YAAa,KAKf,uBAEA,8BAJA,4BADA,yBAEA,oBAEA,2BDNC,4BkBruFG,mCAJA,yBD0ZJ,gCbvWE,MAAA,QJ2rFD,2BkBxuFG,aAAA,QACE,mBAAA,MAAA,EAAA,IAAA,IAAA,iBd4CJ,WAAA,MAAA,EAAA,IAAA,IAAA,iBJgsFD,iCiBz1EC,aAAc,QC5YZ,mBAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QACA,WAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QlByuFH,gCiB91EC,MAAO,QCtYL,iBAAA,QlBuuFH,aAAA,QCWD,oCACE,MAAO,QAKT,uBAEA,8BAJA,4BADA,yBAEA,oBAEA,2BDNC,4BkBnwFG,mCAJA,yBD6ZJ,gCb1WE,MAAA,QJytFD,2BkBtwFG,aAAA,QACE,mBAAA,MAAA,EAAA,IAAA,IAAA,iBd4CJ,WAAA,MAAA,EAAA,IAAA,IAAA,iBJ8tFD,iCiBp3EC,aAAc,QC/YZ,mBAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QACA,WAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QlBuwFH,gCiBz3EC,MAAO,QCzYL,iBAAA,QlBqwFH,aAAA,QCWD,oCACE,MAAO,QAKT,qBAEA,4BAJA,0BADA,uBAEA,kBAEA,yBDNC,0BkBjyFG,iCAJA,uBDgaJ,8Bb7WE,MAAA,QJuvFD,yBkBpyFG,aAAA,QACE,mBAAA,MAAA,EAAA,IAAA,IAAA,iBd4CJ,WAAA,MAAA,EAAA,IAAA,IAAA,iBJ4vFD,+BiB/4EC,aAAc,QClZZ,mBAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QACA,WAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QlBqyFH,8BiBp5EC,MAAO,QC5YL,iBAAA,QlBmyFH,aAAA,QiB/4EG,kCjBk5EH,MAAA,QiB/4EG,2CjBk5EH,IAAA,KiBv4EC,mDACA,IAAA,EAEA,YjB04ED,QAAA,MiBvzEC,WAAY,IAwEZ,cAAe,KAtIX,MAAA,QAEA,yBjBy3EH,yBiBrvEC,QAAS,aA/HP,cAAA,EACA,eAAA,OjBw3EH,2BiB1vEC,QAAS,aAxHP,MAAA,KjBq3EH,eAAA,OiBj3EG,kCACA,QAAA,aAmHJ,0BhB4wEE,QAAS,aACT,eAAgB,OgBr3Ed,wCjB82EH,6CiBtwED,2CjBywEC,MAAA,KiB72EG,wCACA,MAAA,KAmGJ,4BhBwxEE,cAAe,EgBp3Eb,eAAA,OAGA,uBADA,oBjB82EH,QAAA,aiBpxEC,WAAY,EhB+xEZ,cAAe,EgBr3EX,eAAA,OAsFN,6BAAA,0BAjFI,aAAA,EAiFJ,4CjB6xEC,sCiBx2EG,SAAA,SjB22EH,YAAA,EiBh2ED,kDhB42EE,IAAK,GgBl2EL,2BjB+1EH,kCiBh2EG,wBAEA,+BAXF,YAAa,IhBo3Eb,WAAY,EgBn2EV,cAAA,EJviBF,2BIshBF,wBJrhBE,WAAA,KI4jBA,6BAyBA,aAAc,MAnCV,YAAA,MAEA,yBjBw1EH,gCACF,YAAA,IiBx3EG,cAAe,EAwCf,WAAA,OAwBJ,sDAdQ,MAAA,KjB80EL,yBACF,+CiBn0EC,YAAA,KAEE,UAAW,MjBs0EZ,yBACF,+CmBp6FG,YAAa,IACf,UAAA,MAGA,KACA,QAAA,aACA,QAAA,IAAA,KAAA,cAAA,EACA,UAAA,KACA,YAAA,IACA,YAAA,WACA,WAAA,OC0CA,YAAA,OACA,eAAA,OACA,iBAAA,aACA,aAAA,ahB+JA,OAAA,QACG,oBAAA,KACC,iBAAA,KACI,gBAAA,KJ+tFT,YAAA,KmBv6FG,iBAAA,KlBm7FF,OAAQ,IAAI,MAAM,YAClB,cAAe,IkB96Ff,kBdzBA,kBACA,WLk8FD,kBCOD,kBADA,WAME,QAAS,IAAI,KAAK,yBAClB,eAAgB,KkBh7FhB,WnBy6FD,WmB56FG,WlBw7FF,MAAO,KkBn7FL,gBAAA,Kf6BM,YADR,YJk5FD,iBAAA,KmBz6FC,QAAA,ElBq7FA,mBAAoB,MAAM,EAAE,IAAI,IAAI,iBAC5B,WAAY,MAAM,EAAE,IAAI,IAAI,iBoBh+FpC,cAGA,ejB8DA,wBACQ,OAAA,YJ05FT,OAAA,kBmBz6FG,mBAAA,KlBq7FM,WAAY,KkBn7FhB,QAAA,IASN,eC3DE,yBACA,eAAA,KpBi+FD,aoB99FC,MAAA,KnB0+FA,iBAAkB,KmBx+FhB,aAAA,KpBk+FH,mBoBh+FO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBi+FH,mBoB99FC,MAAA,KnB0+FA,iBAAkB,QAClB,aAAc,QmBt+FR,oBADJ,oBpBi+FH,mCoB99FG,MAAA,KnB0+FF,iBAAkB,QAClB,aAAc,QmBt+FN,0BnB4+FV,0BAHA,0BmB1+FM,0BnB4+FN,0BAHA,0BDFC,yCoBx+FK,yCnB4+FN,yCmBv+FE,MAAA,KnB++FA,iBAAkB,QAClB,aAAc,QmBx+FZ,oBpBg+FH,oBoBh+FG,mCnB6+FF,iBAAkB,KmBz+FV,4BnB8+FV,4BAHA,4BDHC,6BCOD,6BAHA,6BkB39FA,sCClBM,sCnB8+FN,sCmBx+FI,iBAAA,KACA,aAAA,KDcJ,oBC9DE,MAAA,KACA,iBAAA,KpB0hGD,aoBvhGC,MAAA,KnBmiGA,iBAAkB,QmBjiGhB,aAAA,QpB2hGH,mBoBzhGO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpB0hGH,mBoBvhGC,MAAA,KnBmiGA,iBAAkB,QAClB,aAAc,QmB/hGR,oBADJ,oBpB0hGH,mCoBvhGG,MAAA,KnBmiGF,iBAAkB,QAClB,aAAc,QmB/hGN,0BnBqiGV,0BAHA,0BmBniGM,0BnBqiGN,0BAHA,0BDFC,yCoBjiGK,yCnBqiGN,yCmBhiGE,MAAA,KnBwiGA,iBAAkB,QAClB,aAAc,QmBjiGZ,oBpByhGH,oBoBzhGG,mCnBsiGF,iBAAkB,KmBliGV,4BnBuiGV,4BAHA,4BDHC,6BCOD,6BAHA,6BkBjhGA,sCCrBM,sCnBuiGN,sCmBjiGI,iBAAA,QACA,aAAA,QDkBJ,oBClEE,MAAA,QACA,iBAAA,KpBmlGD,aoBhlGC,MAAA,KnB4lGA,iBAAkB,QmB1lGhB,aAAA,QpBolGH,mBoBllGO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBmlGH,mBoBhlGC,MAAA,KnB4lGA,iBAAkB,QAClB,aAAc,QmBxlGR,oBADJ,oBpBmlGH,mCoBhlGG,MAAA,KnB4lGF,iBAAkB,QAClB,aAAc,QmBxlGN,0BnB8lGV,0BAHA,0BmB5lGM,0BnB8lGN,0BAHA,0BDFC,yCoB1lGK,yCnB8lGN,yCmBzlGE,MAAA,KnBimGA,iBAAkB,QAClB,aAAc,QmB1lGZ,oBpBklGH,oBoBllGG,mCnB+lGF,iBAAkB,KmB3lGV,4BnBgmGV,4BAHA,4BDHC,6BCOD,6BAHA,6BkBtkGA,sCCzBM,sCnBgmGN,sCmB1lGI,iBAAA,QACA,aAAA,QDsBJ,oBCtEE,MAAA,QACA,iBAAA,KpB4oGD,UoBzoGC,MAAA,KnBqpGA,iBAAkB,QmBnpGhB,aAAA,QpB6oGH,gBoB3oGO,gBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpB4oGH,gBoBzoGC,MAAA,KnBqpGA,iBAAkB,QAClB,aAAc,QmBjpGR,iBADJ,iBpB4oGH,gCoBzoGG,MAAA,KnBqpGF,iBAAkB,QAClB,aAAc,QmBjpGN,uBnBupGV,uBAHA,uBmBrpGM,uBnBupGN,uBAHA,uBDFC,sCoBnpGK,sCnBupGN,sCmBlpGE,MAAA,KnB0pGA,iBAAkB,QAClB,aAAc,QmBnpGZ,iBpB2oGH,iBoB3oGG,gCnBwpGF,iBAAkB,KmBppGV,yBnBypGV,yBAHA,yBDHC,0BCOD,0BAHA,0BkB3nGA,mCC7BM,mCnBypGN,mCmBnpGI,iBAAA,QACA,aAAA,QD0BJ,iBC1EE,MAAA,QACA,iBAAA,KpBqsGD,aoBlsGC,MAAA,KnB8sGA,iBAAkB,QmB5sGhB,aAAA,QpBssGH,mBoBpsGO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBqsGH,mBoBlsGC,MAAA,KnB8sGA,iBAAkB,QAClB,aAAc,QmB1sGR,oBADJ,oBpBqsGH,mCoBlsGG,MAAA,KnB8sGF,iBAAkB,QAClB,aAAc,QmB1sGN,0BnBgtGV,0BAHA,0BmB9sGM,0BnBgtGN,0BAHA,0BDFC,yCoB5sGK,yCnBgtGN,yCmB3sGE,MAAA,KnBmtGA,iBAAkB,QAClB,aAAc,QmB5sGZ,oBpBosGH,oBoBpsGG,mCnBitGF,iBAAkB,KmB7sGV,4BnBktGV,4BAHA,4BDHC,6BCOD,6BAHA,6BkBhrGA,sCCjCM,sCnBktGN,sCmB5sGI,iBAAA,QACA,aAAA,QD8BJ,oBC9EE,MAAA,QACA,iBAAA,KpB8vGD,YoB3vGC,MAAA,KnBuwGA,iBAAkB,QmBrwGhB,aAAA,QpB+vGH,kBoB7vGO,kBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpB8vGH,kBoB3vGC,MAAA,KnBuwGA,iBAAkB,QAClB,aAAc,QmBnwGR,mBADJ,mBpB8vGH,kCoB3vGG,MAAA,KnBuwGF,iBAAkB,QAClB,aAAc,QmBnwGN,yBnBywGV,yBAHA,yBmBvwGM,yBnBywGN,yBAHA,yBDFC,wCoBrwGK,wCnBywGN,wCmBpwGE,MAAA,KnB4wGA,iBAAkB,QAClB,aAAc,QmBrwGZ,mBpB6vGH,mBoB7vGG,kCnB0wGF,iBAAkB,KmBtwGV,2BnB2wGV,2BAHA,2BDHC,4BCOD,4BAHA,4BkBruGA,qCCrCM,qCnB2wGN,qCmBrwGI,iBAAA,QACA,aAAA,QDuCJ,mBACE,MAAA,QACA,iBAAA,KnB+tGD,UmB5tGC,YAAA,IlBwuGA,MAAO,QACP,cAAe,EAEjB,UGzwGE,iBemCE,iBflCM,oBJkwGT,6BmB7tGC,iBAAA,YlByuGA,mBAAoB,KACZ,WAAY,KkBtuGlB,UAEF,iBAAA,gBnB6tGD,gBmB3tGG,aAAA,YnBiuGH,gBmB/tGG,gBAIA,MAAA,QlBuuGF,gBAAiB,UACjB,iBAAkB,YDNnB,0BmBhuGK,0BAUN,mCATM,mClB2uGJ,MAAO,KmB1yGP,gBAAA,KAGA,mBADA,QpBmyGD,QAAA,KAAA,KmBztGC,UAAW,KlBquGX,YAAa,UmBjzGb,cAAA,IAGA,mBADA,QpB0yGD,QAAA,IAAA,KmB5tGC,UAAW,KlBwuGX,YAAa,ImBxzGb,cAAA,IAGA,mBADA,QpBizGD,QAAA,IAAA,ImB3tGC,UAAW,KACX,YAAA,IACA,cAAA,IAIF,WACE,QAAA,MnB2tGD,MAAA,KCYD,sBACE,WAAY,IqBz3GZ,6BADF,4BtBk3GC,6BI7rGC,MAAA,KAEQ,MJisGT,QAAA,EsBr3GC,mBAAA,QAAA,KAAA,OACE,cAAA,QAAA,KAAA,OtBu3GH,WAAA,QAAA,KAAA,OsBl3GC,StBq3GD,QAAA,EsBn3Ga,UtBs3Gb,QAAA,KsBr3Ga,atBw3Gb,QAAA,MsBv3Ga,etB03Gb,QAAA,UsBt3GC,kBACA,QAAA,gBlBwKA,YACQ,SAAA,SAAA,OAAA,EAOR,SAAA,OACQ,mCAAA,KAAA,8BAAA,KAGR,2BAAA,KACQ,4BAAA,KAAA,uBAAA,KJ2sGT,oBAAA,KuBr5GC,4BAA6B,OAAQ,WACrC,uBAAA,OAAA,WACA,oBAAA,OAAA,WAEA,OACA,QAAA,aACA,MAAA,EACA,OAAA,EACA,YAAA,IACA,eAAA,OvBu5GD,WAAA,IAAA,OuBn5GC,WAAY,IAAI,QtBk6GhB,aAAc,IAAI,MAAM,YsBh6GxB,YAAA,IAAA,MAAA,YAKA,UADF,QvBo5GC,SAAA,SuB94GC,uBACA,QAAA,EAEA,eACA,SAAA,SACA,IAAA,KACA,KAAA,EACA,QAAA,KACA,QAAA,KACA,MAAA,KACA,UAAA,MACA,QAAA,IAAA,EACA,OAAA,IAAA,EAAA,EACA,UAAA,KACA,WAAA,KACA,WAAA,KnBsBA,iBAAA,KACQ,wBAAA,YmBrBR,gBAAA,YtB+5GA,OsB/5GA,IAAA,MAAA,KvBk5GD,OAAA,IAAA,MAAA,gBuB74GC,cAAA,IACE,mBAAA,EAAA,IAAA,KAAA,iBACA,WAAA,EAAA,IAAA,KAAA,iBAzBJ,0BCzBE,MAAA,EACA,KAAA,KAEA,wBxBo8GD,OAAA,IuB96GC,OAAQ,IAAI,EAmCV,SAAA,OACA,iBAAA,QAEA,oBACA,QAAA,MACA,QAAA,IAAA,KACA,MAAA,KvB84GH,YAAA,IuBx4GC,YAAA,WtBw5GA,MAAO,KsBt5GL,YAAA,OvB44GH,0BuB14GG,0BAMF,MAAA,QtBo5GA,gBAAiB,KACjB,iBAAkB,QsBj5GhB,yBAEA,+BADA,+BvBu4GH,MAAA,KuB73GC,gBAAA,KtB64GA,iBAAkB,QAClB,QAAS,EDZV,2BuB33GC,iCAAA,iCAEE,MAAA,KEzGF,iCF2GE,iCAEA,gBAAA,KvB63GH,OAAA,YuBx3GC,iBAAkB,YAGhB,iBAAA,KvBw3GH,OAAA,0DuBn3GG,qBvBs3GH,QAAA,MuB72GC,QACA,QAAA,EAQF,qBACE,MAAA,EACA,KAAA,KAIF,oBACE,MAAA,KACA,KAAA,EAEA,iBACA,QAAA,MACA,QAAA,IAAA,KvBw2GD,UAAA,KuBp2GC,YAAa,WACb,MAAA,KACA,YAAA,OAEA,mBACA,SAAA,MACA,IAAA,EvBs2GD,MAAA,EuBl2GC,OAAQ,EACR,KAAA,EACA,QAAA,IAQF,2BtB42GE,MAAO,EsBx2GL,KAAA,KAEA,eACA,sCvB41GH,QAAA,GuBn2GC,WAAY,EtBm3GZ,cAAe,IAAI,OsBx2GjB,cAAA,IAAA,QAEA,uBvB41GH,8CuBv0GC,IAAK,KAXL,OAAA,KApEA,cAAA,IvB25GC,yBuBv1GD,6BA1DA,MAAA,EACA,KAAA,KvBq5GD,kC0BpiHG,MAAO,KzBojHP,KAAM,GyBhjHR,W1BsiHD,oB0B1iHC,SAAU,SzB0jHV,QAAS,ayBpjHP,eAAA,OAGA,yB1BsiHH,gBCgBC,SAAU,SACV,MAAO,KyB7iHT,gC1BsiHC,gCCYD,+BAFA,+ByBhjHA,uBANM,uBzBujHN,sBAFA,sBAQE,QAAS,EyBljHP,qB1BuiHH,2B0BliHD,2BACE,iC1BoiHD,YAAA,KCgBD,aACE,YAAa,KDZd,kB0B1iHD,wBAAA,0BzB2jHE,MAAO,KDZR,kB0B/hHD,wBACE,0B1BiiHD,YAAA,I0B5hHC,yE1B+hHD,cAAA,E2BhlHC,4BACG,YAAA,EDsDL,mEzB6iHE,wBAAyB,E0B5lHzB,2BAAA,E3BilHD,6C0B5hHD,8CACE,uBAAA,E1B8hHD,0BAAA,E0B3hHC,sB1B8hHD,MAAA,KCgBD,8D0B/mHE,cAAA,E3BomHD,mE0B3hHD,oECjEE,wBAAA,EACG,2BAAA,EDqEL,oEzB0iHE,uBAAwB,EyBxiHxB,0BAAA,EAiBF,mCACE,iCACA,QAAA,EAEF,iCACE,cAAA,IACA,aAAA,IAKF,oCtB/CE,cAAA,KACQ,aAAA,KsBkDR,iCtBnDA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBsByDV,0CACE,mBAAA,K1BugHD,WAAA,K0BngHC,YACA,YAAA,EAGF,eACE,aAAA,IAAA,IAAA,E1BqgHD,oBAAA,ECgBD,uBACE,aAAc,EAAE,IAAI,IyB1gHlB,yBACA,+BACA,oC1B+/GH,QAAA,M0BtgHC,MAAO,KAcH,MAAA,K1B2/GL,UAAA,KCgBD,oCACE,MAAO,KyBpgHL,8BACA,oC1By/GH,oC0Bp/GC,0CACE,WAAA,K1Bs/GH,YAAA,E2B/pHC,4DACC,cAAA,EAQA,sD3B4pHF,uBAAA,I0Bt/GC,wBAAA,IC/KA,2BAAA,EACC,0BAAA,EAQA,sD3BkqHF,uBAAA,E0Bv/GC,wBAAyB,EACzB,2BAAA,I1By/GD,0BAAA,ICgBD,uE0BtrHE,cAAA,E3B2qHD,4E0Bt/GD,6EC7LE,2BAAA,EACC,0BAAA,EDoMH,6EACE,uBAAA,EACA,wBAAA,EAEA,qB1Bo/GD,QAAA,M0Bx/GC,MAAO,KzBwgHP,aAAc,MyBjgHZ,gBAAA,SAEA,0B1Bq/GH,gC0B9/GC,QAAS,WAYP,MAAA,K1Bq/GH,MAAA,G0Bj/GG,qC1Bo/GH,MAAA,KCgBD,+CACE,KAAM,KyB7+GF,gDAFA,6C1Bs+GL,2D0Br+GK,wDEzOJ,SAAU,SACV,KAAA,cACA,eAAA,K5BitHD,a4B7sHC,SAAA,SACE,QAAA,MACA,gBAAA,S5BgtHH,0B4BxtHC,MAAO,KAeL,cAAA,EACA,aAAA,EAOA,2BACA,SAAA,S5BusHH,QAAA,E4BrsHG,MAAA,KACE,MAAA,K5BusHL,cAAA,ECgBD,iCACE,QAAS,EiBnrHT,8BACA,mCACA,sCACA,OAAA,KlBwqHD,QAAA,KAAA,KkBtqHC,UAAA,KjBsrHA,YAAa,UACb,cAAe,IiBrrHb,oClB0qHH,yCkBvqHC,4CjBurHA,OAAQ,KACR,YAAa,KDTd,8C4B/sHD,mDAAA,sD3B0tHA,sCACA,2CiBzrHI,8CjB8rHF,OAAQ,KiB1sHR,8BACA,mCACA,sCACA,OAAA,KlB+rHD,QAAA,IAAA,KkB7rHC,UAAA,KjB6sHA,YAAa,IACb,cAAe,IiB5sHb,oClBisHH,yCkB9rHC,4CjB8sHA,OAAQ,KACR,YAAa,KDTd,8C4B7tHD,mDAAA,sD3BwuHA,sCACA,2CiBhtHI,8CjBqtHF,OAAQ,K2BzuHR,2B5B6tHD,mB4B7tHC,iB3B8uHA,QAAS,W2BzuHX,8D5B6tHC,sD4B7tHD,oDAEE,cAAA,EAEA,mB5B+tHD,iB4B1tHC,MAAO,GACP,YAAA,OACA,eAAA,OAEA,mBACA,QAAA,IAAA,KACA,UAAA,KACA,YAAA,IACA,YAAA,EACA,MAAA,K5B4tHD,WAAA,O4BztHC,iBAAA,KACE,OAAA,IAAA,MAAA,KACA,cAAA,I5B4tHH,4B4BztHC,QAAA,IAAA,KACE,UAAA,KACA,cAAA,I5B4tHH,4B4B/uHC,QAAS,KAAK,K3B+vHd,UAAW,K2BruHT,cAAA,IAKJ,wCAAA,qC3BquHE,WAAY,EAEd,uCACA,+BACA,kC0B70HE,6CACG,8CC4GL,6D5BqtHC,wE4BptHC,wBAAA,E5ButHD,2BAAA,ECgBD,+BACE,aAAc,EAEhB,sCACA,8B2BhuHA,+D5BstHC,oDCWD,iC0Bl1HE,4CACG,6CCiHH,uBAAA,E5BwtHD,0BAAA,E4BltHC,8BAGA,YAAA,E5BotHD,iB4BxtHC,SAAU,SAUR,UAAA,E5BitHH,YAAA,O4B/sHK,sB5BktHL,SAAA,SCgBD,2BACE,YAAa,K2BxtHb,6BAAA,4B5B4sHD,4B4BzsHK,QAAA,EAGJ,kCAAA,wCAGI,aAAA,K5B4sHL,iC6B12HD,uCACE,QAAA,EACA,YAAA,K7B62HD,K6B/2HC,aAAc,EAOZ,cAAA,EACA,WAAA,KARJ,QAWM,SAAA,SACA,QAAA,M7B42HL,U6B12HK,SAAA,S5B03HJ,QAAS,M4Bx3HH,QAAA,KAAA,KAMJ,gB7Bu2HH,gB6Bt2HK,gBAAA,K7By2HL,iBAAA,KCgBD,mB4Br3HQ,MAAA,KAGA,yBADA,yB7B02HP,MAAA,K6Bl2HG,gBAAA,K5Bk3HF,OAAQ,YACR,iBAAkB,Y4B/2Hd,aAzCN,mB7B64HC,mBwBh5HC,iBAAA,KACA,aAAA,QAEA,kBxBm5HD,OAAA,I6Bn5HC,OAAQ,IAAI,EA0DV,SAAA,O7B41HH,iBAAA,Q6Bl1HC,c7Bq1HD,UAAA,K6Bn1HG,UAEA,cAAA,IAAA,MAAA,KALJ,aASM,MAAA,KACA,cAAA,KAEA,e7Bo1HL,aAAA,I6Bn1HK,YAAA,WACE,OAAA,IAAA,MAAA,Y7Bq1HP,cAAA,IAAA,IAAA,EAAA,ECgBD,qBACE,aAAc,KAAK,KAAK,K4B51HlB,sBAEA,4BADA,4BAEA,MAAA,K7Bi1HP,OAAA,Q6B50HC,iBAAA,KAqDA,OAAA,IAAA,MAAA,KA8BA,oBAAA,YAnFA,wBAwDE,MAAA,K7B2xHH,cAAA,E6BzxHK,2BACA,MAAA,KA3DJ,6BAgEE,cAAA,IACA,WAAA,OAYJ,iDA0DE,IAAK,KAjED,KAAA,K7B0xHH,yB6BztHD,2BA9DM,QAAA,W7B0xHL,MAAA,G6Bn2HD,6BAuFE,cAAA,GAvFF,6B5Bw3HA,aAAc,EACd,cAAe,IDZhB,kC6BtuHD,wCA3BA,wCATM,OAAA,IAAA,MAAA,K7B+wHH,yB6B3uHD,6B5B2vHE,cAAe,IAAI,MAAM,KACzB,cAAe,IAAI,IAAI,EAAE,EDZ1B,kC6B92HD,wC7B+2HD,wC6B72HG,oBAAA,MAIE,c7B+2HL,MAAA,K6B52HK,gB7B+2HL,cAAA,ICgBD,iBACE,YAAa,I4Bv3HP,uBAQR,6B7Bo2HC,6B6Bl2HG,MAAA,K7Bq2HH,iBAAA,Q6Bn2HK,gBACA,MAAA,KAYN,mBACE,WAAA,I7B41HD,YAAA,E6Bz1HG,e7B41HH,MAAA,K6B11HK,kBACA,MAAA,KAPN,oBAYI,cAAA,IACA,WAAA,OAYJ,wCA0DE,IAAK,KAjED,KAAA,K7B21HH,yB6B1xHD,kBA9DM,QAAA,W7B21HL,MAAA,G6Bl1HD,oBACA,cAAA,GAIE,oBACA,cAAA,EANJ,yB5B02HE,aAAc,EACd,cAAe,IDZhB,8B6B1yHD,oCA3BA,oCATM,OAAA,IAAA,MAAA,K7Bm1HH,yB6B/yHD,yB5B+zHE,cAAe,IAAI,MAAM,KACzB,cAAe,IAAI,IAAI,EAAE,EDZ1B,8B6Bx0HD,oC7By0HD,oC6Bv0HG,oBAAA,MAGA,uB7B00HH,QAAA,K6B/zHC,qBF3OA,QAAA,M3B+iID,yB8BxiIC,WAAY,KACZ,uBAAA,EACA,wBAAA,EAEA,Q9B0iID,SAAA,S8BliIC,WAAY,KA8nBZ,cAAe,KAhoBb,OAAA,IAAA,MAAA,Y9ByiIH,yB8BzhIC,QAgnBE,cAAe,K9B86GlB,yB8BjhIC,eACA,MAAA,MAGA,iBACA,cAAA,KAAA,aAAA,KAEA,WAAA,Q9BkhID,2BAAA,M8BhhIC,WAAA,IAAA,MAAA,YACE,mBAAA,MAAA,EAAA,IAAA,EAAA,qB9BkhIH,WAAA,MAAA,EAAA,IAAA,EAAA,qB8Bz7GD,oBArlBI,WAAA,KAEA,yBAAA,iB9BkhID,MAAA,K8BhhIC,WAAA,EACE,mBAAA,KACA,WAAA,KAEA,0B9BkhIH,QAAA,gB8B/gIC,OAAA,eACE,eAAA,E9BihIH,SAAA,kBCkBD,oBACE,WAAY,QDZf,sC8B/gIK,mC9B8gIH,oC8BzgIC,cAAe,E7B4hIf,aAAc,G6Bj+GlB,sCAnjBE,mC7ByhIA,WAAY,MDdX,4D8BngID,sC9BogID,mCCkBG,WAAY,O6B3gId,kCANE,gC9BsgIH,4B8BvgIG,0BAuiBF,aAAc,M7Bm/Gd,YAAa,MAEf,yBDZC,kC8B3gIK,gC9B0gIH,4B8B3gIG,0BAcF,aAAc,EAChB,YAAA,GAMF,mBA8gBE,QAAS,KAhhBP,aAAA,EAAA,EAAA,I9BkgIH,yB8B7/HC,mB7B+gIE,cAAe,G6B1gIjB,qBADA,kB9BggID,SAAA,M8Bz/HC,MAAO,EAggBP,KAAM,E7B4gHN,QAAS,KDdR,yB8B7/HD,qB9B8/HD,kB8B7/HC,cAAA,GAGF,kBACE,IAAA,EACA,aAAA,EAAA,EAAA,I9BigID,qB8B1/HC,OAAQ,EACR,cAAA,EACA,aAAA,IAAA,EAAA,EAEA,cACA,MAAA,K9B4/HD,OAAA,K8B1/HC,QAAA,KAAA,K7B4gIA,UAAW,K6B1gIT,YAAA,KAIA,oBAbJ,oB9BwgIC,gBAAA,K8Bv/HG,kB7B0gIF,QAAS,MDdR,yBACF,iC8Bh/HC,uCACA,YAAA,OAGA,eC9LA,SAAA,SACA,MAAA,MD+LA,QAAA,IAAA,KACA,WAAA,IACA,aAAA,KACA,cAAA,I9Bm/HD,iBAAA,Y8B/+HC,iBAAA,KACE,OAAA,IAAA,MAAA,Y9Bi/HH,cAAA,I8B5+HG,qBACA,QAAA,EAEA,yB9B++HH,QAAA,M8BrgIC,MAAO,KAyBL,OAAA,I9B++HH,cAAA,I8BpjHD,mCAvbI,WAAA,I9Bg/HH,yB8Bt+HC,eACA,QAAA,MAGE,YACA,OAAA,MAAA,M9By+HH,iB8B58HC,YAAA,KA2YA,eAAgB,KAjaZ,YAAA,KAEA,yBACA,iCACA,SAAA,OACA,MAAA,KACA,MAAA,KAAA,WAAA,E9Bs+HH,iBAAA,Y8B3kHC,OAAQ,E7B8lHR,mBAAoB,K6Bt/HhB,WAAA,KAGA,kDAqZN,sC9BklHC,QAAA,IAAA,KAAA,IAAA,KCmBD,sC6Bv/HQ,YAAA,KAmBR,4C9Bs9HD,4C8BvlHG,iBAAkB,M9B4lHnB,yB8B5lHD,YAtYI,MAAA,K9Bq+HH,OAAA,E8Bn+HK,eACA,MAAA,K9Bu+HP,iB8B39HG,YAAa,KACf,eAAA,MAGA,aACA,QAAA,KAAA,K1B9NA,WAAA,IACQ,aAAA,M2B/DR,cAAA,IACA,YAAA,M/B4vID,WAAA,IAAA,MAAA,YiBtuHC,cAAe,IAAI,MAAM,YAwEzB,mBAAoB,MAAM,EAAE,IAAI,EAAE,qBAAyB,EAAE,IAAI,EAAE,qBAtI/D,WAAA,MAAA,EAAA,IAAA,EAAA,qBAAA,EAAA,IAAA,EAAA,qBAEA,yBjBwyHH,yBiBpqHC,QAAS,aA/HP,cAAA,EACA,eAAA,OjBuyHH,2BiBzqHC,QAAS,aAxHP,MAAA,KjBoyHH,eAAA,OiBhyHG,kCACA,QAAA,aAmHJ,0BhBmsHE,QAAS,aACT,eAAgB,OgB5yHd,wCjB6xHH,6CiBrrHD,2CjBwrHC,MAAA,KiB5xHG,wCACA,MAAA,KAmGJ,4BhB+sHE,cAAe,EgB3yHb,eAAA,OAGA,uBADA,oBjB6xHH,QAAA,aiBnsHC,WAAY,EhBstHZ,cAAe,EgB5yHX,eAAA,OAsFN,6BAAA,0BAjFI,aAAA,EAiFJ,4CjB4sHC,sCiBvxHG,SAAA,SjB0xHH,YAAA,E8BngID,kDAmWE,IAAK,GAvWH,yBACE,yB9B8gIL,cAAA,I8B5/HD,oCAoVE,cAAe,GA1Vf,yBACA,aACA,MAAA,KACA,YAAA,E1BzPF,eAAA,EACQ,aAAA,EJmwIP,YAAA,EACF,OAAA,E8BngIG,mBAAoB,KACtB,WAAA,M9BugID,8B8BngIC,WAAY,EACZ,uBAAA,EHzUA,wBAAA,EAQA,mDACC,cAAA,E3By0IF,uBAAA,I8B//HC,wBAAyB,IChVzB,2BAAA,EACA,0BAAA,EDkVA,YCnVA,WAAA,IACA,cAAA,IDqVA,mBCtVA,WAAA,KACA,cAAA,KD+VF,mBChWE,WAAA,KACA,cAAA,KDuWF,aAsSE,WAAY,KA1SV,cAAA,KAEA,yB9B+/HD,aACF,MAAA,K8Bl+HG,aAAc,KAhBhB,YAAA,MACA,yBE5WA,aF8WE,MAAA,eAFF,cAKI,MAAA,gB9Bu/HH,aAAA,M8B7+HD,4BACA,aAAA,GADF,gBAKI,iBAAA,Q9Bg/HH,aAAA,QCmBD,8B6BhgIM,MAAA,KARN,oC9B0/HC,oC8B5+HG,MAAA,Q9B++HH,iBAAA,Y8B1+HK,6B9B6+HL,MAAA,KCmBD,iC6B5/HQ,MAAA,KAKF,uC9By+HL,uCCmBC,MAAO,KACP,iBAAkB,Y6Bz/HZ,sCAIF,4C9Bu+HL,4CCmBC,MAAO,KACP,iBAAkB,Q6Bv/HZ,wCAxCR,8C9BihIC,8C8Bn+HG,MAAA,K9Bs+HH,iBAAA,YCmBD,+B6Bt/HM,aAAA,KAGA,qCApDN,qC9B2hIC,iBAAA,KCmBD,yC6Bp/HI,iBAAA,KAOE,iCAAA,6B7Bk/HJ,aAAc,Q6B9+HR,oCAiCN,0C9B+7HD,0C8B3xHC,MAAO,KA7LC,iBAAA,QACA,yB7B8+HR,sD6B5+HU,MAAA,KAKF,4D9By9HP,4DCmBC,MAAO,KACP,iBAAkB,Y6Bz+HV,2DAIF,iE9Bu9HP,iECmBC,MAAO,KACP,iBAAkB,Q6Bv+HV,6D9B09HX,mEADE,mE8B1jIC,MAAO,KA8GP,iBAAA,aAEE,6B9Bi9HL,MAAA,K8B58HG,mC9B+8HH,MAAA,KCmBD,0B6B/9HM,MAAA,KAIA,gCAAA,gC7Bg+HJ,MAAO,K6Bt9HT,0CARQ,0CASN,mD9Bu8HD,mD8Bt8HC,MAAA,KAFF,gBAKI,iBAAA,K9B08HH,aAAA,QCmBD,8B6B19HM,MAAA,QARN,oC9Bo9HC,oC8Bt8HG,MAAA,K9By8HH,iBAAA,Y8Bp8HK,6B9Bu8HL,MAAA,QCmBD,iC6Bt9HQ,MAAA,QAKF,uC9Bm8HL,uCCmBC,MAAO,KACP,iBAAkB,Y6Bn9HZ,sCAIF,4C9Bi8HL,4CCmBC,MAAO,KACP,iBAAkB,Q6Bj9HZ,wCAxCR,8C9B2+HC,8C8B57HG,MAAA,K9B+7HH,iBAAA,YCmBD,+B6B/8HM,aAAA,KAGA,qCArDN,qC9Bq/HC,iBAAA,KCmBD,yC6B78HI,iBAAA,KAME,iCAAA,6B7B48HJ,aAAc,Q6Bx8HR,oCAuCN,0C9Bm5HD,0C8B33HC,MAAO,KAvDC,iBAAA,QAuDV,yBApDU,kE9Bs7HP,aAAA,Q8Bn7HO,0D9Bs7HP,iBAAA,QCmBD,sD6Bt8HU,MAAA,QAKF,4D9Bm7HP,4DCmBC,MAAO,KACP,iBAAkB,Y6Bn8HV,2DAIF,iE9Bi7HP,iECmBC,MAAO,KACP,iBAAkB,Q6Bj8HV,6D9Bo7HX,mEADE,mE8B1hIC,MAAO,KA+GP,iBAAA,aAEE,6B9Bg7HL,MAAA,Q8B36HG,mC9B86HH,MAAA,KCmBD,0B6B97HM,MAAA,QAIA,gCAAA,gC7B+7HJ,MAAO,KgCvkJT,0CH0oBQ,0CGzoBN,mDjCwjJD,mDiCvjJC,MAAA,KAEA,YACA,QAAA,IAAA,KjC2jJD,cAAA,KiChkJC,WAAY,KAQV,iBAAA,QjC2jJH,cAAA,IiCxjJK,eACA,QAAA,ajC4jJL,yBiCxkJC,QAAS,EAAE,IAkBT,MAAA,KjCyjJH,QAAA,SkC5kJC,oBACA,MAAA,KAEA,YlC+kJD,QAAA,akCnlJC,aAAc,EAOZ,OAAA,KAAA,ElC+kJH,cAAA,ICmBD,eiC/lJM,QAAA,OAEA,iBACA,oBACA,SAAA,SACA,MAAA,KACA,QAAA,IAAA,KACA,YAAA,KACA,YAAA,WlCglJL,MAAA,QkC9kJG,gBAAA,KjCimJF,iBAAkB,KiC9lJZ,OAAA,IAAA,MAAA,KPVH,6B3B2lJJ,gCkC7kJG,YAAA,EjCgmJF,uBAAwB,I0BvnJxB,0BAAA,I3BymJD,4BkCxkJG,+BjC2lJF,wBAAyB,IACzB,2BAA4B,IiCxlJxB,uBAFA,uBAGA,0BAFA,0BlC8kJL,QAAA,EkCtkJG,MAAA,QjCylJF,iBAAkB,KAClB,aAAc,KAEhB,sBiCvlJM,4BAFA,4BjC0lJN,yBiCvlJM,+BAFA,+BAGA,QAAA,ElC2kJL,MAAA,KkCloJC,OAAQ,QjCqpJR,iBAAkB,QAClB,aAAc,QiCnlJV,wBAEA,8BADA,8BjColJN,2BiCtlJM,iCjCulJN,iCDZC,MAAA,KkC/jJC,OAAQ,YjCklJR,iBAAkB,KkC7pJd,aAAA,KAEA,oBnC8oJL,uBmC5oJG,QAAA,KAAA,KlC+pJF,UAAW,K0B1pJX,YAAA,U3B4oJD,gCmC3oJG,mClC8pJF,uBAAwB,I0BvqJxB,0BAAA,I3BypJD,+BkC1kJD,kCjC6lJE,wBAAyB,IkC7qJrB,2BAAA,IAEA,oBnC8pJL,uBmC5pJG,QAAA,IAAA,KlC+qJF,UAAW,K0B1qJX,YAAA,I3B4pJD,gCmC3pJG,mClC8qJF,uBAAwB,I0BvrJxB,0BAAA,I3ByqJD,+BoC3qJD,kCACE,wBAAA,IACA,2BAAA,IAEA,OpC6qJD,aAAA,EoCjrJC,OAAQ,KAAK,EAOX,WAAA,OpC6qJH,WAAA,KCmBD,UmC7rJM,QAAA,OAEA,YACA,eACA,QAAA,apC8qJL,QAAA,IAAA,KoC5rJC,iBAAkB,KnC+sJlB,OAAQ,IAAI,MAAM,KmC5rJd,cAAA,KAnBN,kBpCisJC,kBCmBC,gBAAiB,KmCzrJb,iBAAA,KA3BN,eAAA,kBAkCM,MAAA,MAlCN,mBAAA,sBnC6tJE,MAAO,KmClrJH,mBAEA,yBADA,yBpCqqJL,sBqCltJC,MAAO,KACP,OAAA,YACA,iBAAA,KAEA,OACA,QAAA,OACA,QAAA,KAAA,KAAA,KACA,UAAA,IACA,YAAA,IACA,YAAA,EACA,MAAA,KrCotJD,WAAA,OqChtJG,YAAA,OpCmuJF,eAAgB,SoCjuJZ,cAAA,MrCotJL,cqCltJK,cAKJ,MAAA,KACE,gBAAA,KrC+sJH,OAAA,QqC1sJG,aACA,QAAA,KAOJ,YCtCE,SAAA,StC+uJD,IAAA,KCmBD,eqC7vJM,iBAAA,KALJ,2BD0CF,2BrC4sJC,iBAAA,QCmBD,eqCpwJM,iBAAA,QALJ,2BD8CF,2BrC+sJC,iBAAA,QCmBD,eqC3wJM,iBAAA,QALJ,2BDkDF,2BrCktJC,iBAAA,QCmBD,YqClxJM,iBAAA,QALJ,wBDsDF,wBrCqtJC,iBAAA,QCmBD,eqCzxJM,iBAAA,QALJ,2BD0DF,2BrCwtJC,iBAAA,QCmBD,cqChyJM,iBAAA,QCDJ,0BADF,0BAEE,iBAAA,QAEA,OACA,QAAA,aACA,UAAA,KACA,QAAA,IAAA,IACA,UAAA,KACA,YAAA,IACA,YAAA,EACA,MAAA,KACA,WAAA,OvCqxJD,YAAA,OuClxJC,eAAA,OACE,iBAAA,KvCoxJH,cAAA,KuC/wJG,aACA,QAAA,KAGF,YtCkyJA,SAAU,SsChyJR,IAAA,KAMA,0BvC4wJH,eCmBC,IAAK,EsC7xJD,QAAA,IAAA,IvCgxJL,cuC9wJK,cAKJ,MAAA,KtC4xJA,gBAAiB,KsC1xJf,OAAA,QvC4wJH,+BuCxwJC,4BACE,MAAA,QvC0wJH,iBAAA,KuCtwJG,wBvCywJH,MAAA,MuCrwJG,+BvCwwJH,aAAA,IwCj0JC,uBACA,YAAA,IAEA,WACA,YAAA,KxCo0JD,eAAA,KwCz0JC,cAAe,KvC41Jf,MAAO,QuCn1JL,iBAAA,KAIA,eAbJ,cAcI,MAAA,QxCo0JH,awCl1JC,cAAe,KAmBb,UAAA,KxCk0JH,YAAA,ICmBD,cuCh1JI,iBAAA,QAEA,sBxCi0JH,4BwC31JC,cAAe,KA8Bb,aAAA,KxCg0JH,cAAA,IwC7yJD,sBAfI,UAAA,KxCi0JD,oCwC9zJC,WvCi1JA,YAAa,KuC/0JX,eAAA,KxCi0JH,sBwCvzJD,4BvC00JE,cAAe,KuC90Jb,aAAA,KC5CJ,ezC42JD,cyC32JC,UAAA,MAGA,WACA,QAAA,MACA,QAAA,IACA,cAAA,KrCiLA,YAAA,WACK,iBAAA,KACG,OAAA,IAAA,MAAA,KJ8rJT,cAAA,IyCx3JC,mBAAoB,OAAO,IAAI,YxC24J1B,cAAe,OAAO,IAAI,YwC93J7B,WAAA,OAAA,IAAA,YAKF,iBzC22JD,eCmBC,aAAc,KACd,YAAa,KwCv3JX,mBA1BJ,kBzCk4JC,kByCv2JG,aAAA,QCzBJ,oBACE,QAAA,IACA,MAAA,KAEA,O1Cs4JD,QAAA,K0C14JC,cAAe,KAQb,OAAA,IAAA,MAAA,YAEA,cAAA,IAVJ,UAeI,WAAA,E1Ck4JH,MAAA,QCmBD,mByC/4JI,YAAA,IArBJ,SAyBI,U1C+3JH,cAAA,ECmBD,WyCx4JE,WAAA,IAFF,mBAAA,mBAMI,cAAA,KAEA,0BACA,0B1Cy3JH,SAAA,S0Cj3JC,IAAK,KCvDL,MAAA,MACA,MAAA,Q3C46JD,e0Ct3JC,MAAO,QClDL,iBAAA,Q3C26JH,aAAA,Q2Cx6JG,kB3C26JH,iBAAA,Q2Cn7JC,2BACA,MAAA,Q3Cu7JD,Y0C73JC,MAAO,QCtDL,iBAAA,Q3Cs7JH,aAAA,Q2Cn7JG,e3Cs7JH,iBAAA,Q2C97JC,wBACA,MAAA,Q3Ck8JD,e0Cp4JC,MAAO,QC1DL,iBAAA,Q3Ci8JH,aAAA,Q2C97JG,kB3Ci8JH,iBAAA,Q2Cz8JC,2BACA,MAAA,Q3C68JD,c0C34JC,MAAO,QC9DL,iBAAA,Q3C48JH,aAAA,Q2Cz8JG,iB3C48JH,iBAAA,Q4C78JC,0BAAQ,MAAA,QACR,wCAAQ,K5Cm9JP,oBAAA,KAAA,E4C/8JD,GACA,oBAAA,EAAA,GACA,mCAAQ,K5Cq9JP,oBAAA,KAAA,E4Cv9JD,GACA,oBAAA,EAAA,GACA,gCAAQ,K5Cq9JP,oBAAA,KAAA,E4C78JD,GACA,oBAAA,EAAA,GAGA,UACA,OAAA,KxCsCA,cAAA,KACQ,SAAA,OJ26JT,iBAAA,Q4C78JC,cAAe,IACf,mBAAA,MAAA,EAAA,IAAA,IAAA,eACA,WAAA,MAAA,EAAA,IAAA,IAAA,eAEA,cACA,MAAA,KACA,MAAA,EACA,OAAA,KACA,UAAA,KxCyBA,YAAA,KACQ,MAAA,KAyHR,WAAA,OACK,iBAAA,QACG,mBAAA,MAAA,EAAA,KAAA,EAAA,gBJ+zJT,WAAA,MAAA,EAAA,KAAA,EAAA,gB4C18JC,mBAAoB,MAAM,IAAI,K3Cq+JzB,cAAe,MAAM,IAAI,K4Cp+J5B,WAAA,MAAA,IAAA,KDEF,sBCAE,gCDAF,iBAAA,yK5C88JD,iBAAA,oK4Cv8JC,iBAAiB,iK3Cm+JjB,wBAAyB,KAAK,KG/gK9B,gBAAA,KAAA,KJy/JD,qBIv/JS,+BwCmDR,kBAAmB,qBAAqB,GAAG,OAAO,SErElD,aAAA,qBAAA,GAAA,OAAA,S9C4gKD,UAAA,qBAAA,GAAA,OAAA,S6Cz9JG,sBACA,iBAAA,Q7C69JH,wC4Cx8JC,iBAAkB,yKEzElB,iBAAA,oK9CohKD,iBAAA,iK6Cj+JG,mBACA,iBAAA,Q7Cq+JH,qC4C58JC,iBAAkB,yKE7ElB,iBAAA,oK9C4hKD,iBAAA,iK6Cz+JG,sBACA,iBAAA,Q7C6+JH,wC4Ch9JC,iBAAkB,yKEjFlB,iBAAA,oK9CoiKD,iBAAA,iK6Cj/JG,qBACA,iBAAA,Q7Cq/JH,uC+C5iKC,iBAAkB,yKAElB,iBAAA,oK/C6iKD,iBAAA,iK+C1iKG,O/C6iKH,WAAA,KC4BD,mB8CnkKE,WAAA,E/C4iKD,O+CxiKD,YACE,SAAA,O/C0iKD,KAAA,E+CtiKC,Y/CyiKD,MAAA,Q+CriKG,c/CwiKH,QAAA,MC4BD,4B8C9jKE,UAAA,KAGF,aAAA,mBAEE,aAAA,KAGF,YAAA,kB9C+jKE,cAAe,K8CxjKjB,YAHE,Y/CoiKD,a+ChiKC,QAAA,W/CmiKD,eAAA,I+C/hKC,c/CkiKD,eAAA,O+C7hKC,cACA,eAAA,OAMF,eACE,WAAA,EACA,cAAA,ICvDF,YAEE,aAAA,EACA,WAAA,KAQF,YACE,aAAA,EACA,cAAA,KAGA,iBACA,SAAA,SACA,QAAA,MhD6kKD,QAAA,KAAA,KgD1kKC,cAAA,KrB3BA,iBAAA,KACC,OAAA,IAAA,MAAA,KqB6BD,6BACE,uBAAA,IrBvBF,wBAAA,I3BsmKD,4BgDpkKC,cAAe,E/CgmKf,2BAA4B,I+C9lK5B,0BAAA,IAFF,kBAAA,uBAKI,MAAA,KAIF,2CAAA,gD/CgmKA,MAAO,K+C5lKL,wBAFA,wBhDykKH,6BgDxkKG,6BAKF,MAAO,KACP,gBAAA,KACA,iBAAA,QAKA,uB/C4lKA,MAAO,KACP,WAAY,K+CzlKV,0BhDmkKH,gCgDlkKG,gCALF,MAAA,K/CmmKA,OAAQ,YACR,iBAAkB,KDxBnB,mDgD5kKC,yDAAA,yD/CymKA,MAAO,QDxBR,gDgDhkKC,sDAAA,sD/C6lKA,MAAO,K+CzlKL,wBAEA,8BADA,8BhDmkKH,QAAA,EgDxkKC,MAAA,K/ComKA,iBAAkB,QAClB,aAAc,QAEhB,iDDpBC,wDCuBD,uDADA,uD+CzmKE,8DAYI,6D/C4lKN,uD+CxmKE,8D/C2mKF,6DAKE,MAAO,QDxBR,8CiD1qKG,oDADF,oDAEE,MAAA,QAEA,yBhDusKF,MAAO,QgDrsKH,iBAAA,QAFF,0BAAA,+BAKI,MAAA,QAGF,mDAAA,wDhDwsKJ,MAAO,QDtBR,gCiDhrKO,gCAGF,qCAFE,qChD2sKN,MAAO,QACP,iBAAkB,QAEpB,iCgDvsKQ,uCAFA,uChD0sKR,sCDtBC,4CiDnrKO,4CArBN,MAAA,KACE,iBAAA,QACA,aAAA,QAEA,sBhDouKF,MAAO,QgDluKH,iBAAA,QAFF,uBAAA,4BAKI,MAAA,QAGF,gDAAA,qDhDquKJ,MAAO,QDtBR,6BiD7sKO,6BAGF,kCAFE,kChDwuKN,MAAO,QACP,iBAAkB,QAEpB,8BgDpuKQ,oCAFA,oChDuuKR,mCDtBC,yCiDhtKO,yCArBN,MAAA,KACE,iBAAA,QACA,aAAA,QAEA,yBhDiwKF,MAAO,QgD/vKH,iBAAA,QAFF,0BAAA,+BAKI,MAAA,QAGF,mDAAA,wDhDkwKJ,MAAO,QDtBR,gCiD1uKO,gCAGF,qCAFE,qChDqwKN,MAAO,QACP,iBAAkB,QAEpB,iCgDjwKQ,uCAFA,uChDowKR,sCDtBC,4CiD7uKO,4CArBN,MAAA,KACE,iBAAA,QACA,aAAA,QAEA,wBhD8xKF,MAAO,QgD5xKH,iBAAA,QAFF,yBAAA,8BAKI,MAAA,QAGF,kDAAA,uDhD+xKJ,MAAO,QDtBR,+BiDvwKO,+BAGF,oCAFE,oChDkyKN,MAAO,QACP,iBAAkB,QAEpB,gCgD9xKQ,sCAFA,sChDiyKR,qCDtBC,2CiD1wKO,2CDkGN,MAAO,KACP,iBAAA,QACA,aAAA,QAEF,yBACE,WAAA,EACA,cAAA,IE1HF,sBACE,cAAA,EACA,YAAA,IAEA,O9C0DA,cAAA,KACQ,iBAAA,KJ6uKT,OAAA,IAAA,MAAA,YkDnyKC,cAAe,IACf,mBAAA,EAAA,IAAA,IAAA,gBlDqyKD,WAAA,EAAA,IAAA,IAAA,gBkD/xKC,YACA,QAAA,KvBnBC,e3BuzKF,QAAA,KAAA,KkDtyKC,cAAe,IAAI,MAAM,YAMvB,uBAAA,IlDmyKH,wBAAA,IkD7xKC,0CACA,MAAA,QAEA,alDgyKD,WAAA,EkDpyKC,cAAe,EjDg0Kf,UAAW,KACX,MAAO,QDtBR,oBkD1xKC,sBjDkzKF,eiDxzKI,mBAKJ,qBAEE,MAAA,QvBvCA,cACC,QAAA,KAAA,K3Bs0KF,iBAAA,QkDrxKC,WAAY,IAAI,MAAM,KjDizKtB,2BAA4B,IiD9yK1B,0BAAA,IAHJ,mBAAA,mCAMM,cAAA,ElDwxKL,oCkDnxKG,oDjD+yKF,aAAc,IAAI,EiD7yKZ,cAAA,EvBtEL,4D3B61KF,4EkDjxKG,WAAA,EjD6yKF,uBAAwB,IiD3yKlB,wBAAA,IvBtEL,0D3B21KF,0EkD1yKC,cAAe,EvB1Df,2BAAA,IACC,0BAAA,IuB0FH,+EAEI,uBAAA,ElD8wKH,wBAAA,EkD1wKC,wDlD6wKD,iBAAA,EC4BD,0BACE,iBAAkB,EiDlyKpB,8BlD0wKC,ckD1wKD,gCjDuyKE,cAAe,EiDvyKjB,sCAQM,sBlDwwKL,wCC4BC,cAAe,K0Br5Kf,aAAA,KuByGF,wDlDqxKC,0BC4BC,uBAAwB,IACxB,wBAAyB,IiDlzK3B,yFAoBQ,yFlDwwKP,2DkDzwKO,2DjDqyKN,uBAAwB,IACxB,wBAAyB,IAK3B,wGiD9zKA,wGjD4zKA,wGDtBC,wGCuBD,0EiD7zKA,0EjD2zKA,0EiDnyKU,0EjD2yKR,uBAAwB,IAK1B,uGiDx0KA,uGjDs0KA,uGDtBC,uGCuBD,yEiDv0KA,yEjDq0KA,yEiDzyKU,yEvB7HR,wBAAA,IuBiGF,sDlDqzKC,yBC4BC,2BAA4B,IAC5B,0BAA2B,IiDxyKrB,qFA1CR,qFAyCQ,wDlDmxKP,wDC4BC,2BAA4B,IAC5B,0BAA2B,IAG7B,oGDtBC,oGCwBD,oGiD91KA,oGjD21KA,uEiD7yKU,uEjD+yKV,uEiD71KA,uEjDm2KE,0BAA2B,IAG7B,mGDtBC,mGCwBD,mGiDx2KA,mGjDq2KA,sEiDnzKU,sEjDqzKV,sEiDv2KA,sEjD62KE,2BAA4B,IiDlzK1B,0BlD2xKH,qCkDt1KD,0BAAA,qCA+DI,WAAA,IAAA,MAAA,KA/DJ,kDAAA,kDAmEI,WAAA,EAnEJ,uBAAA,yCjD23KE,OAAQ,EiDjzKA,+CjDqzKV,+CiD/3KA,+CjDi4KA,+CAEA,+CANA,+CDjBC,iECoBD,iEiDh4KA,iEjDk4KA,iEAEA,iEANA,iEAWE,YAAa,EiD3zKL,8CjD+zKV,8CiD74KA,8CjD+4KA,8CAEA,8CANA,8CDjBC,gECoBD,gEiD94KA,gEjDg5KA,gEAEA,gEANA,gEAWE,aAAc,EAIhB,+CiD35KA,+CjDy5KA,+CiDl0KU,+CjDq0KV,iEiD55KA,iEjD05KA,iEDtBC,iEC6BC,cAAe,EAEjB,8CiDn0KU,8CjDq0KV,8CiDr6KA,8CjDo6KA,gEDtBC,gECwBD,gEiDh0KI,gEACA,cAAA,EAUJ,yBACE,cAAA,ElDmyKD,OAAA,EkD/xKG,aACA,cAAA,KANJ,oBASM,cAAA,ElDkyKL,cAAA,IkD7xKG,2BlDgyKH,WAAA,IC4BD,4BiDxzKM,cAAA,EAKF,wDAvBJ,wDlDqzKC,WAAA,IAAA,MAAA,KkD5xKK,2BlD+xKL,WAAA,EmDlhLC,uDnDqhLD,cAAA,IAAA,MAAA,KmDlhLG,eACA,aAAA,KnDshLH,8BmDxhLC,MAAA,KAMI,iBAAA,QnDqhLL,aAAA,KmDlhLK,0DACA,iBAAA,KAGJ,qCAEI,MAAA,QnDmhLL,iBAAA,KmDpiLC,yDnDuiLD,oBAAA,KmDpiLG,eACA,aAAA,QnDwiLH,8BmD1iLC,MAAA,KAMI,iBAAA,QnDuiLL,aAAA,QmDpiLK,0DACA,iBAAA,QAGJ,qCAEI,MAAA,QnDqiLL,iBAAA,KmDtjLC,yDnDyjLD,oBAAA,QmDtjLG,eACA,aAAA,QnD0jLH,8BmD5jLC,MAAA,QAMI,iBAAA,QnDyjLL,aAAA,QmDtjLK,0DACA,iBAAA,QAGJ,qCAEI,MAAA,QnDujLL,iBAAA,QmDxkLC,yDnD2kLD,oBAAA,QmDxkLG,YACA,aAAA,QnD4kLH,2BmD9kLC,MAAA,QAMI,iBAAA,QnD2kLL,aAAA,QmDxkLK,uDACA,iBAAA,QAGJ,kCAEI,MAAA,QnDykLL,iBAAA,QmD1lLC,sDnD6lLD,oBAAA,QmD1lLG,eACA,aAAA,QnD8lLH,8BmDhmLC,MAAA,QAMI,iBAAA,QnD6lLL,aAAA,QmD1lLK,0DACA,iBAAA,QAGJ,qCAEI,MAAA,QnD2lLL,iBAAA,QmD5mLC,yDnD+mLD,oBAAA,QmD5mLG,cACA,aAAA,QnDgnLH,6BmDlnLC,MAAA,QAMI,iBAAA,QnD+mLL,aAAA,QmD5mLK,yDACA,iBAAA,QAGJ,oCAEI,MAAA,QnD6mLL,iBAAA,QoD5nLC,wDACA,oBAAA,QAEA,kBACA,SAAA,SpD+nLD,QAAA,MoDpoLC,OAAQ,EnDgqLR,QAAS,EACT,SAAU,OAEZ,yCmDtpLI,wBADA,yBAEA,yBACA,wBACA,SAAA,SACA,IAAA,EACA,OAAA,EpD+nLH,KAAA,EoD1nLC,MAAO,KACP,OAAA,KpD4nLD,OAAA,EoDvnLC,wBpD0nLD,eAAA,OqDppLC,uBACA,eAAA,IAEA,MACA,WAAA,KACA,QAAA,KjDwDA,cAAA,KACQ,iBAAA,QJgmLT,OAAA,IAAA,MAAA,QqD/pLC,cAAe,IASb,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACA,WAAA,MAAA,EAAA,IAAA,IAAA,gBAKJ,iBACE,aAAA,KACA,aAAA,gBAEF,SACE,QAAA,KACA,cAAA,ICtBF,SACE,QAAA,IACA,cAAA,IAEA,OACA,MAAA,MACA,UAAA,KjCRA,YAAA,IAGA,YAAA,ErBqrLD,MAAA,KsD7qLC,YAAA,EAAA,IAAA,EAAA,KrDysLA,OAAQ,kBqDvsLN,QAAA,GjCbF,aiCeE,ajCZF,MAAA,KrB6rLD,gBAAA,KsDzqLC,OAAA,QACE,OAAA,kBACA,QAAA,GAEA,aACA,mBAAA,KtD2qLH,QAAA,EuDhsLC,OAAQ,QACR,WAAA,IvDksLD,OAAA,EuD7rLC,YACA,SAAA,OAEA,OACA,SAAA,MACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,KAAA,EAIA,QAAA,KvD6rLD,QAAA,KuD1rLC,SAAA,OnD+GA,2BAAA,MACI,QAAA,EAEI,0BAkER,mBAAA,kBAAA,IAAA,SAEK,cAAA,aAAA,IAAA,SACG,WAAA,UAAA,IAAA,SJ6gLT,kBAAA,kBuDhsLC,cAAA,kBnD2GA,aAAA,kBACI,UAAA,kBAEI,wBJwlLT,kBAAA,euDpsLK,cAAe,eACnB,aAAA,eACA,UAAA,eAIF,mBACE,WAAA,OACA,WAAA,KvDqsLD,cuDhsLC,SAAU,SACV,MAAA,KACA,OAAA,KAEA,eACA,SAAA,SnDaA,iBAAA,KACQ,wBAAA,YmDZR,gBAAA,YtD4tLA,OsD5tLA,IAAA,MAAA,KAEA,OAAA,IAAA,MAAA,evDksLD,cAAA,IuD9rLC,QAAS,EACT,mBAAA,EAAA,IAAA,IAAA,eACA,WAAA,EAAA,IAAA,IAAA,eAEA,gBACA,SAAA,MACA,IAAA,EACA,MAAA,EvDgsLD,OAAA,EuD9rLC,KAAA,ElCrEA,QAAA,KAGA,iBAAA,KkCmEA,qBlCtEA,OAAA,iBAGA,QAAA,EkCwEF,mBACE,OAAA,kBACA,QAAA,GAIF,cACE,QAAA,KvDgsLD,cAAA,IAAA,MAAA,QuD3rLC,qBACA,WAAA,KAKF,aACE,OAAA,EACA,YAAA,WAIF,YACE,SAAA,SACA,QAAA,KvD0rLD,cuD5rLC,QAAS,KAQP,WAAA,MACA,WAAA,IAAA,MAAA,QATJ,wBAaI,cAAA,EvDsrLH,YAAA,IuDlrLG,mCvDqrLH,YAAA,KuD/qLC,oCACA,YAAA,EAEA,yBACA,SAAA,SvDkrLD,IAAA,QuDhqLC,MAAO,KAZP,OAAA,KACE,SAAA,OvDgrLD,yBuD7qLD,cnDvEA,MAAA,MACQ,OAAA,KAAA,KmD2ER,eAAY,mBAAA,EAAA,IAAA,KAAA,evD+qLX,WAAA,EAAA,IAAA,KAAA,euDzqLD,UAFA,MAAA,OvDirLD,yBwD/zLC,UACA,MAAA,OCNA,SAEA,SAAA,SACA,QAAA,KACA,QAAA,MACA,YAAA,iBAAA,UAAA,MAAA,WACA,UAAA,KACA,WAAA,OACA,YAAA,IACA,YAAA,WACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KACA,eAAA,ODHA,WAAA,OnCVA,aAAA,OAGA,UAAA,OrBs1LD,YAAA,OwD30LC,OAAA,iBnCdA,QAAA,ErB61LD,WAAA,KwD90LY,YAAmB,OAAA,kBxDk1L/B,QAAA,GwDj1LY,aAAmB,QAAA,IAAA,ExDq1L/B,WAAA,KwDp1LY,eAAmB,QAAA,EAAA,IxDw1L/B,YAAA,IwDv1LY,gBAAmB,QAAA,IAAA,ExD21L/B,WAAA,IwDt1LC,cACA,QAAA,EAAA,IACA,YAAA,KAEA,eACA,UAAA,MxDy1LD,QAAA,IAAA,IwDr1LC,MAAO,KACP,WAAA,OACA,iBAAA,KACA,cAAA,IAEA,exDu1LD,SAAA,SwDn1LC,MAAA,EACE,OAAA,EACA,aAAA,YACA,aAAA,MAEA,4BxDq1LH,OAAA,EwDn1LC,KAAA,IACE,YAAA,KACA,aAAA,IAAA,IAAA,EACA,iBAAA,KAEA,iCxDq1LH,MAAA,IwDn1LC,OAAA,EACE,cAAA,KACA,aAAA,IAAA,IAAA,EACA,iBAAA,KAEA,kCxDq1LH,OAAA,EwDn1LC,KAAA,IACE,cAAA,KACA,aAAA,IAAA,IAAA,EACA,iBAAA,KAEA,8BxDq1LH,IAAA,IwDn1LC,KAAA,EACE,WAAA,KACA,aAAA,IAAA,IAAA,IAAA,EACA,mBAAA,KAEA,6BxDq1LH,IAAA,IwDn1LC,MAAA,EACE,WAAA,KACA,aAAA,IAAA,EAAA,IAAA,IACA,kBAAA,KAEA,+BxDq1LH,IAAA,EwDn1LC,KAAA,IACE,YAAA,KACA,aAAA,EAAA,IAAA,IACA,oBAAA,KAEA,oCxDq1LH,IAAA,EwDn1LC,MAAA,IACE,WAAA,KACA,aAAA,EAAA,IAAA,IACA,oBAAA,KAEA,qCxDq1LH,IAAA,E0Dl7LC,KAAM,IACN,WAAA,KACA,aAAA,EAAA,IAAA,IACA,oBAAA,KAEA,SACA,SAAA,SACA,IAAA,EDXA,KAAA,EAEA,QAAA,KACA,QAAA,KACA,UAAA,MACA,QAAA,IACA,YAAA,iBAAA,UAAA,MAAA,WACA,UAAA,KACA,WAAA,OACA,YAAA,IACA,YAAA,WACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KCAA,eAAA,OAEA,WAAA,OACA,aAAA,OAAA,UAAA,OACA,YAAA,OACA,iBAAA,KACA,wBAAA,YtD8CA,gBAAA,YACQ,OAAA,IAAA,MAAA,KJk5LT,OAAA,IAAA,MAAA,e0D77LC,cAAA,IAAY,mBAAA,EAAA,IAAA,KAAA,e1Dg8Lb,WAAA,EAAA,IAAA,KAAA,e0D/7La,WAAA,KACZ,aAAY,WAAA,MACZ,eAAY,YAAA,KAGd,gBACE,WAAA,KAEA,cACA,YAAA,MAEA,e1Dq8LD,QAAA,IAAA,K0Dl8LC,OAAQ,EACR,UAAA,K1Do8LD,iBAAA,Q0D57LC,cAAA,IAAA,MAAA,QzDy9LA,cAAe,IAAI,IAAI,EAAE,EyDt9LvB,iBACA,QAAA,IAAA,KAEA,gBACA,sB1D87LH,SAAA,S0D37LC,QAAS,MACT,MAAA,E1D67LD,OAAA,E0D37LC,aAAc,YACd,aAAA,M1D87LD,gB0Dz7LC,aAAA,KAEE,sBACA,QAAA,GACA,aAAA,KAEA,oB1D27LH,OAAA,M0D17LG,KAAA,IACE,YAAA,MACA,iBAAA,KACA,iBAAA,gBACA,oBAAA,E1D67LL,0B0Dz7LC,OAAA,IACE,YAAA,MACA,QAAA,IACA,iBAAA,KACA,oBAAA,EAEA,sB1D27LH,IAAA,I0D17LG,KAAA,MACE,WAAA,MACA,mBAAA,KACA,mBAAA,gBACA,kBAAA,E1D67LL,4B0Dz7LC,OAAA,MACE,KAAA,IACA,QAAA,IACA,mBAAA,KACA,kBAAA,EAEA,uB1D27LH,IAAA,M0D17LG,KAAA,IACE,YAAA,MACA,iBAAA,EACA,oBAAA,KACA,oBAAA,gB1D67LL,6B0Dx7LC,IAAA,IACE,YAAA,MACA,QAAA,IACA,iBAAA,EACA,oBAAA,KAEA,qB1D07LH,IAAA,I0Dz7LG,MAAA,MACE,WAAA,MACA,mBAAA,EACA,kBAAA,KACA,kBAAA,gB1D47LL,2B2DpjMC,MAAO,IACP,OAAA,M3DsjMD,QAAA,I2DnjMC,mBAAoB,EACpB,kBAAA,KAEA,U3DqjMD,SAAA,S2DljMG,gBACA,SAAA,SvD6KF,MAAA,KACK,SAAA,OJ04LN,sB2D/jMC,SAAU,S1D4lMV,QAAS,K0D9kML,mBAAA,IAAA,YAAA,K3DqjML,cAAA,IAAA,YAAA,K2D3hMC,WAAA,IAAA,YAAA,KvDmKK,4BAFL,0BAGQ,YAAA,EA3JA,qDA+GR,sBAEQ,mBAAA,kBAAA,IAAA,YJ86LP,cAAA,aAAA,IAAA,Y2DzjMG,WAAA,UAAA,IAAA,YvDmHJ,4BAAA,OACQ,oBAAA,OuDjHF,oBAAA,O3D4jML,YAAA,OI58LD,mCHs+LA,2BGr+LQ,KAAA,EuD5GF,kBAAA,sB3D6jML,UAAA,sBC2BD,kCADA,2BG5+LA,KAAA,EACQ,kBAAA,uBuDtGF,UAAA,uBArCN,6B3DomMD,gC2DpmMC,iC1D+nME,KAAM,E0DllMN,kBAAA,mB3D4jMH,UAAA,oBAGA,wB2D5mMD,sBAAA,sBAsDI,QAAA,MAEA,wB3D0jMH,KAAA,E2DtjMG,sB3DyjMH,sB2DrnMC,SAAU,SA+DR,IAAA,E3DyjMH,MAAA,KC0BD,sB0D/kMI,KAAA,KAnEJ,sBAuEI,KAAA,MAvEJ,2BA0EI,4B3DwjMH,KAAA,E2D/iMC,6BACA,KAAA,MAEA,8BACA,KAAA,KtC3FA,kBsC6FA,SAAA,SACA,IAAA,EACA,OAAA,EACA,KAAA,EACA,MAAA,I3DmjMD,UAAA,K2D9iMC,MAAA,KdnGE,WAAA,OACA,YAAA,EAAA,IAAA,IAAA,eACA,iBAAA,cAAA,OAAA,kBACA,QAAA,G7CqpMH,uB2DljMC,iBAAA,sEACE,iBAAA,iEACA,iBAAA,uFdxGA,iBAAA,kEACA,OAAA,+GACA,kBAAA,SACA,wBACA,MAAA,E7C6pMH,KAAA,K2DpjMC,iBAAA,sE1DglMA,iBAAiB,iE0D9kMf,iBAAA,uFACA,iBAAA,kEACA,OAAA,+GtCvHF,kBAAA,SsCyFF,wB3DslMC,wBC4BC,MAAO,KACP,gBAAiB,KACjB,OAAQ,kB0D7kMN,QAAA,EACA,QAAA,G3DwjMH,0C2DhmMD,2CA2CI,6BADA,6B1DklMF,SAAU,S0D7kMR,IAAA,IACA,QAAA,E3DqjMH,QAAA,a2DrmMC,WAAY,MAqDV,0CADA,6B3DsjMH,KAAA,I2D1mMC,YAAa,MA0DX,2CADA,6BAEA,MAAA,IACA,aAAA,MAME,6BADF,6B3DmjMH,MAAA,K2D9iMG,OAAA,KACE,YAAA,M3DgjML,YAAA,E2DriMC,oCACA,QAAA,QAEA,oCACA,QAAA,QAEA,qBACA,SAAA,SACA,OAAA,K3DwiMD,KAAA,I2DjjMC,QAAS,GAYP,MAAA,IACA,aAAA,EACA,YAAA,KACA,WAAA,OACA,WAAA,KAEA,wBACA,QAAA,aAWA,MAAA,KACA,OAAA,K3D8hMH,OAAA,I2D7jMC,YAAa,OAkCX,OAAA,QACA,iBAAA,OACA,iBAAA,cACA,OAAA,IAAA,MAAA,K3D8hMH,cAAA,K2DthMC,6BACA,MAAA,KACA,OAAA,KACA,OAAA,EACA,iBAAA,KAEA,kBACA,SAAA,SACA,MAAA,IACA,OAAA,K3DyhMD,KAAA,I2DxhMC,QAAA,GACE,YAAA,K3D0hMH,eAAA,K2Dj/LC,MAAO,KAhCP,WAAA,O1D8iMA,YAAa,EAAE,IAAI,IAAI,eAEzB,uB0D3iMM,YAAA,KAEA,oCACA,0C3DmhMH,2C2D3hMD,6BAAA,6BAYI,MAAA,K3DmhMH,OAAA,K2D/hMD,WAAA,M1D2jME,UAAW,KDxBZ,0C2D9gMD,6BACE,YAAA,MAEA,2C3DghMD,6B2D5gMD,aAAA,M3D+gMC,kBACF,MAAA,I4D7wMC,KAAA,I3DyyME,eAAgB,KAElB,qBACE,OAAQ,MAkBZ,qCADA,sCADA,mBADA,oBAXA,gBADA,iBAOA,uBADA,wBADA,iBADA,kBADA,wBADA,yBASA,mCADA,oC2DpzME,oBAAA,qBAAA,oBAAA,qB3D2zMF,WADA,YAOA,uBADA,wBADA,qBADA,sBADA,cADA,e2D/zMI,a3Dq0MJ,cDvBC,kB4D7yMG,mB3DqzMJ,WADA,YAwBE,QAAS,MACT,QAAS,IASX,qCADA,mBANA,gBAGA,uBADA,iBADA,wBAIA,mCDhBC,oB6D/0MC,oB5Dk2MF,W+B51MA,uBhCo0MC,qB4D5zMG,cChBF,aACA,kB5D+1MF,W+Br1ME,MAAO,KhCy0MR,cgCt0MC,QAAS,MACT,aAAA,KhCw0MD,YAAA,KgC/zMC,YhCk0MD,MAAA,gBgC/zMC,WhCk0MD,MAAA,egC/zMC,MhCk0MD,QAAA,e8Dz1MC,MACA,QAAA,gBAEA,WACA,WAAA,O9B8BF,WACE,KAAA,EAAA,EAAA,EhCg0MD,MAAA,YgCzzMC,YAAa,KACb,iBAAA,YhC2zMD,OAAA,E+D31MC,Q/D81MD,QAAA,eC4BD,OACE,SAAU,M+Dn4MV,chE42MD,MAAA,aC+BD,YADA,YADA,YADA,YAIE,QAAS,e+Dp5MT,kBhEs4MC,mBgEr4MD,yBhEi4MD,kB+Dl1MD,mBA6IA,yB9D4tMA,kBACA,mB8Dj3ME,yB9D62MF,kBACA,mBACA,yB+Dv5MY,QAAA,eACV,yBAAU,YhE04MT,QAAA,gBC4BD,iB+Dp6MU,QAAA,gBhE64MX,c+D51MG,QAAS,oB/Dg2MV,c+Dl2MC,c/Dm2MH,QAAA,sB+D91MG,yB/Dk2MD,kBACF,QAAA,iB+D91MG,yB/Dk2MD,mBACF,QAAA,kBgEh6MC,yBhEo6MC,yBgEn6MD,QAAA,wBACA,+CAAU,YhEw6MT,QAAA,gBC4BD,iB+Dl8MU,QAAA,gBhE26MX,c+Dr2MG,QAAS,oB/Dy2MV,c+D32MC,c/D42MH,QAAA,sB+Dv2MG,+C/D22MD,kBACF,QAAA,iB+Dv2MG,+C/D22MD,mBACF,QAAA,kBgE97MC,+ChEk8MC,yBgEj8MD,QAAA,wBACA,gDAAU,YhEs8MT,QAAA,gBC4BD,iB+Dh+MU,QAAA,gBhEy8MX,c+D92MG,QAAS,oB/Dk3MV,c+Dp3MC,c/Dq3MH,QAAA,sB+Dh3MG,gD/Do3MD,kBACF,QAAA,iB+Dh3MG,gD/Do3MD,mBACF,QAAA,kBgE59MC,gDhEg+MC,yBgE/9MD,QAAA,wBACA,0BAAU,YhEo+MT,QAAA,gBC4BD,iB+D9/MU,QAAA,gBhEu+MX,c+Dv3MG,QAAS,oB/D23MV,c+D73MC,c/D83MH,QAAA,sB+Dz3MG,0B/D63MD,kBACF,QAAA,iB+Dz3MG,0B/D63MD,mBACF,QAAA,kBgEl/MC,0BhEs/MC,yBACF,QAAA,wBgEv/MC,yBhE2/MC,WACF,QAAA,gBgE5/MC,+ChEggNC,WACF,QAAA,gBgEjgNC,gDhEqgNC,WACF,QAAA,gBAGA,0B+Dh3MC,WA4BE,QAAS,gBC5LX,eAAU,QAAA,eACV,aAAU,ehEyhNT,QAAA,gBC4BD,oB+DnjNU,QAAA,gBhE4hNX,iB+D93MG,QAAS,oBAMX,iB/D23MD,iB+Dt2MG,QAAS,sB/D22MZ,qB+D/3MC,QAAS,e/Dk4MV,a+D53MC,qBAcE,QAAS,iB/Dm3MZ,sB+Dh4MC,QAAS,e/Dm4MV,a+D73MC,sBAOE,QAAS,kB/D23MZ,4B+D53MC,QAAS,eCpLT,ahEojNC,4BACF,QAAA,wBC6BD,aACE,cACE,QAAS","sourcesContent":["/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS and IE text size adjust after device orientation change,\n// without disabling user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability of focused elements when they are also in an\n// active/hover state.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome.\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n box-sizing: content-box; //2\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n","/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('../fonts/glyphicons-halflings-regular.eot');\n src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\002a\";\n}\n.glyphicon-plus:before {\n content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-btc:before {\n content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\n[role=\"button\"] {\n cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n background-color: #fcf8e3;\n padding: .2em;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #fff;\n background-color: #333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n word-break: break-all;\n word-wrap: break-word;\n color: #333333;\n background-color: #f5f5f5;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #ddd;\n}\n.table .table {\n background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #ddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n min-width: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #fff;\n background-image: none;\n border: 1px solid #ccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999;\n}\n.form-control::-ms-expand {\n border: 0;\n background-color: transparent;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: #eeeeee;\n opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"].form-control,\n input[type=\"time\"].form-control,\n input[type=\"datetime-local\"].form-control,\n input[type=\"month\"].form-control {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n min-height: 34px;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.form-group-sm select.form-control {\n height: 30px;\n line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n min-height: 32px;\n padding: 6px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.form-group-lg select.form-control {\n height: 46px;\n line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n min-height: 38px;\n padding: 11px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n border-color: #3c763d;\n background-color: #dff0d8;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n border-color: #8a6d3b;\n background-color: #fcf8e3;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n border-color: #a94442;\n background-color: #f2dede;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 11px;\n font-size: 18px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n font-size: 12px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n outline: 0;\n background-image: none;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n opacity: 0.65;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n pointer-events: none;\n}\n.btn-default {\n color: #333;\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n color: #333;\n background-color: #e6e6e6;\n border-color: #8c8c8c;\n}\n.btn-default:hover {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n color: #333;\n background-color: #d4d4d4;\n border-color: #8c8c8c;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default .badge {\n color: #fff;\n background-color: #333;\n}\n.btn-primary {\n color: #fff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n color: #fff;\n background-color: #286090;\n border-color: #122b40;\n}\n.btn-primary:hover {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n color: #fff;\n background-color: #204d74;\n border-color: #122b40;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.btn-success {\n color: #fff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n color: #fff;\n background-color: #449d44;\n border-color: #255625;\n}\n.btn-success:hover {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n color: #fff;\n background-color: #398439;\n border-color: #255625;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #fff;\n}\n.btn-info {\n color: #fff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n color: #fff;\n background-color: #31b0d5;\n border-color: #1b6d85;\n}\n.btn-info:hover {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n color: #fff;\n background-color: #269abc;\n border-color: #1b6d85;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #fff;\n}\n.btn-warning {\n color: #fff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n color: #fff;\n background-color: #ec971f;\n border-color: #985f0d;\n}\n.btn-warning:hover {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n color: #fff;\n background-color: #d58512;\n border-color: #985f0d;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #fff;\n}\n.btn-danger {\n color: #fff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n color: #fff;\n background-color: #c9302c;\n border-color: #761c19;\n}\n.btn-danger:hover {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n color: #fff;\n background-color: #ac2925;\n border-color: #761c19;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #fff;\n}\n.btn-link {\n color: #337ab7;\n font-weight: normal;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n}\n.collapse.in {\n display: block;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px dashed;\n border-top: 4px solid \\9;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #fff;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n background-clip: padding-box;\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n text-decoration: none;\n color: #262626;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n background-color: #337ab7;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n border-top: 0;\n border-bottom: 4px dashed;\n border-bottom: 4px solid \\9;\n content: \"\";\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group .form-control:focus {\n z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n z-index: 2;\n margin-left: -1px;\n}\n.nav {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n background-color: #fff;\n border: 1px solid #ddd;\n border-bottom-color: transparent;\n cursor: default;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #fff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-left: 0;\n padding-right: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: 15px;\n padding: 9px 10px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n margin-left: -15px;\n margin-right: -15px;\n padding: 10px 15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-left: 15px;\n margin-right: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777;\n}\n.navbar-default .navbar-link:hover {\n color: #333;\n}\n.navbar-default .btn-link {\n color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #ccc;\n}\n.navbar-inverse {\n background-color: #222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #fff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #fff;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #fff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #fff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n content: \"/\\00a0\";\n padding: 0 5px;\n color: #ccc;\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #fff;\n border: 1px solid #ddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n z-index: 2;\n color: #23527c;\n background-color: #eeeeee;\n border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 3;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n background-color: #fff;\n border-color: #ddd;\n cursor: not-allowed;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n background-color: #fff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n color: #fff;\n line-height: 1;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding-top: 30px;\n padding-bottom: 30px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n border-radius: 6px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding-top: 48px;\n padding-bottom: 48px;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-left: 60px;\n padding-right: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-left: auto;\n margin-right: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #fff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-object.img-thumbnail {\n max-width: none;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n margin-bottom: 20px;\n padding-left: 0;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item,\nbutton.list-group-item {\n color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n text-decoration: none;\n color: #555;\n background-color: #f5f5f5;\n}\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #fff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #ddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-left-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n border: 0;\n margin-bottom: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #ddd;\n}\n.panel-default {\n border-color: #ddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n}\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #fff;\n border: 1px solid #999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n background-clip: padding-box;\n outline: 0;\n}\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n.modal-backdrop.fade {\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 12px;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n bottom: 0;\n right: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 14px;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n margin: 0;\n padding: 8px 14px;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #fff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #fff;\n}\n.popover.bottom > .arrow {\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\n border-top-width: 0;\n border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #fff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000px;\n -moz-perspective: 1000px;\n perspective: 1000px;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\n font-size: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #fff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n margin-top: -10px;\n z-index: 5;\n display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n line-height: 1;\n font-family: serif;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #fff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #fff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -10px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -10px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -10px;\n }\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n content: \" \";\n display: table;\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table !important;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table !important;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table !important;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table !important;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table !important;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n font-family: sans-serif;\n -webkit-text-size-adjust: 100%;\n -ms-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n margin: .67em 0;\n font-size: 2em;\n}\nmark {\n color: #000;\n background: #ff0;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\nsup {\n top: -.5em;\n}\nsub {\n bottom: -.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n height: 0;\n -webkit-box-sizing: content-box;\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n margin: 0;\n font: inherit;\n color: inherit;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n padding: 0;\n border: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: content-box;\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n -webkit-appearance: textfield;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n padding: .35em .625em .75em;\n margin: 0 2px;\n border: 1px solid #c0c0c0;\n}\nlegend {\n padding: 0;\n border: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-spacing: 0;\n border-collapse: collapse;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n color: #000 !important;\n text-shadow: none !important;\n background: transparent !important;\n -webkit-box-shadow: none !important;\n box-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n\n src: url('../fonts/glyphicons-halflings-regular.eot');\n src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\002a\";\n}\n.glyphicon-plus:before {\n content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-btc:before {\n content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333;\n background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n display: inline-block;\n max-width: 100%;\n height: auto;\n padding: 4px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: all .2s ease-in-out;\n -o-transition: all .2s ease-in-out;\n transition: all .2s ease-in-out;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\n[role=\"button\"] {\n cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n padding: .2em;\n background-color: #fcf8e3;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n margin-left: -5px;\n list-style: none;\n}\n.list-inline > li {\n display: inline-block;\n padding-right: 5px;\n padding-left: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n overflow: hidden;\n clear: left;\n text-align: right;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n text-align: right;\n border-right: 5px solid #eee;\n border-left: 0;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #fff;\n background-color: #333;\n border-radius: 3px;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n color: #333;\n word-break: break-all;\n word-wrap: break-word;\n background-color: #f5f5f5;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n.row {\n margin-right: -15px;\n margin-left: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-right: 15px;\n padding-left: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #ddd;\n}\n.table .table {\n background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n display: table-column;\n float: none;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n display: table-cell;\n float: none;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n min-height: .01%;\n overflow-x: auto;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #ddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555;\n background-color: #fff;\n background-image: none;\n border: 1px solid #ccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n}\n.form-control::-moz-placeholder {\n color: #999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999;\n}\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: #eee;\n opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"].form-control,\n input[type=\"time\"].form-control,\n input[type=\"datetime-local\"].form-control,\n input[type=\"month\"].form-control {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-top: 4px \\9;\n margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n vertical-align: middle;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n min-height: 34px;\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-right: 0;\n padding-left: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.form-group-sm select.form-control {\n height: 30px;\n line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n min-height: 32px;\n padding: 6px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.form-group-lg select.form-control {\n height: 46px;\n line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n min-height: 38px;\n padding: 11px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #3c763d;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #8a6d3b;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n background-color: #f2dede;\n border-color: #a94442;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n padding-top: 7px;\n margin-top: 0;\n margin-bottom: 0;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n padding-top: 7px;\n margin-bottom: 0;\n text-align: right;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 11px;\n font-size: 18px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n font-size: 12px;\n }\n}\n.btn {\n display: inline-block;\n padding: 6px 12px;\n margin-bottom: 0;\n font-size: 14px;\n font-weight: normal;\n line-height: 1.42857143;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n -ms-touch-action: manipulation;\n touch-action: manipulation;\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n outline: 0;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n opacity: .65;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n pointer-events: none;\n}\n.btn-default {\n color: #333;\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n color: #333;\n background-color: #e6e6e6;\n border-color: #8c8c8c;\n}\n.btn-default:hover {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n color: #333;\n background-color: #d4d4d4;\n border-color: #8c8c8c;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default .badge {\n color: #fff;\n background-color: #333;\n}\n.btn-primary {\n color: #fff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n color: #fff;\n background-color: #286090;\n border-color: #122b40;\n}\n.btn-primary:hover {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n color: #fff;\n background-color: #204d74;\n border-color: #122b40;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.btn-success {\n color: #fff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n color: #fff;\n background-color: #449d44;\n border-color: #255625;\n}\n.btn-success:hover {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n color: #fff;\n background-color: #398439;\n border-color: #255625;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #fff;\n}\n.btn-info {\n color: #fff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n color: #fff;\n background-color: #31b0d5;\n border-color: #1b6d85;\n}\n.btn-info:hover {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n color: #fff;\n background-color: #269abc;\n border-color: #1b6d85;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #fff;\n}\n.btn-warning {\n color: #fff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n color: #fff;\n background-color: #ec971f;\n border-color: #985f0d;\n}\n.btn-warning:hover {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n color: #fff;\n background-color: #d58512;\n border-color: #985f0d;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #fff;\n}\n.btn-danger {\n color: #fff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n color: #fff;\n background-color: #c9302c;\n border-color: #761c19;\n}\n.btn-danger:hover {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n color: #fff;\n background-color: #ac2925;\n border-color: #761c19;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #fff;\n}\n.btn-link {\n font-weight: normal;\n color: #337ab7;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity .15s linear;\n -o-transition: opacity .15s linear;\n transition: opacity .15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n}\n.collapse.in {\n display: block;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-timing-function: ease;\n -o-transition-timing-function: ease;\n transition-timing-function: ease;\n -webkit-transition-duration: .35s;\n -o-transition-duration: .35s;\n transition-duration: .35s;\n -webkit-transition-property: height, visibility;\n -o-transition-property: height, visibility;\n transition-property: height, visibility;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px dashed;\n border-top: 4px solid \\9;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n font-size: 14px;\n text-align: left;\n list-style: none;\n background-color: #fff;\n -webkit-background-clip: padding-box;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, .15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n color: #262626;\n text-decoration: none;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #fff;\n text-decoration: none;\n background-color: #337ab7;\n outline: 0;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n cursor: not-allowed;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n content: \"\";\n border-top: 0;\n border-bottom: 4px dashed;\n border-bottom: 4px solid \\9;\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n right: 0;\n left: auto;\n }\n .navbar-right .dropdown-menu-left {\n right: auto;\n left: 0;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-right: 8px;\n padding-left: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-right: 12px;\n padding-left: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n display: table-cell;\n float: none;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-right: 0;\n padding-left: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group .form-control:focus {\n z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555;\n text-align: center;\n background-color: #eee;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n z-index: 2;\n margin-left: -1px;\n}\n.nav {\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eee;\n}\n.nav > li.disabled > a {\n color: #777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777;\n text-decoration: none;\n cursor: not-allowed;\n background-color: transparent;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eee #eee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555;\n cursor: default;\n background-color: #fff;\n border: 1px solid #ddd;\n border-bottom-color: transparent;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n margin-bottom: 5px;\n text-align: center;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #fff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n margin-bottom: 5px;\n text-align: center;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n padding-right: 15px;\n padding-left: 15px;\n overflow-x: visible;\n -webkit-overflow-scrolling: touch;\n border-top: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-right: 0;\n padding-left: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n height: 50px;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n padding: 9px 10px;\n margin-top: 8px;\n margin-right: 15px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n padding: 10px 15px;\n margin-top: 8px;\n margin-right: -15px;\n margin-bottom: 8px;\n margin-left: -15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n padding-top: 0;\n padding-bottom: 0;\n margin-right: 0;\n margin-left: 0;\n border: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-right: 15px;\n margin-left: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777;\n}\n.navbar-default .navbar-link:hover {\n color: #333;\n}\n.navbar-default .btn-link {\n color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #ccc;\n}\n.navbar-inverse {\n background-color: #222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #fff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n color: #fff;\n background-color: #080808;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #fff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #fff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n padding: 0 5px;\n color: #ccc;\n content: \"/\\00a0\";\n}\n.breadcrumb > .active {\n color: #777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n margin-left: -1px;\n line-height: 1.42857143;\n color: #337ab7;\n text-decoration: none;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-top-left-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n z-index: 2;\n color: #23527c;\n background-color: #eee;\n border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 3;\n color: #fff;\n cursor: default;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777;\n cursor: not-allowed;\n background-color: #fff;\n border-color: #ddd;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-top-left-radius: 6px;\n border-bottom-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-top-left-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-top-right-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n text-align: center;\n list-style: none;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777;\n cursor: not-allowed;\n background-color: #fff;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n background-color: #777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding-top: 30px;\n padding-bottom: 30px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n padding-right: 15px;\n padding-left: 15px;\n border-radius: 6px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding-top: 48px;\n padding-bottom: 48px;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-right: 60px;\n padding-left: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: border .2s ease-in-out;\n -o-transition: border .2s ease-in-out;\n transition: border .2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-right: auto;\n margin-left: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@-o-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n height: 20px;\n margin-bottom: 20px;\n overflow: hidden;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n}\n.progress-bar {\n float: left;\n width: 0;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #fff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n -webkit-transition: width .6s ease;\n -o-transition: width .6s ease;\n transition: width .6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n 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);\n 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);\n 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);\n -webkit-background-size: 40px 40px;\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n 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);\n 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);\n 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);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n 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);\n 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);\n 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);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n 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);\n 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);\n 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);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n 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);\n 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);\n 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);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n overflow: hidden;\n zoom: 1;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-object.img-thumbnail {\n max-width: none;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n padding-left: 0;\n margin-bottom: 20px;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item,\nbutton.list-group-item {\n color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n color: #555;\n text-decoration: none;\n background-color: #f5f5f5;\n}\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n color: #777;\n cursor: not-allowed;\n background-color: #eee;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #fff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #ddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-right: 15px;\n padding-left: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n margin-bottom: 0;\n border: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #ddd;\n}\n.panel-default {\n border-color: #ddd;\n}\n.panel-default > .panel-heading {\n color: #333;\n background-color: #f5f5f5;\n border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, .15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n filter: alpha(opacity=20);\n opacity: .2;\n}\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n filter: alpha(opacity=50);\n opacity: .5;\n}\nbutton.close {\n -webkit-appearance: none;\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n display: none;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transition: -webkit-transform .3s ease-out;\n -o-transition: -o-transform .3s ease-out;\n transition: transform .3s ease-out;\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #fff;\n -webkit-background-clip: padding-box;\n background-clip: padding-box;\n border: 1px solid #999;\n border: 1px solid rgba(0, 0, 0, .2);\n border-radius: 6px;\n outline: 0;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n}\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n.modal-backdrop.fade {\n filter: alpha(opacity=0);\n opacity: 0;\n}\n.modal-backdrop.in {\n filter: alpha(opacity=50);\n opacity: .5;\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-bottom: 0;\n margin-left: 5px;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 12px;\n font-style: normal;\n font-weight: normal;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n white-space: normal;\n filter: alpha(opacity=0);\n opacity: 0;\n\n line-break: auto;\n}\n.tooltip.in {\n filter: alpha(opacity=90);\n opacity: .9;\n}\n.tooltip.top {\n padding: 5px 0;\n margin-top: -3px;\n}\n.tooltip.right {\n padding: 0 5px;\n margin-left: 3px;\n}\n.tooltip.bottom {\n padding: 5px 0;\n margin-top: 3px;\n}\n.tooltip.left {\n padding: 0 5px;\n margin-left: -3px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n right: 5px;\n bottom: 0;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n font-style: normal;\n font-weight: normal;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n white-space: normal;\n background-color: #fff;\n -webkit-background-clip: padding-box;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, .2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n\n line-break: auto;\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n padding: 8px 14px;\n margin: 0;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n content: \"\";\n border-width: 10px;\n}\n.popover.top > .arrow {\n bottom: -11px;\n left: 50%;\n margin-left: -11px;\n border-top-color: #999;\n border-top-color: rgba(0, 0, 0, .25);\n border-bottom-width: 0;\n}\n.popover.top > .arrow:after {\n bottom: 1px;\n margin-left: -10px;\n content: \" \";\n border-top-color: #fff;\n border-bottom-width: 0;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-right-color: #999;\n border-right-color: rgba(0, 0, 0, .25);\n border-left-width: 0;\n}\n.popover.right > .arrow:after {\n bottom: -10px;\n left: 1px;\n content: \" \";\n border-right-color: #fff;\n border-left-width: 0;\n}\n.popover.bottom > .arrow {\n top: -11px;\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999;\n border-bottom-color: rgba(0, 0, 0, .25);\n}\n.popover.bottom > .arrow:after {\n top: 1px;\n margin-left: -10px;\n content: \" \";\n border-top-width: 0;\n border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999;\n border-left-color: rgba(0, 0, 0, .25);\n}\n.popover.left > .arrow:after {\n right: 1px;\n bottom: -10px;\n content: \" \";\n border-right-width: 0;\n border-left-color: #fff;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n.carousel-inner > .item {\n position: relative;\n display: none;\n -webkit-transition: .6s ease-in-out left;\n -o-transition: .6s ease-in-out left;\n transition: .6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform .6s ease-in-out;\n -o-transition: -o-transform .6s ease-in-out;\n transition: transform .6s ease-in-out;\n\n -webkit-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000px;\n perspective: 1000px;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n left: 0;\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n left: 0;\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n left: 0;\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 15%;\n font-size: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n background-color: rgba(0, 0, 0, 0);\n filter: alpha(opacity=50);\n opacity: .5;\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001)));\n background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n background-repeat: repeat-x;\n}\n.carousel-control.right {\n right: 0;\n left: auto;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5)));\n background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n background-repeat: repeat-x;\n}\n.carousel-control:hover,\n.carousel-control:focus {\n color: #fff;\n text-decoration: none;\n filter: alpha(opacity=90);\n outline: 0;\n opacity: .9;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n margin-top: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n font-family: serif;\n line-height: 1;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n padding-left: 0;\n margin-left: -30%;\n text-align: center;\n list-style: none;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n border: 1px solid #fff;\n border-radius: 10px;\n}\n.carousel-indicators .active {\n width: 12px;\n height: 12px;\n margin: 0;\n background-color: #fff;\n}\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -10px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -10px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -10px;\n }\n .carousel-caption {\n right: 20%;\n left: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n display: table;\n content: \" \";\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-right: auto;\n margin-left: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table !important;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table !important;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table !important;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table !important;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table !important;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// Star\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\002a\"; } }\n.glyphicon-plus { &:before { content: \"\\002b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-btc { &:before { content: \"\\e227\"; } }\n.glyphicon-xbt { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-jpy { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-rub { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n\n\n// iOS \"clickable elements\" fix for role=\"button\"\n//\n// Fixes \"clickability\" issue (and more generally, the firing of events such as focus as well)\n// for traditionally non-focusable elements with role=\"button\"\n// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n\n[role=\"button\"] {\n cursor: pointer;\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // WebKit-specific. Other browsers will keep their default outline style.\n // (Initially tried to also force default via `outline: initial`,\n // but that seems to erroneously remove the outline in Firefox altogether.)\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @dl-horizontal-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n .text-uppercase();\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover,\n a&:focus {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover,\n a&:focus {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: floor((@gutter / 2));\n padding-right: ceil((@gutter / 2));\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: ceil((@gutter / -2));\n margin-right: floor((@gutter / -2));\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: ceil((@grid-gutter-width / 2));\n padding-right: floor((@grid-gutter-width / 2));\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Unstyle the caret on ``\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n .opacity(.65);\n .box-shadow(none);\n }\n\n a& {\n &.disabled,\n fieldset[disabled] & {\n pointer-events: none; // Future-proof disabling of clicks on `` elements\n }\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 25%);\n }\n &:hover {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n\n &:hover,\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 17%);\n border-color: darken(@border, 25%);\n }\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus,\n &.focus {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n\n &.in { display: block; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base dashed;\n border-top: @caret-width-base solid ~\"\\9\"; // IE8\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base dashed;\n border-bottom: @caret-width-base solid ~\"\\9\"; // IE8\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn,\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n .border-top-radius(@btn-border-radius-base);\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n .border-top-radius(0);\n .border-bottom-radius(@btn-border-radius-base);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n\n &:focus {\n z-index: 3;\n }\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @input-border-radius;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @input-border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @input-border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n z-index: 2;\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n }\n > .active {\n display: block;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n","// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 3;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n","// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n\n .btn-xs &,\n .btn-group-xs > .btn & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n\n .list-group-item > & {\n float: right;\n }\n\n .list-group-item > & + & {\n margin-right: 5px;\n }\n\n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding-top: @jumbotron-padding;\n padding-bottom: @jumbotron-padding;\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n\n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding-top: (@jumbotron-padding * 1.6);\n padding-bottom: (@jumbotron-padding * 1.6);\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: @jumbotron-heading-font-size;\n }\n }\n}\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n\n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n\n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n",".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n\n // Fix collapse in webkit from max-width: 100% and display: table-cell.\n &.img-thumbnail {\n max-width: none;\n }\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on
        ,
          , or
          .\n\n.list-group {\n // No need to set list-style: none; since .list-group-item is block level\n margin-bottom: 20px;\n padding-left: 0; // reset padding because ul and ol\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -1px;\n background-color: @list-group-bg;\n border: 1px solid @list-group-border;\n\n // Round the first and last items\n &:first-child {\n .border-top-radius(@list-group-border-radius);\n }\n &:last-child {\n margin-bottom: 0;\n .border-bottom-radius(@list-group-border-radius);\n }\n}\n\n\n// Interactive list items\n//\n// Use anchor or button elements instead of `li`s or `div`s to create interactive items.\n// Includes an extra `.active` modifier class for showing selected items.\n\na.list-group-item,\nbutton.list-group-item {\n color: @list-group-link-color;\n\n .list-group-item-heading {\n color: @list-group-link-heading-color;\n }\n\n // Hover state\n &:hover,\n &:focus {\n text-decoration: none;\n color: @list-group-link-hover-color;\n background-color: @list-group-hover-bg;\n }\n}\n\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n\n.list-group-item {\n // Disabled state\n &.disabled,\n &.disabled:hover,\n &.disabled:focus {\n background-color: @list-group-disabled-bg;\n color: @list-group-disabled-color;\n cursor: @cursor-disabled;\n\n // Force color to inherit for custom content\n .list-group-item-heading {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-disabled-text-color;\n }\n }\n\n // Active class on item itself, not parent\n &.active,\n &.active:hover,\n &.active:focus {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: @list-group-active-color;\n background-color: @list-group-active-bg;\n border-color: @list-group-active-border;\n\n // Force color to inherit for custom content\n .list-group-item-heading,\n .list-group-item-heading > small,\n .list-group-item-heading > .small {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-active-text-color;\n }\n }\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n.list-group-item-variant(success; @state-success-bg; @state-success-text);\n.list-group-item-variant(info; @state-info-bg; @state-info-text);\n.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);\n.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);\n\n\n// Custom content options\n//\n// Extra classes for creating well-formatted content within `.list-group-item`s.\n\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n","// List Groups\n\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a&,\n button& {\n color: @color;\n\n .list-group-item-heading {\n color: inherit;\n }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n","//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n margin-bottom: @line-height-computed;\n background-color: @panel-bg;\n border: 1px solid transparent;\n border-radius: @panel-border-radius;\n .box-shadow(0 1px 1px rgba(0,0,0,.05));\n}\n\n// Panel contents\n.panel-body {\n padding: @panel-body-padding;\n &:extend(.clearfix all);\n}\n\n// Optional heading\n.panel-heading {\n padding: @panel-heading-padding;\n border-bottom: 1px solid transparent;\n .border-top-radius((@panel-border-radius - 1));\n\n > .dropdown .dropdown-toggle {\n color: inherit;\n }\n}\n\n// Within heading, strip any `h*` tag of its default margins for spacing.\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: ceil((@font-size-base * 1.125));\n color: inherit;\n\n > a,\n > small,\n > .small,\n > small > a,\n > .small > a {\n color: inherit;\n }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n padding: @panel-footer-padding;\n background-color: @panel-footer-bg;\n border-top: 1px solid @panel-inner-border;\n .border-bottom-radius((@panel-border-radius - 1));\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n > .list-group,\n > .panel-collapse > .list-group {\n margin-bottom: 0;\n\n .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n }\n\n // Add border top radius for first one\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n .border-top-radius((@panel-border-radius - 1));\n }\n }\n\n // Add border bottom radius for last one\n &:last-child {\n .list-group-item:last-child {\n border-bottom: 0;\n .border-bottom-radius((@panel-border-radius - 1));\n }\n }\n }\n > .panel-heading + .panel-collapse > .list-group {\n .list-group-item:first-child {\n .border-top-radius(0);\n }\n }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n .list-group-item:first-child {\n border-top-width: 0;\n }\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n > .table,\n > .table-responsive > .table,\n > .panel-collapse > .table {\n margin-bottom: 0;\n\n caption {\n padding-left: @panel-body-padding;\n padding-right: @panel-body-padding;\n }\n }\n // Add border top radius for first one\n > .table:first-child,\n > .table-responsive:first-child > .table:first-child {\n .border-top-radius((@panel-border-radius - 1));\n\n > thead:first-child,\n > tbody:first-child {\n > tr:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n border-top-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-top-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n // Add border bottom radius for last one\n > .table:last-child,\n > .table-responsive:last-child > .table:last-child {\n .border-bottom-radius((@panel-border-radius - 1));\n\n > tbody:last-child,\n > tfoot:last-child {\n > tr:last-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n border-bottom-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n > .panel-body + .table,\n > .panel-body + .table-responsive,\n > .table + .panel-body,\n > .table-responsive + .panel-body {\n border-top: 1px solid @table-border-color;\n }\n > .table > tbody:first-child > tr:first-child th,\n > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n }\n > .table-bordered,\n > .table-responsive > .table-bordered {\n border: 0;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n > thead,\n > tbody {\n > tr:first-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n > tbody,\n > tfoot {\n > tr:last-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n }\n > .table-responsive {\n border: 0;\n margin-bottom: 0;\n }\n}\n\n\n// Collapsible panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n margin-bottom: @line-height-computed;\n\n // Tighten up margin so it's only between panels\n .panel {\n margin-bottom: 0;\n border-radius: @panel-border-radius;\n\n + .panel {\n margin-top: 5px;\n }\n }\n\n .panel-heading {\n border-bottom: 0;\n\n + .panel-collapse > .panel-body,\n + .panel-collapse > .list-group {\n border-top: 1px solid @panel-inner-border;\n }\n }\n\n .panel-footer {\n border-top: 0;\n + .panel-collapse .panel-body {\n border-bottom: 1px solid @panel-inner-border;\n }\n }\n}\n\n\n// Contextual variations\n.panel-default {\n .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-info {\n .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n.panel-warning {\n .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n","// Panels\n\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse > .panel-body {\n border-top-color: @border;\n }\n .badge {\n color: @heading-bg-color;\n background-color: @heading-text-color;\n }\n }\n & > .panel-footer {\n + .panel-collapse > .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n","// Embeds responsive\n//\n// Credit: Nicolas Gallagher and SUIT CSS.\n\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n\n .embed-responsive-item,\n iframe,\n embed,\n object,\n video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n }\n}\n\n// Modifier class for 16:9 aspect ratio\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n\n// Modifier class for 4:3 aspect ratio\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n","//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: @well-bg;\n border: 1px solid @well-border;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));\n blockquote {\n border-color: #ddd;\n border-color: rgba(0,0,0,.15);\n }\n}\n\n// Sizes\n.well-lg {\n padding: 24px;\n border-radius: @border-radius-large;\n}\n.well-sm {\n padding: 9px;\n border-radius: @border-radius-small;\n}\n","//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n float: right;\n font-size: (@font-size-base * 1.5);\n font-weight: @close-font-weight;\n line-height: 1;\n color: @close-color;\n text-shadow: @close-text-shadow;\n .opacity(.2);\n\n &:hover,\n &:focus {\n color: @close-color;\n text-decoration: none;\n cursor: pointer;\n .opacity(.5);\n }\n\n // Additional properties for button version\n // iOS requires the button element instead of an anchor tag.\n // If you want the anchor version, it requires `href=\"#\"`.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n button& {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n }\n}\n","//\n// Modals\n// --------------------------------------------------\n\n// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n overflow: hidden;\n}\n\n// Container that the modal scrolls within\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal;\n -webkit-overflow-scrolling: touch;\n\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n\n // When fading in the modal, animate it to slide down\n &.fade .modal-dialog {\n .translate(0, -25%);\n .transition-transform(~\"0.3s ease-out\");\n }\n &.in .modal-dialog { .translate(0, 0) }\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n background-color: @modal-content-bg;\n border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n border: 1px solid @modal-content-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 3px 9px rgba(0,0,0,.5));\n background-clip: padding-box;\n // Remove focus outline from opened modal\n outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal-background;\n background-color: @modal-backdrop-bg;\n // Fade for backdrop\n &.fade { .opacity(0); }\n &.in { .opacity(@modal-backdrop-opacity); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n padding: @modal-title-padding;\n border-bottom: 1px solid @modal-header-border-color;\n &:extend(.clearfix all);\n}\n// Close icon\n.modal-header .close {\n margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n margin: 0;\n line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n padding: @modal-inner-padding;\n text-align: right; // right align buttons\n border-top: 1px solid @modal-footer-border-color;\n &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons\n\n // Properly space out buttons\n .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n }\n // but override that for button groups\n .btn-group .btn + .btn {\n margin-left: -1px;\n }\n // and override it for block buttons as well\n .btn-block + .btn-block {\n margin-left: 0;\n }\n}\n\n// Measure scrollbar width for padding body during modal show/hide\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n// Scale up the modal\n@media (min-width: @screen-sm-min) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n width: @modal-md;\n margin: 30px auto;\n }\n .modal-content {\n .box-shadow(0 5px 15px rgba(0,0,0,.5));\n }\n\n // Modal sizes\n .modal-sm { width: @modal-sm; }\n}\n\n@media (min-width: @screen-md-min) {\n .modal-lg { width: @modal-lg; }\n}\n","//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n position: absolute;\n z-index: @zindex-tooltip;\n display: block;\n // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n .reset-text();\n font-size: @font-size-small;\n\n .opacity(0);\n\n &.in { .opacity(@tooltip-opacity); }\n &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }\n &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }\n &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }\n &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: @tooltip-max-width;\n padding: 3px 8px;\n color: @tooltip-color;\n text-align: center;\n background-color: @tooltip-bg;\n border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1\n.tooltip {\n &.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-left .tooltip-arrow {\n bottom: 0;\n right: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-right .tooltip-arrow {\n bottom: 0;\n left: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n border-right-color: @tooltip-arrow-color;\n }\n &.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-left-color: @tooltip-arrow-color;\n }\n &.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-left .tooltip-arrow {\n top: 0;\n right: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-right .tooltip-arrow {\n top: 0;\n left: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n}\n",".reset-text() {\n font-family: @font-family-base;\n // We deliberately do NOT reset font-size.\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: @line-height-base;\n text-align: left; // Fallback for where `start` is not supported\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n}\n","//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: @zindex-popover;\n display: none;\n max-width: @popover-max-width;\n padding: 1px;\n // Our parent element can be arbitrary since popovers are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n .reset-text();\n font-size: @font-size-base;\n\n background-color: @popover-bg;\n background-clip: padding-box;\n border: 1px solid @popover-fallback-border-color;\n border: 1px solid @popover-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 5px 10px rgba(0,0,0,.2));\n\n // Offset the popover to account for the popover arrow\n &.top { margin-top: -@popover-arrow-width; }\n &.right { margin-left: @popover-arrow-width; }\n &.bottom { margin-top: @popover-arrow-width; }\n &.left { margin-left: -@popover-arrow-width; }\n}\n\n.popover-title {\n margin: 0; // reset heading margin\n padding: 8px 14px;\n font-size: @font-size-base;\n background-color: @popover-title-bg;\n border-bottom: 1px solid darken(@popover-title-bg, 5%);\n border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;\n}\n\n.popover-content {\n padding: 9px 14px;\n}\n\n// Arrows\n//\n// .arrow is outer, .arrow:after is inner\n\n.popover > .arrow {\n &,\n &:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n }\n}\n.popover > .arrow {\n border-width: @popover-arrow-outer-width;\n}\n.popover > .arrow:after {\n border-width: @popover-arrow-width;\n content: \"\";\n}\n\n.popover {\n &.top > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-top-color: @popover-arrow-outer-color;\n bottom: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n bottom: 1px;\n margin-left: -@popover-arrow-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-color;\n }\n }\n &.right > .arrow {\n top: 50%;\n left: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-right-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n left: 1px;\n bottom: -@popover-arrow-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-color;\n }\n }\n &.bottom > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-bottom-color: @popover-arrow-outer-color;\n top: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n top: 1px;\n margin-left: -@popover-arrow-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-color;\n }\n }\n\n &.left > .arrow {\n top: 50%;\n right: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-width: 0;\n border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-left-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: @popover-arrow-color;\n bottom: -@popover-arrow-width;\n }\n }\n}\n","//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n\n > .item {\n display: none;\n position: relative;\n .transition(.6s ease-in-out left);\n\n // Account for jankitude on images\n > img,\n > a > img {\n &:extend(.img-responsive);\n line-height: 1;\n }\n\n // WebKit CSS3 transforms for supported devices\n @media all and (transform-3d), (-webkit-transform-3d) {\n .transition-transform(~'0.6s ease-in-out');\n .backface-visibility(~'hidden');\n .perspective(1000px);\n\n &.next,\n &.active.right {\n .translate3d(100%, 0, 0);\n left: 0;\n }\n &.prev,\n &.active.left {\n .translate3d(-100%, 0, 0);\n left: 0;\n }\n &.next.left,\n &.prev.right,\n &.active {\n .translate3d(0, 0, 0);\n left: 0;\n }\n }\n }\n\n > .active,\n > .next,\n > .prev {\n display: block;\n }\n\n > .active {\n left: 0;\n }\n\n > .next,\n > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n }\n\n > .next {\n left: 100%;\n }\n > .prev {\n left: -100%;\n }\n > .next.left,\n > .prev.right {\n left: 0;\n }\n\n > .active.left {\n left: -100%;\n }\n > .active.right {\n left: 100%;\n }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: @carousel-control-width;\n .opacity(@carousel-control-opacity);\n font-size: @carousel-control-font-size;\n color: @carousel-control-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n background-color: rgba(0, 0, 0, 0); // Fix IE9 click-thru bug\n // We can't have this transition here because WebKit cancels the carousel\n // animation if you trip this while in the middle of another animation.\n\n // Set gradients for backgrounds\n &.left {\n #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n }\n &.right {\n left: auto;\n right: 0;\n #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n }\n\n // Hover/focus state\n &:hover,\n &:focus {\n outline: 0;\n color: @carousel-control-color;\n text-decoration: none;\n .opacity(.9);\n }\n\n // Toggles\n .icon-prev,\n .icon-next,\n .glyphicon-chevron-left,\n .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n margin-top: -10px;\n z-index: 5;\n display: inline-block;\n }\n .icon-prev,\n .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n }\n .icon-next,\n .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n }\n .icon-prev,\n .icon-next {\n width: 20px;\n height: 20px;\n line-height: 1;\n font-family: serif;\n }\n\n\n .icon-prev {\n &:before {\n content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n }\n }\n .icon-next {\n &:before {\n content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n }\n }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n\n li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid @carousel-indicator-border-color;\n border-radius: 10px;\n cursor: pointer;\n\n // IE8-9 hack for event handling\n //\n // Internet Explorer 8-9 does not support clicks on elements without a set\n // `background-color`. We cannot use `filter` since that's not viewed as a\n // background color by the browser. Thus, a hack is needed.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer\n //\n // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n // set alpha transparency for the best results possible.\n background-color: #000 \\9; // IE8\n background-color: rgba(0,0,0,0); // IE9\n }\n .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: @carousel-indicator-active-bg;\n }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: @carousel-caption-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n & .btn {\n text-shadow: none; // No shadow for button elements in carousel-caption\n }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n // Scale up the controls a smidge\n .carousel-control {\n .glyphicon-chevron-left,\n .glyphicon-chevron-right,\n .icon-prev,\n .icon-next {\n width: (@carousel-control-font-size * 1.5);\n height: (@carousel-control-font-size * 1.5);\n margin-top: (@carousel-control-font-size / -2);\n font-size: (@carousel-control-font-size * 1.5);\n }\n .glyphicon-chevron-left,\n .icon-prev {\n margin-left: (@carousel-control-font-size / -2);\n }\n .glyphicon-chevron-right,\n .icon-next {\n margin-right: (@carousel-control-font-size / -2);\n }\n }\n\n // Show and left align the captions\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n\n // Move up the indicators\n .carousel-indicators {\n bottom: 20px;\n }\n}\n","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n","// Center-align a block level element\n\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n","// CSS image replacement\n//\n// Heads up! v3 launched with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (has been removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n","//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 in Windows (Phone) 8\n//\n// Support for responsive views via media queries is kind of borked in IE10, for\n// Surface/desktop in split view and for Windows Phone 8. This particular fix\n// must be accompanied by a snippet of JavaScript to sniff the user agent and\n// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at\n// our Getting Started page for more information on this bug.\n//\n// For more information, see the following:\n//\n// Issue: https://github.com/twbs/bootstrap/issues/10497\n// Docs: http://getbootstrap.com/getting-started/#support-ie10-width\n// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/\n// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n\n@-ms-viewport {\n width: device-width;\n}\n\n\n// Visibility utilities\n// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n .responsive-invisibility();\n}\n\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n\n.visible-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-visibility();\n }\n}\n.visible-xs-block {\n @media (max-width: @screen-xs-max) {\n display: block !important;\n }\n}\n.visible-xs-inline {\n @media (max-width: @screen-xs-max) {\n display: inline !important;\n }\n}\n.visible-xs-inline-block {\n @media (max-width: @screen-xs-max) {\n display: inline-block !important;\n }\n}\n\n.visible-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-visibility();\n }\n}\n.visible-sm-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: block !important;\n }\n}\n.visible-sm-inline {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline !important;\n }\n}\n.visible-sm-inline-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline-block !important;\n }\n}\n\n.visible-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-visibility();\n }\n}\n.visible-md-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: block !important;\n }\n}\n.visible-md-inline {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline !important;\n }\n}\n.visible-md-inline-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline-block !important;\n }\n}\n\n.visible-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-visibility();\n }\n}\n.visible-lg-block {\n @media (min-width: @screen-lg-min) {\n display: block !important;\n }\n}\n.visible-lg-inline {\n @media (min-width: @screen-lg-min) {\n display: inline !important;\n }\n}\n.visible-lg-inline-block {\n @media (min-width: @screen-lg-min) {\n display: inline-block !important;\n }\n}\n\n.hidden-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-invisibility();\n }\n}\n.hidden-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-invisibility();\n }\n}\n.hidden-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-invisibility();\n }\n}\n.hidden-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-invisibility();\n }\n}\n\n\n// Print utilities\n//\n// Media queries are placed on the inside to be mixin-friendly.\n\n// Note: Deprecated .visible-print as of v3.2.0\n.visible-print {\n .responsive-invisibility();\n\n @media print {\n .responsive-visibility();\n }\n}\n.visible-print-block {\n display: none !important;\n\n @media print {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n\n @media print {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n\n @media print {\n display: inline-block !important;\n }\n}\n\n.hidden-print {\n @media print {\n .responsive-invisibility();\n }\n}\n","// Responsive utilities\n\n//\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table !important; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n"]} \ No newline at end of file diff --git a/histview2/static/common/css/components.css b/histview2/static/common/css/components.css new file mode 100644 index 0000000..56ecd2a --- /dev/null +++ b/histview2/static/common/css/components.css @@ -0,0 +1,187 @@ +.grouplist-checkbox-with-search { + max-height: 150px; + overflow: auto; +} + +.grouplist-checkbox-with-search.disable-max-height { + max-height: unset; +} + +.grouplist-checkbox-with-search::-webkit-scrollbar-track, +.select2-results__options::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px grey; +} + +.grouplist-checkbox-with-search::-webkit-scrollbar-thumb, +.select2-results__options::-webkit-scrollbar-thumb { + border-radius: 25px; +} + +.form-group .list-group-item { + padding: 0.2rem 1rem; + font-size: 14px; + border-left: none; + border-right: none; +} + +.shorten-name { + white-space: nowrap; + display: inline-block; + /*max-width: 13vw;*/ + overflow: hidden !important; + text-overflow: ellipsis; +} +.fit-item { + max-width: 80px; +} + + +.floating-dropdown { + height: 50vh; + position: absolute; + z-index: 100; + /*min-width: 24.5vw;*/ + border: 1px solid #375a7f; + background-color: #333; +} + +#cond-proc-row h5, h6 { + margin-bottom: 0; +} + +.end-proc, .cond-proc { + min-width: 250px; +} + +.card .end-proc, +.card .cond-proc { + padding: 1rem !important; + border: 1px solid #444; + position: relative; +} + +.card .end-proc .list-item, +.card .cond-proc .list-item { + margin-bottom: 0px !important; + margin-top: 3px; +} + +.close-icon { + position: absolute; + top: -2px; + right: 15px; + width: 12px; + height: 12px; +} + +.close-icon svg { + width: 12px; + height: 12px; +} + + +/* Tooltip CSS */ +.tooltip-parent .tooltip-content { + visibility: hidden; + width: 150px; + background-color: rgba(8, 8, 8, .8); + color: #fff; + text-align: left; + border-radius: 6px; + border: 1px solid #444444; + padding: 5px; + margin: 0 0.5rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + /* Position the tooltip */ + position: absolute; + z-index: 9999; +} + +.tooltip-parent:hover .tooltip-content { + visibility: visible; +} + +.tooltip-parent-lg .tooltip-content-lg { + visibility: hidden; + background-color: rgba(8, 8, 8, .8); + color: #fff; + text-align: left; + border-radius: 6px; + border: 1px solid #444444; + padding: 5px; + margin: -8vh 0.5rem 0 0.5rem; + word-wrap: break-word; + hyphens: auto; + + /* Position the tooltip */ + position: absolute; + z-index: 9999; +} + +.tooltip-parent-lg:hover .tooltip-content-lg { + visibility: visible; +} + +/* End tooltip CSS */ + +/* limit */ +.num-sensor-limit { + display: grid; + border-left: 1px solid white; + border-right: 1px solid white; + border-radius: 4px; + width: 5vw; + text-align: center; +} + +.border-gray-curve { + border: 1px solid #444; + border-radius: 3px; +} + + +.level-select { + min-width: unset; + padding: 3px; + height: auto; + width: 60px; +} + +.level-select:disabled { + width: 60px; + min-width: 60px; +} + +.level-select:focus { + width: 60px; +} + + +.expand-arrow { + position: relative; + padding-bottom: 15px; +} + +.expand-arrow .arrow { + position: absolute; + display: block; + content: ''; + bottom: 2px; + left: 50%; + transform: translateX(-50%); + cursor: pointer; + border-left: 20px solid transparent; + border-top: 10px solid #444; + border-right: 20px solid transparent; +} + +.plc-38 { + padding-left: 35px; +} + +.gray, .gray + label { + color: gray; +} \ No newline at end of file diff --git a/histview2/static/common/css/dataTables.bootstrap4.min.css b/histview2/static/common/css/dataTables.bootstrap4.min.css new file mode 100644 index 0000000..faf03d7 --- /dev/null +++ b/histview2/static/common/css/dataTables.bootstrap4.min.css @@ -0,0 +1 @@ +table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important;border-spacing:0}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:auto;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:0.85em;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap;justify-content:flex-end}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:before,table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:before,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:before,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:0.9em;display:block;opacity:0.3}table.dataTable thead .sorting:before,table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_desc:before,table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_desc_disabled:before{right:1em;content:"\2191"}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{right:0.5em;content:"\2193"}table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_desc:after{opacity:1}table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_desc_disabled:after{opacity:0}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:before,div.dataTables_scrollBody table thead .sorting_asc:before,div.dataTables_scrollBody table thead .sorting_desc:before,div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot>.dataTables_scrollFootInner{box-sizing:content-box}div.dataTables_scrollFoot>.dataTables_scrollFootInner>table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}div.dataTables_wrapper div.dataTables_paginate ul.pagination{justify-content:center !important}}table.dataTable.table-sm>thead>tr>th :not(.sorting_disabled){padding-right:20px}table.dataTable.table-sm .sorting:before,table.dataTable.table-sm .sorting_asc:before,table.dataTable.table-sm .sorting_desc:before{top:5px;right:0.85em}table.dataTable.table-sm .sorting:after,table.dataTable.table-sm .sorting_asc:after,table.dataTable.table-sm .sorting_desc:after{top:5px}table.table-bordered.dataTable{border-right-width:0}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:1px}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0} diff --git a/histview2/static/common/css/dragndrop.css b/histview2/static/common/css/dragndrop.css new file mode 100644 index 0000000..486cdbb --- /dev/null +++ b/histview2/static/common/css/dragndrop.css @@ -0,0 +1,73 @@ +.drag-area{ + border: 2px dashed #444444; + /*height: 500px;*/ + /*width: 700px;*/ + border-radius: 5px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + padding: 10px 0; +} +.drag-area.active{ + border: 2px solid #fff; +} +.drag-area .icon{ + font-size: 50px; + color: #fff; +} +.drag-area header{ + font-size: 18px; + font-weight: 500; + color: #fff; + background-color: transparent; + border: none; + box-shadow: none; + padding: 0; +} +.drag-area span{ + font-size: 18px; + font-weight: 500; + color: #fff; +} +.drag-area button{ + margin-bottom: 10px; + padding: 7px 25px; + font-weight: 500; + border: none; + outline: none; + background: #ced4da; + color: #000000; + border-radius: 5px; + cursor: pointer; +} +.drag-area img{ + height: 100%; + width: 100%; + object-fit: cover; + border-radius: 5px; +} +.box { + position: relative; +} +.box.has-advanced-upload { + outline: 2px dashed #444444; + outline-offset: -10px; + -webkit-transition: outline-offset .15s ease-in-out, background-color .15s linear; + transition: outline-offset .15s ease-in-out, background-color .15s linear; +} +.box.is-dragover { + outline-offset: -15px; + outline-color: #dee2e6; +} +.box__input { + display: contents; +} +.box__input .hide { + display: none; +} +.box__success { + margin-bottom: 10px; + display: none; +} + diff --git a/histview2/static/common/css/fSelect.css b/histview2/static/common/css/fSelect.css new file mode 100644 index 0000000..78fffbe --- /dev/null +++ b/histview2/static/common/css/fSelect.css @@ -0,0 +1,154 @@ +.fs-wrap { + display: inline-block; + cursor: pointer; + line-height: 1; + /* width: 180px; */ + width: 171.97px; + color: black; +} + +.fs-label-wrap { + position: relative; + background-color: #fff; + border: 1px solid #ddd; + cursor: default; + height: 36.5px; + color: #444; + background-clip:padding-box; + border-radius: 0.25rem; +} + +.fs-label-wrap, +.fs-dropdown { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.fs-label-wrap .fs-label { + padding: 10px 22px 6px 8px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + color:black +} + +.fs-arrow { + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #333; + position: absolute; + top: 0; + right: 5px; + bottom: 0; + margin: auto; + transition: ease-in 0.15s; +} + +.fs-open .fs-arrow { + transform: rotate(-180deg); +} + +.fs-dropdown { + position: absolute; + background-color: #fff; + border: 1px solid #ddd; + width: 171.97px; + margin-top: 5px; + z-index: 1000; +} + +.fs-dropdown .fs-options { + max-height: 200px; + overflow: auto; +} + +.fs-search input { + border: none !important; + box-shadow: none !important; + outline: none; + padding: 6px 0; + width: 100%; +} + +.fs-option, +.fs-search, +.fs-optgroup-label { + padding: 6px 8px; + border-bottom: 1px solid #eee; + cursor: default; +} + +.fs-option:last-child { + border-bottom: none; +} + +.fs-search { + padding: 0 8px; +} + +.fs-no-results { + padding: 6px 8px; +} + +.fs-option { + cursor: pointer; + word-break: break-all; +} + +.fs-option.disabled { + opacity: 0.4; + cursor: default; +} + +.fs-option.hl { + background-color: #f5f5f5; +} + +.fs-wrap.multiple .fs-option { + position: relative; + padding-left: 30px; +} + +.fs-wrap.multiple .fs-checkbox { + position: absolute; + display: block; + width: 30px; + top: 0; + left: 0; + bottom: 0; +} + +.fs-wrap.multiple .fs-option .fs-checkbox i { + position: absolute; + margin: auto; + left: 0; + right: 0; + top: 0; + bottom: 0; + width: 14px; + height: 14px; + border: 1px solid #aeaeae; + border-radius: 2px; + background-color: #fff; +} + +.fs-wrap.multiple .fs-option.selected .fs-checkbox i { + background-color: rgb(17, 171.97, 17); + border-color: transparent; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAYAAAD+Bd/7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAABMSURBVAiZfc0xDkAAFIPhd2Kr1WRjcAExuIgzGUTIZ/AkImjSofnbNBAfHvzAHjOKNzhiQ42IDFXCDivaaxAJd0xYshT3QqBxqnxeHvhunpu23xnmAAAAAElFTkSuQmCC'); + background-repeat: no-repeat; + background-position: center; +} + +.fs-optgroup-label { + font-weight: bold; + text-align: center; +} + +.hidden { + display: none; +} diff --git a/histview2/static/common/css/graph_nav.css b/histview2/static/common/css/graph_nav.css new file mode 100644 index 0000000..a3c0a55 --- /dev/null +++ b/histview2/static/common/css/graph_nav.css @@ -0,0 +1,122 @@ +.graphNavActions { + position: fixed; + bottom: 10px; + right: 1px; + z-index: 999; +} + +.graphNavButton { + height: 20px; + width: 20px; + display: block; + color: #fff; + text-align: center; + position: relative; + z-index: 1; +} + +.graphNavButton svg { + position: absolute; + left: 50%; + transform: translateX(-50%); +} + +.graphNavButton svg:nth-child(1) { + top: 0; +} + +.graphNavButton svg:nth-child(2) { + bottom: 0; +} + +.graphNavButtons { + position: absolute; + width: 20px; + bottom: 50vh; + right: -1px; + transform: translateY(55%); + text-align: center; +} + +.graphNavButtons a { + display: none; + border-radius: 50%; + text-decoration: none; + margin: 0 auto; + line-height: 1.15; + color: #fff; + opacity: 0; + visibility: hidden; + position: relative; + box-shadow: 0 0 5px 1px rgba(51, 51, 51, 0.3); + padding-bottom: 14px; +} + +.graphNavButtons a svg { + font-size: 14px; +} + +.graphNavButtons a:hover { + transform: scale(1.3); +} + +.graphNavButtons a:nth-child(1) { + transition: opacity 0.2s ease-in-out 0.3s, transform 0.15s ease-in-out; +} + +.graphNavButtons a:nth-child(2) { + transition: opacity 0.2s ease-in-out 0.25s, transform 0.15s ease-in-out; +} + +.graphNavButtons a:nth-child(3) { + transition: opacity 0.2s ease-in-out 0.2s, transform 0.15s ease-in-out; +} + +.graphNavButtons a:nth-child(4) { + transition: opacity 0.2s ease-in-out 0.15s, transform 0.15s ease-in-out; +} + +.graphNavButtons a:nth-child(5) { + transition: opacity 0.2s ease-in-out 0.10s, transform 0.15s ease-in-out; +} + +.graphNavButtons a:nth-child(6) { + transition: opacity 0.2s ease-in-out 0.05s, transform 0.15s ease-in-out; +} + +.graphNavActions a i { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.graphNavToggle { + -webkit-appearance: none; + position: absolute; + top: 0; + left: 0; + width: 20px; + height: 20px; + cursor: pointer; + background-color: transparent; + border: 1px solid #444; + border-radius: 3px; + z-index: 2; + transition: box-shadow 0.2s ease-in-out; + box-shadow: 0 3px 5px 1px rgba(51, 51, 51, 0.3); +} + +.graphNavToggle:hover { + box-shadow: 0 3px 6px 2px rgba(51, 51, 51, 0.3); +} + +.graphNavToggle:checked ~ .graphNavButtons a { + opacity: 1; + visibility: visible; + display: block; +} + +.graphNavToggle:checked ~ .graphNavButton i { + transform: rotate(-45deg) +} diff --git a/histview2/static/common/css/jexcel.css b/histview2/static/common/css/jexcel.css new file mode 100644 index 0000000..4a9760d --- /dev/null +++ b/histview2/static/common/css/jexcel.css @@ -0,0 +1,751 @@ +:root { + --jexcel-border-color:#000; +} + +.jexcel_container { + display:inline-block; + padding-right:2px; + box-sizing: border-box; + overscroll-behavior: contain; + outline: none; +} + +.jexcel_container.fullscreen { + position:fixed; + top:0px; + left:0px; + width:100%; + height:100%; + z-index:21; +} + +.jexcel_container.fullscreen .jexcel_content { + overflow:auto; + width:100%; + height:100%; + background-color:#ffffff; +} + +.jexcel_container.with-toolbar .jexcel > thead > tr > td { + top: 0; +} + +.jexcel_container.fullscreen.with-toolbar { + height: calc(100% - 46px); +} + +.jexcel_content { + display:inline-block; + box-sizing: border-box; + padding-right:3px; + padding-bottom:3px; + position:relative; + scrollbar-width: thin; + scrollbar-color: #666 transparent; +} + +@supports (-moz-appearance:none) { + .jexcel_content { padding-right:10px; } +} + +.jexcel_content::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.jexcel_content::-webkit-scrollbar-track { + background: #eee; +} + +.jexcel_content::-webkit-scrollbar-thumb { + background: #666; +} + +.jexcel { + border-collapse: separate; + table-layout: fixed; + white-space: nowrap; + empty-cells: show; + border: 0px; + background-color: #fff; + width: 0; + + border-top: 1px solid transparent; + border-left: 1px solid transparent; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} + +.jexcel > thead > tr > td +{ + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid transparent; + border-bottom: 1px solid transparent; + background-color: #f3f3f3; + padding: 2px; + cursor: pointer; + box-sizing: border-box; + overflow: hidden; + position: -webkit-sticky; + position: sticky; + top: 0; + z-index:2; +} + +.jexcel_container.with-toolbar .jexcel > thead > tr > td +{ + top:42px; +} + +.jexcel > thead > tr > td.dragging +{ + background-color:#fff; + opacity:0.5; +} + +.jexcel > thead > tr > td.selected +{ + background-color:#dcdcdc; +} + +.jexcel > thead > tr > td.arrow-up +{ + background-repeat:no-repeat; + background-position:center right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 14l5-5 5 5H7z' fill='gray'/%3E%3C/svg%3E"); + text-decoration:underline; +} + +.jexcel > thead > tr > td.arrow-down +{ + background-repeat:no-repeat; + background-position:center right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E"); + text-decoration:underline; +} + +.jexcel > tbody > tr > td:first-child +{ + position:relative; + background-color:#f3f3f3; + text-align:center; +} + +.jexcel > tbody.resizable > tr > td:first-child::before +{ + content:'\00a0'; + width:100%; + height:3px; + position:absolute; + bottom:0px; + left:0px; + cursor:row-resize; +} + +.jexcel > tbody.draggable > tr > td:first-child::after +{ + content:'\00a0'; + width:3px; + height:100%; + position:absolute; + top:0px; + right:0px; + cursor:move; +} + +.jexcel > tbody > tr.dragging > td +{ + background-color:#eee; + opacity:0.5; +} + +.jexcel > tbody > tr > td +{ + border-top:1px solid #ccc; + border-left:1px solid #ccc; + border-right:1px solid transparent; + border-bottom:1px solid transparent; + padding:4px; + white-space: nowrap; + box-sizing: border-box; + line-height:1em; +} + +.jexcel_overflow > tbody > tr > td { + overflow: hidden; +} + +.jexcel > tbody > tr > td:last-child +{ + overflow: hidden; +} + +.jexcel > tbody > tr > td > img +{ + display:inline-block; + max-width:100px; +} + +.jexcel > tbody > tr > td.readonly +{ + color:rgba(0,0,0,0.3) +} +.jexcel > tbody > tr.selected > td:first-child +{ + background-color:#dcdcdc; +} +.jexcel > tbody > tr > td > select, +.jexcel > tbody > tr > td > input, +.jexcel > tbody > tr > td > textarea +{ + border:0px; + border-radius:0px; + outline:0px; + width:100%; + margin:0px; + padding:0px; + padding-right:2px; + background-color:transparent; + box-sizing: border-box; +} + +.jexcel > tbody > tr > td > textarea +{ + resize: none; + padding-top:6px !important; +} + +.jexcel > tbody > tr > td > input[type=checkbox] +{ + width:12px; + margin-top:2px; +} +.jexcel > tbody > tr > td > input[type=radio] +{ + width:12px; + margin-top:2px; +} + +.jexcel > tbody > tr > td > select +{ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-repeat: no-repeat; + background-position-x: 100%; + background-position-y: 40%; + background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSdibGFjaycgaGVpZ2h0PScyNCcgdmlld0JveD0nMCAwIDI0IDI0JyB3aWR0aD0nMjQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggZD0nTTcgMTBsNSA1IDUtNXonLz48cGF0aCBkPSdNMCAwaDI0djI0SDB6JyBmaWxsPSdub25lJy8+PC9zdmc+); +} + +.jexcel > tbody > tr > td.jexcel_dropdown +{ + background-repeat: no-repeat; + background-position:top 50% right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E"); + text-overflow: ellipsis; + overflow-x:hidden; +} + +.jexcel > tbody > tr > td.jexcel_dropdown.jexcel_comments +{ + background:url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E") top 50% right 5px no-repeat, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII=') top right no-repeat; +} + +.jexcel > tbody > tr > td > .color +{ + width:90%; + height:10px; + margin:auto; +} + +.jexcel > tbody > tr > td > a { + text-decoration: underline; +} + +.jexcel > tbody > tr > td.highlight > a { + color: blue; + cursor: pointer; +} + +.jexcel > tfoot > tr > td +{ + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid transparent; + border-bottom: 1px solid transparent; + background-color: #f3f3f3; + padding: 2px; + cursor: pointer; + box-sizing: border-box; + overflow: hidden; +} + +.jexcel .highlight { + background-color:rgba(0,0,0,0.05); +} + +.jexcel .highlight-top { + border-top:1px solid #000; /* var(--jexcel-border-color);*/ + box-shadow: 0px -1px #ccc; +} + +.jexcel .highlight-left { + border-left:1px solid #000; /* var(--jexcel-border-color);*/ + box-shadow: -1px 0px #ccc; +} + +.jexcel .highlight-right { + border-right:1px solid #000; /* var(--jexcel-border-color);*/ +} + +.jexcel .highlight-bottom { + border-bottom:1px solid #000; /* var(--jexcel-border-color);*/ +} + +.jexcel .highlight-top.highlight-left { + box-shadow: -1px -1px #ccc; + -webkit-box-shadow: -1px -1px #ccc; + -moz-box-shadow: -1px -1px #ccc; +} + +.jexcel .highlight-selected +{ + background-color:rgba(0,0,0,0.0); +} +.jexcel .selection +{ + background-color:rgba(0,0,0,0.05); +} +.jexcel .selection-left +{ + border-left:1px dotted #000; +} +.jexcel .selection-right +{ + border-right:1px dotted #000; +} +.jexcel .selection-top +{ + border-top:1px dotted #000; +} +.jexcel .selection-bottom +{ + border-bottom:1px dotted #000; +} +.jexcel_corner +{ + position:absolute; + background-color: rgb(0, 0, 0); + height: 1px; + width: 1px; + border: 1px solid rgb(255, 255, 255); + top:-2000px; + left:-2000px; + cursor:crosshair; + box-sizing: initial; + z-index:30; + padding: 2px; +} + +.jexcel .editor +{ + outline:0px solid transparent; + overflow:visible; + white-space: nowrap; + text-align:left; + padding:0px; + box-sizing: border-box; + overflow:visible !important; +} + +.jexcel .editor > input +{ + padding-left:4px; +} + +.jexcel .editor .jupload +{ + position:fixed; + top:100%; + z-index:40; + user-select:none; + -webkit-font-smoothing: antialiased; + font-size: .875rem; + letter-spacing: .2px; + -webkit-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); + box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); + padding:10px; + background-color:#fff; + width:300px; + min-height:225px; + margin-top:2px; +} + +.jexcel .editor .jupload img +{ + width:100%; + height:auto; +} + +.jexcel .editor .jexcel_richtext +{ + position:fixed; + top:100%; + z-index:40; + user-select:none; + -webkit-font-smoothing: antialiased; + font-size: .875rem; + letter-spacing: .2px; + -webkit-box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); + box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); + padding:10px; + background-color:#fff; + min-width:280px; + max-width:310px; + margin-top:2px; + text-align:left; +} + +.jexcel .editor .jclose:after +{ + position:absolute; + top:0; + right:0; + margin:10px; + content:'close'; + font-family:'Material icons'; + font-size:24px; + width:24px; + height:24px; + line-height:24px; + cursor:pointer; + text-shadow: 0px 0px 5px #fff; +} + +.jexcel, .jexcel td, .jexcel_corner +{ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-drag: none; + -khtml-user-drag: none; + -moz-user-drag: none; + -o-user-drag: none; + user-drag: none; +} + +.jexcel_textarea +{ + position:absolute; + top:-999px; + left:-999px; + width:1px; + height:1px; +} +.jexcel .dragline +{ + position:absolute; +} +.jexcel .dragline div +{ + position:relative; + top:-6px; + height:5px; + width:22px; +} +.jexcel .dragline div:hover +{ + cursor:move; +} + +.jexcel .onDrag +{ + background-color:rgba(0,0,0,0.6); +} + +.jexcel .error +{ + border:1px solid red; +} + +.jexcel thead td.resizing +{ + border-right-style:dotted !important; + border-right-color:red !important; +} + +.jexcel tbody tr.resizing > td +{ + border-bottom-style:dotted !important; + border-bottom-color:red !important; +} + +.jexcel tbody td.resizing +{ + border-right-style:dotted !important; + border-right-color:red !important; +} + +.jexcel .jdropdown-header +{ + border:0px !important; + outline:none !important; + width:100% !important; + height:100% !important; + padding:0px !important; + padding-left:8px !important; +} + +.jexcel .jdropdown-container +{ + margin-top:1px; +} + +.jexcel .jdropdown-container-header { + padding: 0px; + margin: 0px; + height: inherit; +} + +.jexcel .jdropdown-picker +{ + border:0px !important; + padding:0px !important; + width:inherit; + height:inherit; +} + +.jexcel .jexcel_comments +{ + background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII='); + background-repeat: no-repeat; + background-position: top right; +} + +.jexcel .sp-replacer +{ + margin: 2px; + border:0px; +} + +.jexcel > thead > tr.jexcel_filter > td > input +{ + border:0px; + width:100%; + outline:none; +} + +.jexcel_about { + float: right; + font-size: 0.7em; + padding: 2px; + text-transform: uppercase; + letter-spacing: 1px; + display: none; +} +.jexcel_about a { + color: #ccc; + text-decoration: none; +} + +.jexcel_about img { + display: none; +} + +.jexcel_filter +{ + display:flex; + justify-content:space-between; + margin-bottom:4px; +} + +.jexcel_filter > div +{ + padding:8px; + align-items:center; +} + +.jexcel_pagination +{ + display:flex; + justify-content:space-between; + align-items:center; +} + +.jexcel_pagination > div +{ + display:flex; + padding:10px; +} + +.jexcel_pagination > div:last-child +{ + padding-right:10px; + padding-top:10px; +} + +.jexcel_pagination > div > div +{ + text-align:center; + width:36px; + height:36px; + line-height:34px; + border:1px solid #ccc; + box-sizing: border-box; + margin-left:2px; + cursor:pointer; +} + +.jexcel_page +{ + font-size:0.8em; +} + +.jexcel_page_selected +{ + font-weight:bold; + background-color:#f3f3f3; +} + +.jexcel_toolbar +{ + display:flex; + background-color:#f3f3f3; + border:1px solid #ccc; + padding:4px; + margin:0px 2px 4px 1px; + position:sticky; + top:0px; + z-index:21; +} + +.jexcel_toolbar:empty +{ + display:none; +} + +.jexcel_toolbar i.jexcel_toolbar_item +{ + width:24px; + height:24px; + padding:4px; + cursor:pointer; + display:inline-block; +} + +.jexcel_toolbar i.jexcel_toolbar_item:hover +{ + background-color:#ddd; +} + +.jexcel_toolbar select.jexcel_toolbar_item +{ + margin-left:2px; + margin-right:2px; + display:inline-block; + border:0px; + background-color:transparent; + padding-right:10px; +} + +.jexcel .dragging-left +{ + background-repeat: no-repeat; + background-position:top 50% left 0px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M14 7l-5 5 5 5V7z'/%3E%3Cpath fill='none' d='M24 0v24H0V0h24z'/%3E%3C/svg%3E"); +} + +.jexcel .dragging-right +{ + background-repeat: no-repeat; + background-position:top 50% right 0px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 17l5-5-5-5v10z'/%3E%3Cpath fill='none' d='M0 24V0h24v24H0z'/%3E%3C/svg%3E"); +} + +.jexcel_tabs .jexcel_tab +{ + display:none; +} + +.jexcel_tabs .jexcel_tab_link +{ + display:inline-block; + padding:10px; + padding-left:20px; + padding-right:20px; + margin-right:5px; + margin-bottom:5px; + background-color:#f3f3f3; + cursor:pointer; +} + +.jexcel_tabs .jexcel_tab_link.selected +{ + background-color:#ddd; +} + +.jexcel_hidden_index > tbody > tr > td:first-child, +.jexcel_hidden_index > thead > tr > td:first-child, +.jexcel_hidden_index > tfoot > tr > td:first-child, +.jexcel_hidden_index > colgroup > col:first-child +{ + display:none; +} + + + +.jexcel .jrating { + display: inline-flex; +} +.jexcel .jrating > div { + zoom: 0.55; +} + +.jexcel .copying-top { + border-top:1px dashed #000; +} + +.jexcel .copying-left { + border-left:1px dashed #000; +} + +.jexcel .copying-right { + border-right:1px dashed #000; +} + +.jexcel .copying-bottom { + border-bottom:1px dashed #000; +} + +.jexcel .jexcel_column_filter { + background-repeat: no-repeat; + background-position: top 50% right 5px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + text-overflow: ellipsis; + overflow: hidden; + padding: 0px; + padding-left: 6px; + padding-right: 20px; +} + +.jexcel thead .jexcel_freezed, .jexcel tfoot .jexcel_freezed { + left: 0px; + z-index: 3 !important; + box-shadow: 2px 0px 2px 0.2px #ccc !important; + -webkit-box-shadow: 2px 0px 2px 0.2px #ccc !important; + -moz-box-shadow: 2px 0px 2px 0.2px #ccc !important; +} + +.jexcel tbody .jexcel_freezed { + position: relative; + background-color: #fff; + box-shadow: 1px 1px 1px 1px #ccc !important; + -webkit-box-shadow: 2px 4px 4px 0.1px #ccc !important; + -moz-box-shadow: 2px 4px 4px 0.1px #ccc !important; +} \ No newline at end of file diff --git a/histview2/static/common/css/jquery-ui.css b/histview2/static/common/css/jquery-ui.css new file mode 100644 index 0000000..56e1bbc --- /dev/null +++ b/histview2/static/common/css/jquery-ui.css @@ -0,0 +1,1178 @@ +/*! jQuery UI - v1.10.4 - 2014-01-17 +* http://jqueryui.com +* Includes: jquery.ui.core.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande%2CLucida%20Sans%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=5c9ccc&bgTextureHeader=gloss_wave&bgImgOpacityHeader=55&borderColorHeader=4297d7&fcHeader=ffffff&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=inset_hard&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=469bdd&bgColorDefault=dfeffc&bgTextureDefault=glass&bgImgOpacityDefault=85&borderColorDefault=c5dbec&fcDefault=2e6e9e&iconColorDefault=6da8d5&bgColorHover=d0e5f5&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=79b7e7&fcHover=1d5987&iconColorHover=217bc0&bgColorActive=f5f8f9&bgTextureActive=inset_hard&bgImgOpacityActive=100&borderColorActive=79b7e7&fcActive=e17009&iconColorActive=f9bd01&bgColorHighlight=fbec88&bgTextureHighlight=flat&bgImgOpacityHighlight=55&borderColorHighlight=fad42e&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-clearfix { + min-height: 0; /* support: IE7 */ +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; +} + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin-top: 2px; + padding: .5em .5em .5em .7em; + min-height: 0; /* support: IE7 */ +} +.ui-accordion .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-noicons { + padding-left: .7em; +} +.ui-accordion .ui-accordion-icons .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-header .ui-accordion-header-icon { + position: absolute; + left: .5em; + top: 50%; + margin-top: -8px; +} +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; +} +.ui-autocomplete { + position: absolute; + top: 0; + left: 0; + cursor: default; +} +.ui-button { + display: inline-block; + position: relative; + padding: 0; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + overflow: visible; /* removes extra width in IE */ +} +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2.2em; +} +/* button elements seem to need a little more width */ +button.ui-button-icon-only { + width: 2.4em; +} +.ui-button-icons-only { + width: 3.4em; +} +button.ui-button-icons-only { + width: 3.7em; +} + +/* button text element */ +.ui-button .ui-button-text { + display: block; + line-height: normal; +} +.ui-button-text-only .ui-button-text { + padding: .4em 1em; +} +.ui-button-icon-only .ui-button-text, +.ui-button-icons-only .ui-button-text { + padding: .4em; + text-indent: -9999999px; +} +.ui-button-text-icon-primary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 1em .4em 2.1em; +} +.ui-button-text-icon-secondary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 2.1em .4em 1em; +} +.ui-button-text-icons .ui-button-text { + padding-left: 2.1em; + padding-right: 2.1em; +} +/* no icon support for input elements, provide padding by default */ +input.ui-button { + padding: .4em 1em; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon, +.ui-button-text-icon-primary .ui-icon, +.ui-button-text-icon-secondary .ui-icon, +.ui-button-text-icons .ui-icon, +.ui-button-icons-only .ui-icon { + position: absolute; + top: 50%; + margin-top: -8px; +} +.ui-button-icon-only .ui-icon { + left: 50%; + margin-left: -8px; +} +.ui-button-text-icon-primary .ui-button-icon-primary, +.ui-button-text-icons .ui-button-icon-primary, +.ui-button-icons-only .ui-button-icon-primary { + left: .5em; +} +.ui-button-text-icon-secondary .ui-button-icon-secondary, +.ui-button-text-icons .ui-button-icon-secondary, +.ui-button-icons-only .ui-button-icon-secondary { + right: .5em; +} + +/* button sets */ +.ui-buttonset { + margin-right: 7px; +} +.ui-buttonset .ui-button { + margin-left: 0; + margin-right: -.3em; +} + +/* workarounds */ +/* reset extra padding in Firefox, see h5bp.com/l */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +.ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} +.ui-datepicker .ui-datepicker-next { + right: 2px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 49%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} +.ui-dialog { + overflow: hidden; + position: absolute; + top: 0; + left: 0; + padding: .2em; + outline: 0; +} +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + position: relative; +} +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 0; + white-space: nowrap; + width: 90%; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 20px; + margin: -10px 0 0 0; + padding: 1px; + height: 20px; +} +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; +} +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin-top: .5em; + padding: .3em 1em .5em .4em; +} +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} +.ui-dialog .ui-resizable-se { + width: 12px; + height: 12px; + right: -5px; + bottom: -5px; + background-position: 16px 16px; +} +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} +.ui-menu { + list-style: none; + padding: 2px; + margin: 0; + display: block; + outline: none; +} +.ui-menu .ui-menu { + margin-top: -3px; + position: absolute; +} +.ui-menu .ui-menu-item { + margin: 0; + padding: 0; + width: 100%; + /* support: IE10, see #8844 */ + list-style-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); +} +.ui-menu .ui-menu-divider { + margin: 5px -2px 5px -2px; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-menu-item a { + text-decoration: none; + display: block; + padding: 2px .4em; + line-height: 1.5; + min-height: 0; /* support: IE7 */ + font-weight: normal; +} +.ui-menu .ui-menu-item a.ui-state-focus, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} + +.ui-menu .ui-state-disabled { + font-weight: normal; + margin: .4em 0 .2em; + line-height: 1.5; +} +.ui-menu .ui-state-disabled a { + cursor: default; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item a { + position: relative; + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: .2em; + left: .2em; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + position: static; + float: right; +} +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} +.ui-progressbar .ui-progressbar-value { + margin: -1px; + height: 100%; +} +.ui-progressbar .ui-progressbar-overlay { + background: url("images/animated-overlay.gif"); + height: 100%; + filter: alpha(opacity=25); + opacity: 0.25; +} +.ui-progressbar-indeterminate .ui-progressbar-value { + background-image: none; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* For IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} +.ui-spinner-input { + border: none; + background: none; + color: inherit; + padding: 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 22px; +} +.ui-spinner-button { + width: 16px; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} +/* more specificity required here to override default borders */ +.ui-spinner a.ui-spinner-button { + border-top: none; + border-bottom: none; + border-right: none; +} +/* vertically center icon */ +.ui-spinner .ui-icon { + position: absolute; + margin-top: -8px; + top: 50%; + left: 0; +} +.ui-spinner-up { + top: 0; +} +.ui-spinner-down { + bottom: 0; +} + +/* TR overrides */ +.ui-spinner .ui-icon-triangle-1-s { + /* need to fix icons sprite */ + background-position: -65px -16px; +} +.ui-tabs { + position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ + padding: .2em; +} +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom-width: 0; + padding: 0; + white-space: nowrap; +} +.ui-tabs .ui-tabs-nav .ui-tabs-anchor { + float: left; + padding: .5em 1em; + text-decoration: none; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { + cursor: text; +} +.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { + cursor: pointer; +} +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + max-width: 300px; + -webkit-box-shadow: 0 0 5px #aaa; + box-shadow: 0 0 5px #aaa; +} +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Lucida Grande,Lucida Sans,Arial,sans-serif; + font-size: 1.1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Lucida Grande,Lucida Sans,Arial,sans-serif; + font-size: 1em; +} +.ui-widget-content { + border: 1px solid #a6c9e2; + background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; + color: #222222; +} +.ui-widget-content a { + color: #222222; +} +.ui-widget-header { + border: 1px solid #4297d7; + background: #5c9ccc url(images/ui-bg_gloss-wave_55_5c9ccc_500x100.png) 50% 50% repeat-x; + color: #ffffff; + font-weight: bold; +} +.ui-widget-header a { + color: #ffffff; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default { + border: 1px solid #c5dbec; + background: #dfeffc url(images/ui-bg_glass_85_dfeffc_1x400.png) 50% 50% repeat-x; + font-weight: bold; + color: #2e6e9e; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited { + color: #2e6e9e; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus { + border: 1px solid #79b7e7; + background: #d0e5f5 url(images/ui-bg_glass_75_d0e5f5_1x400.png) 50% 50% repeat-x; + font-weight: bold; + color: #1d5987; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited, +.ui-state-focus a, +.ui-state-focus a:hover, +.ui-state-focus a:link, +.ui-state-focus a:visited { + color: #1d5987; + text-decoration: none; +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active { + border: 1px solid #79b7e7; + background: #f5f8f9 url(images/ui-bg_inset-hard_100_f5f8f9_1x100.png) 50% 50% repeat-x; + font-weight: bold; + color: #e17009; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #e17009; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #fad42e; + background: #fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; + color: #363636; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #363636; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; + color: #cd0a0a; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #cd0a0a; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #cd0a0a; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* For IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url(images/ui-icons_469bdd_256x240.png); +} +.ui-widget-header .ui-icon { + background-image: url(images/ui-icons_d8e7f3_256x240.png); +} +.ui-state-default .ui-icon { + background-image: url(images/ui-icons_6da8d5_256x240.png); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon { + background-image: url(images/ui-icons_217bc0_256x240.png); +} +.ui-state-active .ui-icon { + background-image: url(images/ui-icons_f9bd01_256x240.png); +} +.ui-state-highlight .ui-icon { + background-image: url(images/ui-icons_2e83ff_256x240.png); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url(images/ui-icons_cd0a0a_256x240.png); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 5px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 5px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 5px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 5px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); +} +.ui-widget-shadow { + margin: -8px 0 0 -8px; + padding: 8px; + background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); + border-radius: 8px; +} diff --git a/histview2/static/common/css/jquery.jexcel.min.css b/histview2/static/common/css/jquery.jexcel.min.css new file mode 100644 index 0000000..4b0aade --- /dev/null +++ b/histview2/static/common/css/jquery.jexcel.min.css @@ -0,0 +1,2 @@ +.jexcel{display:inline-block}.jexcel>div.jexcel-toolbar{display:flex;background-color:#f3f3f3;border:1px solid #ccc;padding:4px;margin-bottom:4px;margin-right:3px}.jexcel>div.jexcel-header{padding-right:2px;display:block}.jexcel>div.jexcel-content{padding-right:2px;padding-bottom:2px;display:inline-block}.jexcel>div.jexcel-toolbar i.jexcel-toolbar-item{width:24px;height:24px;padding:4px;cursor:pointer;display:inline-block}.jexcel>div.jexcel-toolbar i.jexcel-toolbar-item:hover{background-color:#ddd}.jexcel>div.jexcel-toolbar select.jexcel-toolbar-item{margin-left:2px;margin-right:2px;display:inline-block}.jexcel>div>table{border-collapse:separate;table-layout:fixed;white-space:nowrap;empty-cells:show;border-top:0 solid transparent;border-left:1px solid #ccc;border-right:1px solid transparent;border-bottom:0 solid transparent;background-color:#fff;width:0}.jexcel>div>table>thead>tr>td{border-left:1px solid transparent;border-right:1px solid #ccc;border-top:1px solid #ccc;border-bottom:1px solid transparent;background-color:#f3f3f3;padding:4px;cursor:pointer;overflow:hidden;box-sizing:border-box}.jexcel>div>table>thead>tr.jexcel_filter>td{padding:0;background-color:#fff}.jexcel>div>table>thead>tr.jexcel_filter>td:first-child{background-color:#f3f3f3}.jexcel>div>table>thead>tr.jexcel_filter>td>input{border:0;width:100%;outline:0}.jexcel>div>table>thead>tr>td.selected{background-color:#dcdcdc}.jexcel>div>table>tbody{padding-right:3px;padding-bottom:1px}.jexcel>div>table>tbody>tr>td.edition{padding:0;overflow:visible}.jexcel>div>table>tbody>tr>td:first-child{background-color:#f3f3f3;width:30px;text-align:center}.jexcel>div>table>tbody>tr>td{border-left:1px solid transparent;border-right:1px solid #ccc;border-top:1px solid transparent;border-bottom:1px solid #ccc;padding:4px;overflow:hidden;white-space:nowrap;box-sizing:border-box}.jexcel>div>table>tbody>tr:first-child>td{border-top:1px solid #ccc}.jexcel>div>table>tbody>tr>td>img{display:block}.jexcel>div>table>tbody>tr>td.readonly{color:rgba(0,0,0,.3)}.jexcel>div>table>tbody>tr.selected>td:first-child{background-color:#dcdcdc}.jexcel>div>table>tbody>tr>td>input,.jexcel>div>table>tbody>tr>td>select,.jexcel>div>table>tbody>tr>td>textarea{border:0;border-radius:0;outline:0;width:100%;margin:0;padding:0;background-color:transparent;box-sizing:border-box}.jexcel>div>table>tbody>tr>td>textarea{resize:none;padding-top:6px!important}.jexcel>div>table>tbody>tr>td>input[type=checkbox]{width:12px;margin-top:2px}.jexcel>div>table>tbody>tr>td>input[type=radio]{width:12px;margin-top:2px}.jexcel>div>table>tbody>tr>td>select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-repeat:no-repeat;background-position-x:100%;background-position-y:40%;background-image:url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSdibGFjaycgaGVpZ2h0PScyNCcgdmlld0JveD0nMCAwIDI0IDI0JyB3aWR0aD0nMjQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggZD0nTTcgMTBsNSA1IDUtNXonLz48cGF0aCBkPSdNMCAwaDI0djI0SDB6JyBmaWxsPSdub25lJy8+PC9zdmc+)}.jexcel .highlight{background-color:rgba(0,0,0,.05)}.jexcel .highlight-top{border-top:1px solid #000!important}.jexcel .highlight-left{border-left:1px solid #000}.jexcel .highlight-right{border-right:1px solid #000;box-shadow:1px 0 #ccc;-webkit-box-shadow:1px 0 #ccc;-moz-box-shadow:1px 0 #ccc}.jexcel .highlight-bottom{border-bottom:1px solid #000;box-shadow:0 1px #ccc;-webkit-box-shadow:0 1px #ccc;-moz-box-shadow:0 1px #ccc}.jexcel .highlight-bottom.highlight-right{border-bottom:1px solid #000;box-shadow:1px 1px #ccc;-webkit-box-shadow:1px 1px #ccc;-moz-box-shadow:1px 1px #ccc}.jexcel .selection{background-color:rgba(0,0,0,.05)}.jexcel .selection-left{border-left:1px dotted #000}.jexcel .selection-right{border-right:1px dotted #000}.jexcel .selection-top{border-top:1px dotted #000}.jexcel .selection-bottom{border-bottom:1px dotted #000}.jexcel_corner{position:absolute;background-color:#000;height:5px;width:5px;border:1px solid #fff;top:-200px;left:-200px;cursor:crosshair;box-sizing:initial;z-index:1}.jexcel .editor{outline:0 solid transparent;overflow:hidden;white-space:nowrap;text-align:left;padding:0;padding-left:4px}.jexcel,.jexcel td,.jexcel_corner{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-drag:none;-khtml-user-drag:none;-moz-user-drag:none;-o-user-drag:none;user-drag:none}.jexcel_textarea{position:absolute;top:-999px;left:-999px;width:1px;height:1px}.jexcel .results{position:absolute;min-height:200px;max-height:300px;width:220px;background-color:#fff;overflow-y:scroll;z-index:99;text-align:left;border:1px solid #ccc;margin-top:8px;margin-left:-5px}.jexcel .results li{list-style:none;padding:6px;cursor:pointer}.jexcel .results li.selected,.jexcel .results li:hover{background-color:#1e90ff;color:#fff}.jexcel .dragline{position:absolute}.jexcel .dragline div{position:relative;top:-6px;height:5px;width:22px}.jexcel .dragline div:hover{cursor:move}.jexcel .onDrag{background-color:rgba(0,0,0,.6)}.jexcel .error{border:1px solid red}.jexcel .arrow-up{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:4px solid #444;position:absolute;margin-left:4px;margin-top:8px}.jexcel .arrow-down{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #444;position:absolute;margin-left:4px;margin-top:8px}.jexcel .resizing{border-right-style:dotted!important;border-right-color:#000!important}.jexcel_contextmenu{display:none;position:absolute;z-index:100;background:#fff;color:#555;font-family:sans-serif;font-size:11px;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-box-shadow:2px 2px 2px 0 rgba(143,144,145,1);-moz-box-shadow:2px 2px 2px 0 rgba(143,144,145,1);box-shadow:2px 2px 2px 0 rgba(143,144,145,1);padding:0;border:1px solid #c6c6c6;padding-top:5px}.jexcel_contextmenu a{display:block;color:#555;text-decoration:none;padding:6px 8px 6px 30px;width:250px;position:relative;cursor:default}.jexcel_contextmenu a span{color:#a1a192;float:right;margin-right:10px}.jexcel_contextmenu a:hover{background:#ebebeb}.jexcel_contextmenu hr{border:1px solid #e9e9e9;border-bottom:0}.jexcel .jexcel_arrow{float:right;position:relative;top:8px;left:-13px}.jexcel #jexcel_arrow{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #bbb;position:absolute;cursor:pointer}.jexcel .jdropdown-header{border:0!important;outline:0!important;width:100%!important;height:100%!important;padding-left:4px!important}.jexcel .jdropdown-container-header{padding:0;margin:0;height:inherit}.jexcel .jdropdown-picker{border:0!important;padding:0!important;width:inherit;height:inherit}.jexcel .jexcel_comments{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:top right}.jexcel .sp-replacer{margin:2px;border:0} +/*# sourceMappingURL=jquery.jexcel.min.css.map */ \ No newline at end of file diff --git a/histview2/static/common/css/jsuites.css b/histview2/static/common/css/jsuites.css new file mode 100644 index 0000000..43e8615 --- /dev/null +++ b/histview2/static/common/css/jsuites.css @@ -0,0 +1,2613 @@ + +/** + * (c) jSuites Javascript Web Components + * + * Website: https://jsuites.net + * Description: Create amazing web based applications. + * + * MIT License + * + */ + +:root { + --button-color: #298BA8; + --active-color: #007aff; +} + +.unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.jdragging { + opacity:0.2; + filter: alpha(opacity=20); +} + +.jupload.input { + padding-right:18px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z'/%3E%3C/svg%3E"); + background-position:top 50% right 5px; + background-repeat:no-repeat; + box-sizing: border-box; + background-size: initial; + height: 33px; + min-height: initial; + padding-right: 30px; +} + +.jupload img { + width: 100%; +} + +.jupload.input img { + width: initial; + max-width: 100%; + height: 100%; +} + +.jupload[data-multiple] { + padding: 10px; +} + +.jupload[data-multiple] img { + height: 70px; + width: 100px; + object-fit: cover; + margin-right: 5px; + margin-bottom: 5px; +} + +.jupload { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAACXBIWXMAAAsTAAALEwEAmpwYAAA7emlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzggNzkuMTU5ODI0LCAyMDE2LzA5LzE0LTAxOjA5OjAxICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoV2luZG93cyk8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTctMTItMzFUMjI6NDM6MzBaPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTgtMDEtMDFUMjA6MDM6MjNaPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxOC0wMS0wMVQyMDowMzoyM1o8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6ZDE3NGVkY2UtNWZkNC0wODRkLTkzNzAtZDRhODVmMzdkNjdiPC94bXBNTTpJbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD54bXAuZGlkOjBmYzcxZDEyLTg1MGUtY2E0NS05MzE4LWRhYjI5YWZhMmQ4MjwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjBmYzcxZDEyLTg1MGUtY2E0NS05MzE4LWRhYjI5YWZhMmQ4MjwveG1wTU06T3JpZ2luYWxEb2N1bWVudElEPgogICAgICAgICA8eG1wTU06SGlzdG9yeT4KICAgICAgICAgICAgPHJkZjpTZXE+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmNyZWF0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDowZmM3MWQxMi04NTBlLWNhNDUtOTMxOC1kYWIyOWFmYTJkODI8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTctMTItMzFUMjI6NDM6MzBaPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjk2MTA5Y2Y0LTEwM2ItNTc0Ny1hZWE3LWI5NDMxOWY0NWFkZjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxOC0wMS0wMVQyMDowMjoyMFo8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE3IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6ZDE3NGVkY2UtNWZkNC0wODRkLTkzNzAtZDRhODVmMzdkNjdiPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE4LTAxLTAxVDIwOjAzOjIzWjwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjY1NTM1PC9leGlmOkNvbG9yU3BhY2U+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj44MDwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj44MDwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+tc8AsgAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAGjUlEQVR42uycW2xURRjHf91SoEIBUQpYI8VSVBDExniLERNUJBpQVEDFqE1AEYkX0JCC0YjgBdEYDOIFeVEEFRBilKBogg+KiQoV5Q6Khla8lrZRBLo+zLdazs7ZM2d3zsJu55/sy3dmz5797Vy+y8wWxONxnNJXzCFwAB1AB9ABdHIAHUAH0AF0Cq929fX1ufbMA4HLgSqgHOgPdJJrfwPbgb3AJuBD4KsoH6agrq4uF6CVAncC4wVYGP0ALAVeAva0NYDFwOPARKBzhvc6ArwG1AC/toU5cDSwFXjAAjyAQmACsAOozneATwLLgdMM2/8jLxN1AxYBC/J1CL8F3BjQZhOwCqgFvgd+AuJAT6ACOBMYCVwUcJ91siDlDcC3gRtSXF8IzAe+M7xfOXC3TAOFPm3WA0NzAeDp8oUGASeKrUF60R6Zn6an6CnTgI1pfnYFMAcY43N9GTDueAR4sQylK8RvS0dzgYcsPc9U4Bmfa9XA4uMF4GXypUdkeJ8Z0nNs6hbgdZ9rPcK6OLZX4XbisH5iAd68COABvAHc63NtwbHsgf2B94BKC/faAFwY8Zy8ErhWYx8EbM42wAuAj4ETAtptllh1N9AiC0t/YLCnXV9xT6JUJ6AOKPHYX5HIJ2sAK4BvgQ4+17fJ5LxKIgudzgBGySS/3sAPtKWZwCyPrQk4ydQxtwFwH9BbY4/LIvBEyF5RIF8iG+oC/K7xEUfLEI98EVnsA28/MCQkPIDmLMIDOCDOu1fXZGMVrgJu19gbZCKuJTek62mV2QD4XIoFZT+5o10a2ylRA6wCLtXYp8uikUv6Webr1uoOdIwS4HiNbS/wFLmnZuCgx9Y+hVdhBeBwjW1+BF9uFvBqFiDGDW3a0Cus+gADPLYWVB7PpiaJn5YYZjMigles6W2NskJH0gN1RZ1EJcyWJnvi0hpUbSQK9dRwaDGIqtIGeLbG9o3lnveCT2YmCogVPqvwdpOIKB2A7TU2W+XCiQEZkSggTvGxl8m0NNc2wDKNrcXCF5mASoUFaQYw2xK8Iv4vyvtpmsTxGQHsLPPQLp9frI+FYftyiPY1liAeBs5DZYNS/XgjgTXpApwq4Gaj0k86DbK4YISBmOlwjreaw+8CzvEDJa7bojAATwU+QtUQSg0Wlr4WFwxT2Z4Ta1GZ9Md8rlcDY00ADpFfZViID7/D8oJxrCACPCIjQ6elqOK8ryM9GPg64AM+B1YAW1BJ0r9M/aaQC0YYiAWWne0FQFf0dZnngdsgOaHaXVySLj43XSY3zCRVNclSz9NpTgQRywfAVRr7WcDWmKaxDl4TKuU+LkN4kyOEF1XEMk5GmFf3eOfAKcD5moY/ypy4OsMHeTjDBSPMcF5o8X4NPs70eKAoMYSLJIDuoOl5AwRiJroJWIIq1BwM8DcLDFyPpoAESTEqN/m0aVYlQF1RtRPviB2ZWEQeRJ//Gm0BHqhttieTutJVgkrGBu0FbEbtvmoMgNjLErxEL3yH5L01oxIA79O8aTVqj7ENmWSpGw1DwhZUJTBIf1ieGlZoAFbGxG3pESLIjkolBsMXaVNC9qWrnZTFZJh69SV283v5oHrNlNA9JvGfV0scL+3cm1Q7iaFPKNY6XimTD0fFwuWaRrscqyR11Hgqh2IkZ5gPy7LtdLRKNX7gnzHgkMdYGDIx0FZUrrHVxVBHBLzDup/jlaSrNbbdMWCn5sIAxyspsrlZY18TQ3/mYoxjdpQmkFx8OgKsjAHvat4wlFZZVyce1diWA80x4DOf7Ma8LD+kaYhmGvLZ0ovoa0LP0mpZ1qXXq4Fzs/igiSRBY8BrH3bq0Ca6HlWt82od6iTBfyn9Lj6+Xx2q2nYwSz3Q9FhrE/ZSVX66BPjU51q/RLCR6IEH0KfCe8sQL85SmNRo+Ioa3tgU8Ga2jtS8RaUdPj7gTtQRqS/yfLHogErf+6Xykk52ekOTESm67AbUrvuyPARXAtyPSvz6wduK5myx7pzIcPy3N4D6Z4w3pc0O1IbybAwrWyqSubZUOsYw4DrU4Ro/bUEd3m4wAZjoie8bPtARVNkvVwAWypxu6gqtRW0u0i6kqU4qDUEVUirasANdQ8BhoVSbizai/uRmbhsEtxp1lCPwpJXpWbmBqD++uTWPQ7xm1NaVJeIomzmvIQ8bdpO0zpWozea9Aibf41WHgd9QhaJtqAPia4FfQnv/7l98M5P79zYH0AF0AB1AJwfQAXQAHUCnNPTvAKemiFU6nb96AAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-size: 100px; + background-position: center; + background-color: rgba(230, 230, 230, 0.1); + border: 1px dotted #eee; + cursor: pointer; + box-sizing: border-box; + width: 100%; + height: 100%; + min-height: 180px; +} + +.jupload-item { + padding-right: 22px; + border-radius: 1px; + display: inline-block; + position: relative; +} + +.jremove { + opacity: 0.2; + filter: alpha(opacity=20); +} + + +/** Animations **/ +.fade-in { + animation: fade-in 2s forwards; +} + +.fade-out { + animation: fade-out 1s forwards; +} + +.slide-left-in { + position: relative; + animation: slide-left-in 0.4s forwards; +} + +.slide-left-out { + position: relative; + animation: slide-left-out 0.4s forwards; +} + +.slide-right-in { + position: relative; + animation: slide-right-in 0.4s forwards; +} + +.slide-right-out { + position: relative; + animation: slide-right-out 0.4s forwards; +} + +.slide-top-in { + position: relative; + animation: slide-top-in 0.4s forwards; +} + +.slide-top-out { + position: relative; + animation: slide-top-out 0.2s forwards; +} + +.slide-bottom-in { + position: relative; + animation: slide-bottom-in 0.4s forwards; +} + +.slide-bottom-out { + position: relative; + animation: slide-bottom-out 0.1s forwards; +} + +/** Fadein and Fadeout **/ +@keyframes fade-in { + 0% { opacity: 0; } + 100% { opacity: 100; } +} + +@-webkit-keyframes fade-in { + 0% { opacity: 0; } + 100% { opacity: 100; } +} + +@keyframes fade-out { + 0% { opacity: 100; } + 100% { opacity: 0; } +} + +@-webkit-keyframes fade-out { + 0% { opacity: 100; } + 100% { opacity: 0; } +} + +/** Keyframes Left to Right **/ +@keyframes slide-left-in { + 0% { left: -100%; } + 100% { left: 0%; } +} + +@-webkit-keyframes slide-left-in { + 0% { left: -100%; } + 100% { left: 0%; } +} + +@keyframes slide-left-out { + 0% { left: 0%; } + 100% { left: -100%; } +} + +@-webkit-keyframes slide-left-out { + 0% { left: 0%; } + 100% { left: -100%; } +} + +/** Keyframes Right to Left **/ +@keyframes slide-right-in { + 0% { left: 100%; } + 100% { left: 0%; } +} + +@-webkit-keyframes slide-right-in +{ + 0% { left: 100%; } + 100% { left: 0%; } +} + +@keyframes slide-right-out { + 0% { left: 0%; } + 100% { left: 100%; } +} + +@-webkit-keyframes slide-right-out { + 0% { left: 0%; } + 100% { left: 100%; } +} + +/** Keyframes Top to Bottom **/ +@keyframes slide-top-in { + 0% { transform: translateY(-100%); } + 100% { transform: translateY(0%); } +} + +@-webkit-keyframes slide-top-in { + 0% { transform: translateY(-100%); } + 100% { -webkit-transform: translateY(0%); } +} + +@keyframes slide-top-out { + 0% { transform: translateY(0%); } + 100% { transform: translateY(-100%); } +} + +@-webkit-keyframes slide-top-out { + 0% { -webkit-transform: translateY(0%); } + 100% { -webkit-transform: translateY(-100%); } +} + +/** Keyframes Bottom to Top **/ +@keyframes slide-bottom-in { + 0% { transform: translateY(100%); } + 100% { transform: translateY(0%); } +} + +@-webkit-keyframes slide-bottom-in { + 0% { transform: translateY(100%); } + 100% { -webkit-transform: translateY(0%); } +} + +@keyframes slide-bottom-out { + 0% { transform: translateY(0%); } + 100% { transform: translateY(100%); } +} + +@-webkit-keyframes slide-bottom-out { + 0% { -webkit-transform: translateY(0%); } + 100% { -webkit-transform: translateY(100%); } +} + +@-webkit-keyframes spin { + from { + -webkit-transform:rotate(0deg); + } + to { + -webkit-transform:rotate(360deg); + } +} + +@keyframes spin { + from { + transform:rotate(0deg); + } + to { + transform:rotate(360deg); + } +} + +.jcalendar { + position:absolute; + z-index:9000; + display:none; + box-sizing:border-box; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-tap-highlight-color: transparent; + min-width:280px; +} + +.jcalendar.jcalendar-focus { + display:block; +} + +.jcalendar .jcalendar-backdrop { + position:fixed; + top:0px; + left:0px; + z-index:9000; + min-width:100%; + min-height:100%; + background-color:rgba(0,0,0,0.5); + border:0px; + padding:0px; + display:none; +} + +.jcalendar .jcalendar-container { + position:relative; + box-sizing:border-box; +} + +.jcalendar .jcalendar-content { + position:absolute; + z-index:9001; + -webkit-box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); + -moz-box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); + box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); + background-color:#fff; +} + +.jcalendar-header { + text-align:center; +} + +.jcalendar-header span { + margin-right:4px; + font-size:1.1em; + font-weight:bold; +} + +.jcalendar-prev { + cursor:pointer; + background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z' fill='%23000' /%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3C/svg%3E"); + background-position:center; + background-repeat:no-repeat; +} + +.jcalendar-next { + cursor:pointer; + background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z' fill='%23000' /%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3C/svg%3E"); + background-position:center; + background-repeat:no-repeat; +} + +.jcalendar-weekday { + font-weight: 600; + background-color: #fcfcfc; + padding: 14px; +} + +.jcalendar-table > table { + width:100%; + background-color:#fff; +} + +.jcalendar-table > table > thead { + cursor:pointer; +} + +.jcalendar-table thead td { + padding:10px; + height:40px; +} + +.jcalendar-table > table > tbody td { + box-sizing:border-box; + cursor:pointer; + padding:9px; + font-size:0.9em; +} + + +.jcalendar-table tfoot td { + padding:10px; +} + +.jcalendar-months td, .jcalendar-years td { + height:24px; +} + +.jcalendar-input { + padding-right:18px; + background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='gray'%3E%3Cpath d='M20 3h-1V1h-2v2H7V1H5v2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 18H4V8h16v13z'/%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3C/svg%3E"); + background-position:top 50% right 5px; + background-repeat:no-repeat; + box-sizing: border-box; +} + +.jcalendar-done { + -webkit-box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); + -moz-box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); + box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); + background-color:#fff; +} + +.jcalendar-update { + border:1px solid #ccc; + background-color:#fff; + border-radius:4px; + padding:5px; + width:100%; +} + +.jcalendar-container select { + width:55px; + display:inline-block; + border:0px; + padding:4px; + text-align:center; + font-size:1.1em; + user-select:none; + margin-right:10px; +} + +.jcalendar-container select:first-child { + margin-right:2px; +} + +.jcalendar-selected { + background-color:#eee; +} + +.jcalendar-reset, .jcalendar-confirm { + text-transform:uppercase; + cursor:pointer; + color: var(--active-color); +} + +.jcalendar-controls { + padding:15px; + + -webkit-box-sizing: border-box; + box-sizing: border-box; + vertical-align:middle; + + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + + -webkit-flex-flow: row wrap; + justify-content: space-between; + align-items:center; +} + +.jcalendar-controls div { + font-weight:bold; +} + +.jcalendar-fullsize { + position:fixed; + width:100%; + top:0px; + left:0px; +} + +.jcalendar-fullsize .jcalendar-content +{ + position:fixed; + width:100%; + left:0px; + bottom:0px; +} + +.jcalendar-focus.jcalendar-fullsize .jcalendar-backdrop { + display:block; +} + +.jcalendar-sunday { + color: red; +} +.jcalendar-disabled { + color: #ccc; +} + +.jcalendar-time { + display:flex; +} + + +.jcolor { + position: absolute; + display: none; + outline: none; +} + +.jcolor-input { + padding-right:18px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z'/%3E%3C/svg%3E"); + background-position:top 50% right 5px; + background-repeat:no-repeat; + box-sizing: border-box; +} + +.jcolor-content { + position: absolute; + left: 0px; + z-index: 9000; + user-select: none; + -webkit-font-smoothing: antialiased; + font-size: .875rem; + letter-spacing: .2px; + -webkit-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); + box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); + background-color:#fff; + box-sizing: border-box; + min-width: 260px; +} + +.jcolor-controls { + display: flex; + padding: 10px; + border-bottom: 1px solid #eee; + margin-bottom: 5px; +} + +.jcolor-controls div { + flex: 1; + font-size: 1em; + color: var(--active-color); + text-transform: uppercase; + font-weight: bold; + box-sizing: border-box; +} + +.jcolor-content table { + border-collapse: collapse; + box-sizing: border-box; +} + +.jcolor-focus { + display:block; +} + +.jcolor table { + width:100%; + height:100%; + min-height: 160px; +} + +.jcolor td { + padding: 7px; +} + +.jcolor-selected { + background-repeat:no-repeat; + background-size: 16px; + background-position: center; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z' fill='white'/%3E%3C/svg%3E"); +} + +.jcolor-fullscreen { + position: fixed; + bottom: 0px; + width:100%; + max-height: 290px; + border-radius: 0px; + box-sizing: border-box; +} + +.jcolor-fullscreen .jcolor-controls { + padding: 15px; + -webkit-box-shadow: 1px 0px 1px 0px rgba(0,0,0,0.39); + -moz-box-shadow: 1px 0px 1px 0px rgba(0,0,0,0.39); + box-shadow: 1px 0px 1px 0px rgba(0,0,0,0.39); +} + +.jcolor-reset { + text-align: left; +} + +.jcolor-close { + text-align: right; +} + +.jcolor-backdrop { + position: fixed; + top: 0px; + left: 0px; + min-width: 100%; + min-height: 100%; + background-color: rgba(0,0,0,0.5); + border: 0px; + padding: 0px; + z-index: 8000; + display: none; + + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome and Opera */ +} + +.jcolor-content .jtabs-content { + padding: 7px; +} + +.jcolor-grid tr:first-child > td:first-child { + border-top-left-radius: 3px; +} + +.jcolor-grid tr:first-child > td:last-child { + border-top-right-radius: 3px; +} + +.jcolor-grid tr:last-child > td:first-child { + border-bottom-left-radius: 3px; +} + +.jcolor-grid tr:last-child > td:last-child { + border-bottom-right-radius: 3px; +} + +.jcolor-hsl { + box-sizing: border-box; +} + +.jcolor-hsl > div { + height: 100%; + position: relative; +} + +.jcolor-hsl canvas { + display: block; + border-radius: 4px; + -webkit-user-drag: none; +} + +.jcolor-point { + height: 5px; + width: 5px; + background-color: #000; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 50%; +} + +.jcolor-sliders { + padding: 10px 20px 10px 10px; +} + +.jcolor-sliders input { + -webkit-appearance: none; + + height: 12px; + width: 80%; + + background: #d3d3d3; + opacity: 1; + + border-radius: 30px; + outline: none; +} + +.jcolor-sliders-input-subcontainer { + display: flex; + justify-content: space-between; + align-items: center; +} + +.jcolor-sliders-input-container { + margin-top: 4px; + line-height: 0.8em; + text-align: left; +} + +.jcolor-sliders-input-container > label { + font-size: 10px; + text-transform: uppercase; + color: #bbbbbd; +} + +.jcolor-sliders-input-subcontainer > input { + border: 0px; + padding: 1px; +} + +.jcolor-sliders-input-container input::-webkit-slider-thumb { + -webkit-appearance: none; + height: 12px; + width: 12px; + border-radius: 50%; + background: #000; + border: 2px solid #fff; + cursor: pointer; +} + +.jcolor-sliders-input-container input::-moz-range-thumb { + -webkit-appearance: none; + height: 12px; + width: 12px; + border-radius: 50%; + background: #000; + border: 2px solid #fff; + cursor: pointer; +} + +.jcolor-sliders-final-color { + padding: 6px; + user-select: all; + margin-top: 10px; + text-align: center; +} + +.jcolor-sliders-final-color > div:nth-child(2) { + width: 71px; + text-transform: uppercase; +} + +.jcolor .jtabs .jtabs-headers-container .jtabs-controls { + display: none !important; +} + +.jcolor .jtabs .jtabs-headers-container { + display: flex !important; + justify-content: center; + padding: 4px; +} + +.jcolor .jtabs-headers > div:not(.jtabs-border) { + padding: 2px !important; + padding-left: 15px !important; + padding-right: 15px !important; + font-size: 0.8em; +} + +.jcontextmenu { + position:fixed; + z-index:10000; + background:#fff; + color: #555; + font-size: 11px; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); + -moz-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); + box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); + border: 1px solid #C6C6C6; + padding: 0px; + padding-top:4px; + padding-bottom:4px; + margin:0px; + outline:none; + display:none; +} + +.jcontextmenu.jcontextmenu-focus { + display:inline-block; +} + +.jcontextmenu > div { + box-sizing: border-box; + display: flex; + padding: 8px 8px 8px 32px; + width: 250px; + position: relative; + cursor: default; + font-size: 11px; + font-family:sans-serif; +} + +.jcontextmenu > div::before { + content: attr(data-icon); + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 16px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; + position: absolute; + left: 9px; +} + +.jcontextmenu > div a { + color: #555; + text-decoration: none; + flex: 1; +} + +.jcontextmenu > div span { + margin-right:10px; +} + +.jcontextmenu .jcontextmenu-disabled a { + color: #ccc; +} + +.jcontextmenu > div:hover { + background: #ebebeb; +} + +.jcontextmenu hr { + border: 1px solid #e9e9e9; + border-bottom: 0; + margin-top:5px; + margin-bottom:5px; +} + +.jcontextmenu > hr:hover { + background: transparent; +} + +.jcontextmenu .jcontextmenu { + top: 4px; + left: 99%; + opacity: 0; + position: absolute; +} + +.jcontextmenu > div:hover > .jcontextmenu { + display: block; + opacity: 1; + -webkit-transform: translate(0, 0) scale(1); + transform: translate(0, 0) scale(1); + pointer-events: auto; +} + +.jdropdown { + cursor:pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + box-sizing: border-box; + background:#fff; + -webkit-tap-highlight-color: transparent; + display: inline-block; +} + +.jdropdown-header::placeholder { + color:#000; +} + +.jdropdown-backdrop { + position:fixed; + top:0px; + left:0px; + min-width:100%; + min-height:100%; + background-color:rgba(0,0,0,0.5); + border:0px; + padding:0px; + z-index:8000; + display:none; +} + +.jdropdown-focus { + position:relative; +} + +.jdropdown-focus .jdropdown-container { + transform: translate3d(0,0,0); +} + +.jdropdown-default.jdropdown-focus .jdropdown-header { + outline:auto 5px -webkit-focus-ring-color; +} + +.jdropdown-default.jdropdown-focus .jdropdown-header.jdropdown-add { + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='24px' height='24px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z'/%3E%3C/svg%3E"); +} + +.jdropdown-container-header +{ + padding:0px; + margin:0px; + position:relative; +} + +.jdropdown-header +{ + width:100%; + appearance: none; + background-repeat: no-repeat; + background-position:top 50% right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E"); + text-overflow: ellipsis; + cursor:pointer; + box-sizing: border-box; + -webkit-appearance: none; + -moz-appearance: none; + padding-right:30px !important; +} + +.jdropdown-insert-button +{ + font-size: 1.4em; + text-transform: uppercase; + position:absolute; + right: 30px; + top: 4px; + display:none; +} + +.jdropdown-container +{ + min-width: inherit; + transform: translate3d(-10000px,0,0); + position:absolute; + z-index:9001; +} + +.jdropdown-close +{ + display:none; + font-size:1em; + color: var(--active-color); + text-transform:uppercase; + text-align:right; + padding:15px; + font-weight:bold; +} + +.jdropdown-content +{ + min-width:inherit; + margin:0px; + box-sizing:border-box; +} + +.jdropdown-content:empty +{ +} + +.jdropdown-item +{ + white-space: nowrap; + text-align: left; + text-overflow: ellipsis; + overflow-x: hidden; + color: #000; + display: flex; + align-items: center; +} + +.jdropdown-description +{ + text-overflow: ellipsis; + overflow: hidden; + line-height: 1.5em; +} + +.jdropdown-image +{ + margin-right:10px; + width: 32px; + height: 32px; + border-radius:20px; +} + +.jdropdown-image-small +{ + width:24px; + height:24px; +} + +.jdropdown-title +{ + font-size: 0.7em; + text-overflow: ellipsis; + overflow-x: hidden; + display: block; +} + +/** Default visual **/ + +.jdropdown-default .jdropdown-header +{ + border:1px solid #ccc; + padding:5px; + padding-left:10px; + padding-right:16px; +} + +.jdropdown-default .jdropdown-container +{ + background-color:#fff; +} + +.jdropdown-default.jdropdown-focus.jdropdown-insert .jdropdown-header { + padding-right:50px; +} + +.jdropdown-default.jdropdown-focus.jdropdown-insert .jdropdown-insert-button { + display:block; +} + +.jdropdown-default .jdropdown-content +{ + min-width:inherit; + border:1px solid #8fb1e3; + margin:0px; + background-color:#fff; + box-sizing:border-box; + min-height:10px; + max-height:215px; + overflow-y:auto; +} + +.jdropdown-default .jdropdown-item +{ + padding:4px; + padding-left:8px; + padding-right:40px; +} + +.jdropdown-default .jdropdown-item:hover +{ + background-color:#1f93ff; + color:#fff; +} + +.jdropdown-default .jdropdown-cursor +{ + background-color:#eee; +} + +.jdropdown-default .jdropdown-selected +{ + background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIiAvPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiIGZpbGw9IndoaXRlIiAvPjwvc3ZnPgo='); + background-repeat:no-repeat; + background-position:top 50% right 5px; + background-color:#1f93ff; + color:#fff; +} + +.jdropdown-default .jdropdown-group { + margin-top:5px; +} + +.jdropdown-default .jdropdown-group .jdropdown-item { + padding-left:16px; +} + +.jdropdown-default .jdropdown-group-name { + padding-left: 8px; + font-weight: bold; + text-align: left; +} + +.jdropdown-default .jdropdown-reset_ { + content:'x'; + position:absolute; + top:0; + right:0; + margin:5px; + margin-right:10px; + font-size:12px; + width:12px; + cursor:pointer; + text-shadow: 0px 0px 5px #fff; + display:none; + line-height: 1.8em; +} + +.jdropdown-default.jdropdown-focus .jdropdown-reset_ { + display:block; +} + +/** Default render for mobile **/ + +.jdropdown-picker.jdropdown-focus .jdropdown-backdrop { + display:block; +} + +.jdropdown-picker .jdropdown-header { + outline: none; +} + +.jdropdown-picker .jdropdown-container +{ + position:fixed; + bottom:0px; + left:0px; + border-bottom:1px solid #e6e6e8; + width:100%; + background-color:#fff; + box-sizing: border-box; +} + +.jdropdown-picker .jdropdown-close +{ + -webkit-box-shadow: 0px -1px 5px 0px rgba(0,0,0,0.39); + -moz-box-shadow: 0px -1px 5px 0px rgba(0,0,0,0.39); + box-shadow: 0px -1px 5px 0px rgba(0,0,0,0.39); + background-color:#fff; + display:block; +} + +.jdropdown-picker .jdropdown-content +{ + overflow-y:scroll; + height:280px; + background-color:#fafafa; + border-top:1px solid #e6e6e8; +} + +.jdropdown-picker .jdropdown-group-name +{ + font-size: 1em; + text-transform: uppercase; + padding-top:10px; + padding-bottom:10px; + display: block; + border-bottom: 1px solid #e6e6e8; + padding-left:20px; + padding-right:20px; + text-align:center; + font-weight:bold; +} + +.jdropdown-picker .jdropdown-item +{ + font-size: 1em; + text-transform: uppercase; + padding-top:10px; + padding-bottom:10px; + border-bottom: 1px solid #e6e6e8; + padding-left:20px; + padding-right:20px; +} + +.jdropdown-picker .jdropdown-selected +{ + background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIiAvPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiIGZpbGw9IndoaXRlIiAvPjwvc3ZnPgo='); + background-repeat:no-repeat; + background-position:top 50% right 15px; + background-color:#1f93ff; + color:#fff; +} + +.jdropdown-picker .jdropdown-cursor +{ + background-color:#1f93ff; + color:#fff; +} + +/** Default render for mobile searchbar **/ + +.jdropdown-searchbar.jdropdown-focus +{ + position:fixed; + top:0px !important; + left:0px !important; + width:100% !important; + height:100% !important; + background-color:#fafafa; + padding:0px; + z-index:9001; + overflow-y:scroll; + will-change: scroll-position; + -webkit-overflow-scrolling: touch; +} + +.jdropdown-searchbar.jdropdown-focus .jdropdown-container-header +{ + position: fixed; + top: 0px; + left: 0px; + z-index: 9002; + padding:10px; + background-color:#fff; + box-shadow: 0 1px 2px rgba(0,0,0,.1); + max-height: 24px; + width: 100%; +} + +.jdropdown-searchbar.jdropdown-focus .jdropdown-header +{ + border: 0px; + background-repeat: no-repeat; + background-position-x: 0%; + background-position-y: 40%; + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTUuNSAxNGgtLjc5bC0uMjgtLjI3QzE1LjQxIDEyLjU5IDE2IDExLjExIDE2IDkuNSAxNiA1LjkxIDEzLjA5IDMgOS41IDNTMyA1LjkxIDMgOS41IDUuOTEgMTYgOS41IDE2YzEuNjEgMCAzLjA5LS41OSA0LjIzLTEuNTdsLjI3LjI4di43OWw1IDQuOTlMMjAuNDkgMTlsLTQuOTktNXptLTYgMEM3LjAxIDE0IDUgMTEuOTkgNSA5LjVTNy4wMSA1IDkuNSA1IDE0IDcuMDEgMTQgOS41IDExLjk5IDE0IDkuNSAxNHoiIGZpbGw9IiNlNmU2ZTgiLz48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+); + padding-left: 30px !important; + padding-right: 60px !important; +} + +.jdropdown-searchbar.jdropdown-focus .jdropdown-close +{ + display:block; +} + +.jdropdown-searchbar .jdropdown-header { + outline: none; +} + +.jdropdown-searchbar .jdropdown-container +{ + margin-top: 40px; + width:100%; +} + +.jdropdown-searchbar .jdropdown-close +{ + position:fixed; + top:0px; + right:0px; +} + +.jdropdown-searchbar .jdropdown-content +{ + margin-top:10px; +} + +.jdropdown-searchbar .jdropdown-group +{ + margin-top:10px; + margin-bottom:15px; + background-color:#fff; +} + +.jdropdown-searchbar .jdropdown-group-name +{ + border-top: 1px solid #e6e6e8; + border-bottom: 1px solid #e6e6e8; + padding:10px; + padding-left:12px; + font-weight:bold; +} + +.jdropdown-searchbar .jdropdown-group-arrow +{ + float:right; + width:24px; + height:24px; + background-repeat:no-repeat; +} + +.jdropdown-searchbar .jdropdown-group-arrow-down +{ + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNNy40MSA4LjU5TDEyIDEzLjE3bDQuNTktNC41OEwxOCAxMGwtNiA2LTYtNiAxLjQxLTEuNDF6Ii8+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyNHYyNEgwVjB6Ii8+PC9zdmc+); +} + +.jdropdown-searchbar .jdropdown-group-arrow-up +{ + background-image: url(data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTcuNDEgMTUuNDFMMTIgMTAuODNsNC41OSA0LjU4TDE4IDE0bC02LTYtNiA2eiIvPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48L3N2Zz4=); +} + +.jdropdown-searchbar .jdropdown-item +{ + padding-top:10px; + padding-bottom:10px; + border-bottom: 1px solid #e6e6e8; + padding-left:15px; + padding-right:40px; + background-color:#fff; + font-size:0.9em; +} + +.jdropdown-searchbar .jdropdown-description { + text-overflow: ellipsis; + overflow: hidden; + max-width: calc(100% - 20px); +} + +.jdropdown-searchbar .jdropdown-content > .jdropdown-item:first-child +{ + border-top: 1px solid #e6e6e8; +} + +.jdropdown-searchbar .jdropdown-selected +{ + background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTkgMTYuMTdMNC44MyAxMmwtMS40MiAxLjQxTDkgMTkgMjEgN2wtMS40MS0xLjQxeiIgZmlsbD0iIzAwN2FmZiIvPjwvc3ZnPg=='); + background-repeat:no-repeat; + background-position:top 50% right 15px; +} + +/** List render **/ + +.jdropdown-list +{ +} + +.jdropdown-list .jdropdown-container +{ + display:block; +} + +.jdropdown-list .jdropdown-header +{ + display:none; +} + +.jdropdown-list .jdropdown-group +{ + background-color:#fff; +} + +.jdropdown-list .jdropdown-group-name +{ + border-bottom: 1px solid #e6e6e8; + padding-top:10px; + padding-bottom:10px; + font-weight:bold; +} + +.jdropdown-list .jdropdown-item +{ + padding-top:10px; + padding-bottom:10px; + border-bottom: 1px solid #e6e6e8; + padding-left:10px; + padding-right:40px; + background-color:#fff; +} + +.jdropdown-list .jdropdown-selected +{ + background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTkgMTYuMTdMNC44MyAxMmwtMS40MiAxLjQxTDkgMTkgMjEgN2wtMS40MS0xLjQxeiIgZmlsbD0iIzAwN2FmZiIvPjwvc3ZnPg=='); + background-repeat:no-repeat; + background-position:top 50% right 10px; +} + +@media only screen and (max-width : 800px) +{ + .jdropdown-list { + width:100% !important; + border:0px; + padding:0px; + } + + .jdropdown-list .jdropdown-container { + min-width:100%; + } + + .jdropdown-searchbar.jdropdown-focus .jdropdown-description { + text-transform: uppercase; + } +} + +.app .jdropdown-item { + text-transform:uppercase; +} + +.jdropdown-create-container { + margin: 10px; + border: 1px solid #ccc; + border-radius: 2px; + padding: 6px; +} + +.jdropdown-color { + background-color: #fff; + border: 1px solid transparent; + border-radius: 12px; + width: 12px; + height: 12px; + margin-right: 6px; +} + +.jdropdown-item[data-disabled] { + opacity: 0.5; + pointer-events: none; +} + +.jeditor-container { + border:1px solid #ccc; + box-sizing: border-box; +} + +.jeditor-dragging { + border:1px dashed #000; +} + +.jeditor-container.jeditor-padding { + padding:10px; +} + +.jeditor { + outline:none; + word-break: break-word; + +} + +.jeditor-container.jeditor-padding .jeditor { + min-height:100px; + margin-bottom:10px; + padding:10px; +} + +/** Snippet **/ + +.jsnippet { + margin-top:15px; + cursor:pointer; + border: 1px solid #ccc; + position:relative; +} + +.jsnippet:focus { + outline: none; +} + +.jsnippet img { + width:100%; +} + +.jsnippet .jsnippet-title { + padding:15px; + font-size:1.4em; +} + +.jsnippet .jsnippet-description { + padding-left:15px; + padding-right:15px; + font-size:1em; +} + +.jsnippet .jsnippet-host { + padding:15px; + text-transform:uppercase; + font-size:0.8em; + color:#777; + text-align:right; +} + +.jsnippet .jsnippet-url { + display:none; +} + +.jeditor .jsnippet:after { + content: 'close'; + font-family: 'Material icons'; + font-size: 24px; + width: 24px; + height: 24px; + line-height: 24px; + cursor: pointer; + text-shadow: 0px 0px 2px #fff; + position: absolute; + top: 12px; + right: 12px; +} + +.jsnippet * { + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; + + -webkit-user-drag: none; + -khtml-user-drag: none; + -moz-user-drag: none; + -o-user-drag: none; +} + +.jeditor img { + border:2px solid transparent; + box-sizing: border-box; +} + +.jeditor img.resizing { + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; + + -webkit-user-drag: none; + -khtml-user-drag: none; + -moz-user-drag: none; + -o-user-drag: none; +} + +.jeditor img:focus { + border:2px solid #0096FD; + outline: #0096FD; +} + +.jeditor .pdf { + background-image: url("data:image/svg+xml,%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 512 512' style='enable-background:new 0 0 512 512;' xml:space='preserve'%3E%3Cpath style='fill:%23C30B15;' d='M511.344,274.266C511.77,268.231,512,262.143,512,256C512,114.615,397.385,0,256,0S0,114.615,0,256 c0,117.769,79.53,216.949,187.809,246.801L511.344,274.266z'/%3E%3Cpath style='fill:%2385080E;' d='M511.344,274.266L314.991,77.913L119.096,434.087l68.714,68.714C209.522,508.787,232.385,512,256,512 C391.243,512,501.976,407.125,511.344,274.266z'/%3E%3Cpolygon style='fill:%23FFFFFF;' points='278.328,333.913 255.711,77.913 119.096,77.913 119.096,311.652 '/%3E%3Cpolygon style='fill:%23E8E6E6;' points='392.904,311.652 392.904,155.826 337.252,133.565 314.991,77.913 255.711,77.913 256.067,333.913 '/%3E%3Cpolygon style='fill:%23FFFFFF;' points='314.991,155.826 314.991,77.913 392.904,155.826 '/%3E%3Crect x='119.096' y='311.652' style='fill:%23FC0F1A;' width='273.809' height='122.435'/%3E%3Cg%3E%3Cpath style='fill:%23FFFFFF;' d='M204.871,346.387c13.547,0,21.341,6.659,21.341,18.465c0,12.412-7.795,19.601-21.341,19.601h-9.611 v14.909h-13.471v-52.975L204.871,346.387L204.871,346.387z M195.26,373.858h8.93c5.904,0,9.308-2.952,9.308-8.552 c0-5.525-3.406-8.324-9.308-8.324h-8.93V373.858z'/%3E%3Cpath style='fill:%23FFFFFF;' d='M257.928,346.387c16.649,0,28.152,10.746,28.152,26.487c0,15.666-11.655,26.488-28.683,26.488 h-22.25v-52.975H257.928z M248.619,388.615h9.611c8.249,0,14.151-6.357,14.151-15.665c0-9.384-6.205-15.817-14.757-15.817h-9.006 V388.615z'/%3E%3Cpath style='fill:%23FFFFFF;' d='M308.563,356.982v12.26h23.763v10.596h-23.763v19.525h-13.471v-52.975h39.277v10.595h-25.806 V356.982z'/%3E%3C/g%3E%3C/svg%3E%0A"); + background-repeat: no-repeat; + background-size: cover; + width:60px; + height:60px; +} + + +.jloading { + position:fixed; + z-index:10001; + width:100%; + left:0; + right:0; + top:0; + bottom:0; + background-color: rgba(0,0,0,0.7); +} + +.jloading::after { + content:''; + display:block; + margin:0 auto; + margin-top:50vh; + width:40px; + height:40px; + border-style:solid; + border-color:white; + border-top-color:transparent; + border-width:4px; + border-radius:50%; + -webkit-animation: spin .8s linear infinite; + animation: spin .8s linear infinite; +} + +.jloading.spin { + background-color:transparent; +} + +.jloading.spin::after { + margin:0 auto; + margin-top:80px; + border-color:#aaa; + border-top-color:transparent; +} + + +.jmodal { + position:fixed; + top:50%; + left:50%; + width:60%; + height:60%; + -webkit-box-shadow: 0 2px 10px rgba(0,0,0,.2); + -moz-box-shadow: 0 2px 10px rgba(0,0,0,.2); + border:1px solid #ccc; + background-color:#fff; + transform: translate(-50%, -50%); + box-sizing: border-box; + padding-top:50px !important; + z-index:9002; + border-radius: 8px; +} + +.jmodal:before { + position:absolute; + top:0; + left:0; + width:100%; + content:attr(title); + padding:15px; + box-sizing: border-box; + font-size:1.2em; + box-shadow: 1px 1px 3px rgba(0,0,0,.2); + background-color: #fff; + border-radius: 8px 8px 0px 0px; +} + +.jmodal_content { + padding:20px; + overflow-y:auto; + max-height:100%; + box-sizing: border-box; + height: -webkit-fill-available; +} +.jmodal.no-title { + padding-top: initial !important; +} + +.jmodal.no-title:before { + display:none; +} + +.jmodal:after { + content:''; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + position:absolute; + top:0; + right:0; + margin:14px; + font-size:24px; + width:24px; + height:24px; + cursor:pointer; + text-shadow: 0px 0px 5px #fff; +} + +.jmodal_fullscreen { + width: 100% !important; + height: 100% !important; + top: 0px; + left: 0px; + transform: none; + border-radius: 0px; +} + + +.jmodal_backdrop { + position: fixed; + top: 0px; + left: 0px; + min-width: 100%; + min-height: 100%; + background-color: rgba(0,0,0,0.5); + border: 0px; + padding: 0px; + z-index: 8000; + display: none; + + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome and Opera */ +} + + +.jnotification { + position: fixed; + z-index: 10000; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 10px; + bottom: 0px; +} + +.jnotification-container { + -webkit-box-shadow: 0px 2px 15px -5px rgba(0, 0, 0, 0.7); + box-shadow: 0px 2px 15px -5px rgba(0, 0, 0, 0.7); + padding: 12px; + border-radius: 8px; + + background-color: #000; + background: rgba(92,92,92,1); + background: linear-gradient(0deg, rgba(92,92,92,1) 0%, rgba(77,77,77,1) 100%); + color: #fff; + width: 320px; + margin: 30px; + padding: 20px; +} + +.jnotification-close { + content: ''; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + font-size: 20px; + width: 20px; + height: 20px; + cursor: pointer; +} + +.jnotification-title { + font-weight: bold; +} + +.jnotification-header { + display: flex; + padding-bottom: 5px; +} + +.jnotification-header:empty { + display: none; +} + +.jnotification-image { + margin-right: 5px; +} + +.jnotification-image:empty { + display: none; +} + +.jnotification-image img { + width: 24px; +} + +.jnotification-name { + text-transform: uppercase; + font-size: 0.9em; + flex: 1; + letter-spacing: 0.1em; +} + +.jnotification-error .jnotification-container { + background: rgb(182,38,6); + background: linear-gradient(0deg, rgba(170,41,13,1) 0%, rgba(149,11,11,1) 100%); +} + +@media (max-width: 800px) { + .jnotification { + top: 0px; + width: 100%; + } + .jnotification-container { + background: rgba(255,255,255,0.95); + border: 1px solid #eee; + color: #444; + margin: 0px; + width: initial; + } + .jnotification-error .jnotification-container { + background: rgba(255,255,255,0.95); + color: #790909; + } + .jnotification-close { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='black'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + } +} + +.jnotification-header { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; +} + +.jpicker { + cursor: pointer; + white-space: nowrap; + display: inline-flex; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + outline: none; + position: relative; +} + +.jpicker-header { + background-repeat: no-repeat; + background-position: top 50% right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E"); + text-overflow: ellipsis; + cursor: pointer; + box-sizing: border-box; + text-align: left; + outline: none; + + line-height: 24px; + padding: 2px; + padding-left: 12px; + padding-right: 35px; + outline: none; + border-radius: 4px; +} + +.jpicker-header:hover { + background-color: #eee; +} + +.jpicker-content { + position: absolute; + display: none; + box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2); + border-radius: 4px; + background-color: #fff; + padding: 4px; + z-index: 50; + text-align: left; + max-height: 200px; + scrollbar-width: thin; + scrollbar-color: #333 transparent; +} + +.jpicker-content::-webkit-scrollbar { + width: 8px; +} + +.jpicker-content::-webkit-scrollbar-track { + background: #eee; +} + +.jpicker-content::-webkit-scrollbar-thumb { + background: #888; +} + +.jpicker-content > div { + padding: 6px; + padding-left: 15px; + padding-right: 15px; +} + +.jpicker-focus > .jpicker-content { + display: block; +} + +.jpicker-content > div:hover { + background-color:#efefef; +} + +.jpicker-content > div:empty { + opacity: 0; +} + +.jpicker-header > i, .jpicker-header > div { + display: block; +} + +.jpicker-focus > .jpicker-content.jpicker-columns { + display: flex !important ; + justify-content: center; + flex-wrap: wrap; +} + + +.jprogressbar +{ + cursor:pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + box-sizing: border-box; + background:#fff; + -webkit-tap-highlight-color: transparent; + display: inline-block; + box-sizing: border-box; + cursor:pointer; + border:1px solid #ccc; + position:relative; +} + +.jprogressbar::before { + content:attr(data-value); + position:absolute; + margin:5px; + margin-left:10px; +} + +.jprogressbar-header::placeholder +{ + color:#000; +} + +.jprogressbar::focus { + outline: auto 5px -webkit-focus-ring-color; +} + +.jprogressbar > div { + background-color: #eee; + background-color: red; + box-sizing: border-box; + height:31px; +} + +.jrating { + display:flex; +} +.jrating > div { + width:24px; + height:24px; + line-height:24px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z' fill='gray'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); +} + +.jrating .jrating-over { + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='black'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + opacity: 0.7; +} + +.jrating .jrating-selected { + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='red'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); +} + + +.jsearch { + position: relative; + display: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.jsearch_container { + position: absolute; + box-shadow: 0 1px 2px 0 rgba(60,64,67,0.302), 0 2px 6px 2px rgba(60,64,67,0.149); + border: none; + -webkit-border-radius: 4px; + border-radius: 4px; + width: 280px; + padding: 8px 0; + + -webkit-box-shadow: 0 2px 4px rgba(0,0,0,0.2); + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + -webkit-transition: opacity .218s; + transition: opacity .218s; + background: #fff; + border: 1px solid rgba(0,0,0,.2); + cursor: pointer; + margin: 0; + min-width: 300px; + outline: none; + width: auto; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.jsearch_container:empty:after { + content: attr(data-placeholder); +} + +.jsearch_container > div { + color: #333; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: flex; + padding: 5px 10px; + user-select: none; + -webkit-align-items: center; + align-items: center; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.jsearch_container > div:hover { + background-color: #e8eaed; +} + +.jsearch_container > div > img { + width: 32px; + height: 32px; + user-select: none; + border-radius: 16px; + margin-right: 2px; +} + +.jsearch_container > div > div { + overflow: hidden; + text-overflow: ellipsis; + margin-left: 2px; + max-width: 300px; + white-space: nowrap; + user-select: none; +} + +.jsearch_container .selected { + background-color: #e8eaed; +} + +.jslider { + outline: none; +} + +.jslider-focus { + width: 100% !important; + height: 100% !important; +} + +.jslider-focus img { + display: none; +} + +.jslider img { + width: 100px; +} + +.jslider-left::before { + position: fixed; + left: 15px; + top: 50%; + content:'arrow_back_ios'; + color: #fff; + width: 30px; + height: 30px; + font-family: 'Material Icons'; + font-size: 30px; + text-shadow: 0px 0px 0px #000; + text-align: center; + cursor: pointer; +} + +.jslider-right::after { + position: fixed; + right: 15px; + top: 50%; + content: 'arrow_forward_ios'; + color: #fff; + width: 30px; + height: 30px; + font-family: 'Material Icons'; + font-size: 30px; + text-shadow: 0px 0px 0px #000; + text-align: center; + cursor: pointer; +} + +.jslider-close { + width:24px; + height:24px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + position:fixed; + top:15px; + right:15px; + cursor:pointer; + z-index:3000; +} + +.jslider-counter { + height:24px; + background-color: transparent; + position:fixed; + left: 50%; + transform: translateX(-50%); + bottom: 15px; + cursor:pointer; + z-index:3000; + + display: flex; + display: -webkit-flex; + -webkit-justify-content: center; + -webkit-align-items: center; + -webkit-flex-direction: row; + justify-content: center; + align-items: center; + flex-direction: row; +} + +.jslider-caption { + position: fixed; + max-width: 90vw; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + top:15px; + left: 15px; + z-index:3000; + color: #FFF; + font-size: 1rem; +} + +.jslider-counter div { + width: 10px; + height: 10px; + background: #fff; + border-radius: 50%; + margin: 0px 5px; +} + +.jslider-counter .jslider-counter-focus { + background-color: cornflowerblue; + pointer-events: none; +} + +.jslider-focus { + position:fixed; + left:0; + top:0; + width: 100%; + min-height:100%; + max-height:100%; + z-index:2000; + margin:0px; + box-sizing:border-box; + + background-color:rgba(0,0,0,0.8); + -webkit-transition-duration: .05s; + transition-duration: .05s; + display: flex; + -ms-flex-align: center; + -webkit-align-items: center; + -webkit-box-align: center; + + align-items: center; +} + +.jslider-focus img { + width: 50vw; + height: auto; + box-sizing: border-box; + margin:0 auto; + vertical-align:middle; + display:none; +} + +.jslider-focus img.jslider-vertical { + width: auto; + height: 50vh; +} + +.jslider-grid { + display: -ms-grid; + display: grid; + grid-gap: 1px; + position: relative; +} + +.jslider-grid[data-number='2'] { + -ms-grid-columns: 1fr 50%; + grid-template-columns: 1fr 50%; +} + +.jslider-grid[data-number='3'] { + -ms-grid-columns: 1fr 33%; + grid-template-columns: 1fr 33%; +} + +.jslider-grid[data-number='4'] { + -ms-grid-columns: 1fr 25%; + grid-template-columns: 1fr 25%; +} + +.jslider-grid img { + display: none; + width: 100%; + height: 100%; + object-fit: cover; +} + +.jslider-grid[data-total]:after { + content: attr(data-total) "+"; + font-size: 1.5em; + position:absolute; + color: #fff; + right: 15px; + bottom: 6px; +} + +.jslider-grid img:first-child { + -ms-grid-column: 1; + -ms-grid-row: 1; + grid-column: 1; + grid-row: 1; + display: block; +} + +.jslider-grid[data-number='2'] img:nth-child(2) { + -ms-grid-column: 2; + -ms-grid-row: 1; + grid-column: 2; + grid-row: 1; + display: block; +} + +.jslider-grid[data-number='3'] img:first-child { + -ms-grid-column: 1 / 2; + -ms-grid-row: 1 / 4; + grid-column: 1 / 2; + grid-row: 1 / 4; +} + +.jslider-grid[data-number='3'] img:nth-child(2) { + -ms-grid-column: 2; + -ms-grid-row: 1; + grid-column: 2; + grid-row: 1; + display: block; +} + +.jslider-grid[data-number='3'] img:nth-child(3) { + -ms-grid-column: 2; + -ms-grid-row: 2; + grid-column: 2; + grid-row: 2; + display: block; +} + +.jslider-grid[data-number='4'] img:first-child { + -ms-grid-column: 1 / 2; + -ms-grid-row: 1 / 4; + grid-column: 1 / 2; + grid-row: 1 / 4; +} + +.jslider-grid[data-number='4'] img:nth-child(2) { + -ms-grid-column: 2; + -ms-grid-row: 1; + grid-column: 2; + grid-row: 1; + display: block; +} + +.jslider-grid[data-number='4'] img:nth-child(3) { + -ms-grid-column: 2; + -ms-grid-row: 2; + grid-column: 2; + grid-row: 2; + display: block; +} + +.jslider-grid[data-number='4'] img:nth-child(4) { + -ms-grid-column: 2; + -ms-grid-row: 3; + grid-column: 2; + grid-row: 3; + display: block; +} + + +.jtabs { + max-width: 100vw; + position: relative; +} + +.jtabs .jtabs-headers-container { + display: flex; + align-items: center; +} + +.jtabs .jtabs-headers { + display: flex; + align-items: center; + overflow: hidden; + position: relative; +} + +.jtabs { + max-width: 100vw; + position: relative; +} + +.jtabs .jtabs-headers-container { + display: flex; + align-items: center; +} + +.jtabs .jtabs-headers { + display: flex; + align-items: center; + overflow: hidden; + position: relative; +} + +.jtabs .jtabs-headers > div:not(.jtabs-border) { + padding: 6px; + padding-left: 20px; + padding-right: 20px; + margin-left: 1px; + margin-right: 1px; + margin-bottom: 1px; + background-color: #f1f1f1; + cursor: pointer; + white-space: nowrap; + text-align: center; +} + +.jtabs .jtabs-headers > div.jtabs-selected { + background-color: #e8e8e8; + color: #000; +} + +.jtabs .jtabs-headers > div > div { + color: #555; + width: 100%; + overflow: hidden; +} + +.jtabs .jtabs-headers i { + display: block; + margin: auto; +} + +.jtabs .jtabs-content { + box-sizing: border-box; +} + +.jtabs .jtabs-content > div { + display: none; + box-sizing: border-box; +} + +.jtabs .jtabs-content > div.jtabs-selected { + display: block; +} + +.jtabs .jtabs-border { + position: absolute; + height: 2px; + background-color: #888; + transform-origin: left; + transition: all .2s cubic-bezier(0.4,0,0.2,1); + transition-property: color,left,transform; + display: none; +} + +.jtabs-animation .jtabs-border { + display: initial; +} + +.jtabs .jtabs-controls { + margin: 3px; + margin-left: 10px; + display: flex; + min-width: 82px; +} + +.jtabs .jtabs-controls > div { + cursor: pointer; + background-position: center; + background-repeat: no-repeat; + width: 24px; + height: 24px; + line-height: 24px; +} + +.jtabs .jtabs-prev { + margin-left: 10px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'/%3E%3C/svg%3E"); +} + +.jtabs .jtabs-prev.disabled { + margin-left: 10px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='lightgray' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'/%3E%3C/svg%3E"); +} + +.jtabs .jtabs-next { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E"); +} + +.jtabs .jtabs-next.disabled { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='lightgray' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E"); +} + +.jtabs .jtabs-add { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 0 24 24' width='24'%3E%3Cpath d='M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z' fill='%23bbbbbb'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); +} + +/** Modern skin **/ + +.jtabs.jtabs-modern .jtabs-headers > div:not(.jtabs-border) { + padding: 4px; + padding-left: 10px; + padding-right: 10px; + background-color: #fff; +} + +.jtabs.jtabs-modern .jtabs-headers > .jtabs-selected { + color: #000; +} + +.jtabs.jtabs-modern .jtabs-headers > .jtabs-selected .material-icons { + color: #000; +} + +.jtabs .jtabs-headers { + font-size: .9rem; +} + +.jtabs.jtabs-modern .jtabs-headers { + background: #EEEEEF !important; + padding: 2px; + border-radius: 4px; +} + +.jtabs.jtabs-modern .jtabs-headers .jtabs-border { + border-color: #EEEEEF !important; +} + +.jtabs.jtabs-modern .jtabs-border { + background-color: rgba(194, 197, 188, 0.884); +} + +.jtags { + display: flex; + flex-wrap: wrap; + -ms-flex-direction: row; + -webkit-flex-direction: row; + flex-direction: row; + -ms-flex-pack: flex-start; + -webkit-justify-content: space-between; + justify-content: flex-start; + padding: 2px; + border: 1px solid #ccc; +} + +.jtags.jtags-empty:not(.jtags-focus)::before { + position: absolute; + margin: 5px; + color: #ccc; + content:attr(data-placeholder); +} + +.jtags > div { + padding: 3px; + padding-left: 10px; + padding-right: 22px; + position: relative; + border-radius: 1px; + margin: 2px; + display: block; + outline: none; +} + +.jtags > div:empty:before { + content: " "; + white-space: pre; +} + +.jtags > div::after { + content: 'x'; + position: absolute; + top: 4px; + right: 4px; + width: 12px; + height: 12px; + cursor: pointer; + font-size: 11px; + display: none; +} + +.jtags_label { + background-color: #eeeeee !important; +} + +.jtags_label::after { + display: inline-block !important; +} + +.jtags_error::after { + color: #fff !important; +} + +.jtags_error { + background-color: #d93025 !important; + color: #fff; +} + + +.jtoolbar-container { + border-radius: 2px; + margin-bottom: 5px; + box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2); + display: inline-flex !important; +} + +.jtoolbar { + cursor: pointer; + white-space: nowrap; + display: flex; + padding:4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.jtoolbar-mobile { + display: flex; + position:fixed; + bottom: 0; + margin: 0; + left: 0; + width: 100%; + background: #f7f7f8; + z-index: 1; + box-sizing: border-box; + box-shadow: 0 -1px 2px rgba(0,0,0,.1); + border-radius: 0px; +} + +.jtoolbar > div { + display: inline-flex; + align-items: center; + box-sizing: border-box; + vertical-align:middle; + justify-content: space-evenly; +} + +.jtoolbar-mobile > div { + display: flex; + width: 100%; +} + +.jtoolbar .jtoolbar-item { + position: relative; + text-align: center; + margin: auto; + padding: 2px; + padding-left:4px; + padding-right:4px; +} + +.jtoolbar-mobile .jtoolbar-item { + flex:1; +} + +.jtoolbar .jtoolbar-divisor { + width: 2px; + height: 18px; + padding: 0px; + margin-left: 4px; + margin-right: 4px; + background-color: #f2f2f2; +} + + +.jtoolbar-mobile a +{ + text-decoration:none; + display:inline-block; +} + +.jtoolbar-mobile i { + display: inline-flex !important; + color:#929292; +} + +.jtoolbar-mobile span { + font-size:0.7em; + display:block; + color:#929292; +} + +.jtoolbar-mobile .jtoolbar-selected a, .jtoolbar-mobile .jtoolbar-selected i, .jtoolbar-mobile .jtoolbar-selected span { + color:var(--active-color) !important; + background-color:transparent; +} + +.jtoolbar-item { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.jtoolbar-item i { + display: block; + color:#333; +} + +.jtoolbar-item:hover { + background-color:#f2f2f2; +} + + +.jtoolbar .jpicker { + padding-left:0px; + padding-right:0px; +} + +.jtoolbar .jpicker-header { + height: 24px; + line-height: 24px; + padding: 0px; + padding-right: 20px; + padding-left: 4px; + background-position: top 50% right 0px; + display: flex; + align-items: center; +} + +.jtoolbar .jpicker-content > div { + padding: 6px; +} + +.jtoolbar-active { + background-color:#eee; +} + +.jtoolbar .fa { + width: 18px; + height: 18px; + display: block; + line-height: 18px; + font-size: 14px; +} + +.jtoolbar .material-icons { + font-size: 18px; + width: 24px; + height: 24px; + display: block; + line-height: 24px; + transform: rotate(0.03deg); + text-align: center; +} + +.jtoolbar .jtoolbar-arrow { + background-repeat: no-repeat; + background-position: center; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'/%3E%3C/svg%3E"); + width: 16px; + height: 18px; +} + +.jtoolbar-floating { + position: absolute; + display: none; + box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2); + border-radius: 4px; + background-color: #fff; + padding: 4px; + z-index: 50; + text-align: left; +} + +.jtoolbar-floating > div { + display: block; +} + +.jtoolbar-arrow-selected .jtoolbar-floating { + display: block; +} + + diff --git a/histview2/static/common/css/main.css b/histview2/static/common/css/main.css new file mode 100644 index 0000000..7de85c1 --- /dev/null +++ b/histview2/static/common/css/main.css @@ -0,0 +1,1712 @@ +/* + DEMO STYLE +*/ +header { + margin: 0px -20px; + padding: 10px 20px; + background-color: #316060; + border: none; + border-radius: 0; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1); +} + +p.modal-inform, label.modal-inform { + color: #999; +} + +a, +a:hover, +a:focus { + color: inherit; + text-decoration: none; + transition: all 0.3s; +} + +.navbar { + padding: 15px 10px; + background-color: #316060; + border: none; + border-radius: 0; + margin-bottom: 40px; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1); +} + +.navbar-btn { + box-shadow: none; + outline: none !important; + border: none; + /* margin-top: 10px; */ + background-color: #444444; + margin-right: 19px; + font-size: 0.6rem; +} + +.line { + width: 100%; + height: 1px; + border-bottom: 1px dashed #316060; + margin: 40px 0; +} + +/* --------------------------------------------------- + SIDEBAR STYLE #316060 +----------------------------------------------------- */ + +.wrapper { + display: flex; + width: 100%; + font-family: Calibri Light; +} + +#sidebar { + width: 230px; + position: fixed; + top: 0; + left: 0; + height: 100vh; + z-index: 9999; + background: hsl(0, 0%, 19%); + /* color: #fff; */ + /*transition: all 0.5s;*/ +} + +#sidebar.active { + margin-left: 0px; + width: 50px; +} + +#sidebar .sidebar-header { + /* padding: 20px; */ + margin: 18px 9px; + /* background: #316060; */ +} + +#sidebar.active .sidebar-header { + /* padding: 20px; */ + margin: 20px 9px; + /* background: #316060; */ +} + +#sidebar ul.components { + padding: 5px 0; + border-top: 1px solid #444444; +} + +#sidebar ul p { + color: #fff; + padding: 10px; +} + +#sidebar ul li a { + padding: 3px 0 4px 15px; + font-size: 0.875rem; + display: block; +} + +#sidebar ul li a:hover { + color: #fff; + background: #375a7f; +} + +#sidebar ul li.active > a, +a[aria-expanded="true"] { + color: #fff; + /* background: #6d7fcc; */ +} + +.sidebar-footer { + background-color: #303030; + box-shadow: 0px -1px 5px #282c33; + width: 100%; + display: flex; + border-top: 1px solid #444444; + padding: 0.1rem 3px; + font-size: 14px; +} + + +.sidebar-body { + max-height: calc(100% - 100px); + overflow-y: auto; + position: relative; +} + +.sidebar-shutdown { + background-color: #303030; + box-shadow: 0px -1px 5px #282c33; + border-top: 1px solid #464a52; + width: 100%; + display: flex; + border-top: 1px solid #444444; + padding: 0.5rem; + font-size: 1em; +} + +span.sidebar-btm { + margin: 0 auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sidebar-shutdown:hover { + background-color: red; + cursor: pointer; +} + +a[data-toggle="collapse"] { + position: relative; +} + +.dropdown-toggle::after { + display: block; + position: absolute; + top: 50%; + right: 20px; + transform: translateY(-50%); +} + +ul ul a { + /* font-size: 0.9em !important; */ + padding-left: 30px !important; + /* background: #6d7fcc; */ +} + +ul.CTAs { + padding: 20px; +} + +ul.CTAs a { + text-align: center; + font-size: 0.9em !important; + display: block; + border-radius: 5px; + margin-bottom: 5px; +} + +a.download { + background: #fff; + color: #7386D5; +} + +a.article, +a.article:hover { + /* background: #6d7fcc !important; */ + color: #fff !important; +} + +/* --------------------------------------------------- + CONTENT STYLE +----------------------------------------------------- */ + +#content { + width: calc(100% - 50px); + padding: 0px 20px; + transition: all 0.1s; + position: absolute; + top: 0; + right: 0; + min-height: 100vh; +} + +#content.active { + /* width: calc(100% - 50px); */ + min-height: 100vh; +} + +/* --------------------------------------------------- + MEDIAQUERIES +----------------------------------------------------- */ + +@media (max-width: 768px) { + /* #sidebar { + margin-left: -250px; + } */ + #sidebar.active { + margin-left: 0; + } + + /* #content.active { + width: calc(100% - 250px); + } */ + #sidebarCollapse span { + display: none; + } +} + +.ls-group-right { + float: right; +} + +.ls-control { + max-width: 180px; +} + +.ls-col-right { + padding-right: 0; +} + +.copyright { + text-align: center; +} + +.form-control, +.form-control:focus, +.form-control { + color: #ffffff; + background-color: #222222; + min-width: 75px; +} + +.form-control:disabled { + color: #ffffff; + background-color: #444444; + min-width: 75px; +} + +.fs-wrap { + border: 1px solid #303030; +} + +.fs-open, .fs-wrap:focus { + border: 1px solid #739ac2; + border-radius: 4px; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(55, 90, 127, 0.25); +} + +.fs-label-wrap { + background-color: #222222 !important; + border: 1px solid #222222 !important; + color: #ffffff !important; +} + +.fs-label-wrap:focus { + border: 1px solid #739ac2; +} + +.fs-label-wrap .fs-label { + color: #ffffff !important; +} + +.fs-arrow { + border-top: 5px solid #ffffff !important; +} + +.fs-dropdown { + border: 1px solid #222222 !important; +} + +.fs-dropdown, .fs-search input { + background-color: #222222 !important; + color: #ffffff !important; +} + +.fs-option, .fs-search, .fs-optgroup-label { + border-bottom: 1px solid #303030 !important; +} + +.fs-wrap.multiple .fs-option.selected .fs-checkbox i { + background-color: rgb(55, 90, 127, 1) !important; +} + + +.close-icon { + cursor: pointer; + text-align: right; + margin-right: -0.75rem; + height: 1rem; +} + +.top-span { + height: 1rem; +} + +.card { + border-color: #444444; +} + +/* + * Scrollbar darkstyle + */ + +.sidebar-body::-webkit-scrollbar-track, +.select2-results__options::-webkit-scrollbar-track, +.list-group::-webkit-scrollbar-track, +.dscr-selection::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); + background-color: #444; +} + +.sidebar-body::-webkit-scrollbar, +.select2-results__options::-webkit-scrollbar, +.list-group::-webkit-scrollbar, +.dscr-selection::-webkit-scrollbar { + width: 10px; + height: 10px; + background-color: #444; +} + +.sidebar-body::-webkit-scrollbar-thumb, +.select2-results__options::-webkit-scrollbar-thumb, +.list-group::-webkit-scrollbar-thumb, +.dscr-selection::-webkit-scrollbar-thumb { + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); + background-color: #222222; +} + +.page-title { + margin-top: 0.4rem; +} + +.page-title h2 { + font-size: 1.75rem; +} + +hr, br { + border-top: 2px solid rgb(68, 68, 68); +} + +.hide-msg { + display: none; +} + +.show-msg { + display: block; +} + +.error-msg { + color: red; + +} + +@keyframes rotate { + 0% { + } + 100% { + transform: rotate(-360deg); + } +} + +@-webkit-keyframes rotate { + 0% { + } + 100% { + -webkit-transform: rotate(-360deg); + } +} + +.hide { + display: none; +} + +.loading { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0, 0, 0, .5); + z-index: 999; +} + +.loading::before { + content: ""; + display: block; + position: fixed; + left: 50%; + top: 50%; + width: 150px; + height: 110px; + border-radius: 5px; + margin-top: -25px; + margin-left: -55px; + /* background-color: white; */ +} + +.loading::after { + content: ""; + display: block; + position: fixed; + left: 50%; + top: 50%; + width: 64px; + height: 64px; + border-radius: 40px; + margin-top: -10px; + margin-left: -10px; + border: 4px solid #60ABB9; + border-right: 4px solid white; + animation: rotate 1s infinite linear; +} + +ul#pageSubmenu li a, +ul#analyzeSubmenu li a, +ul#traceDataSubmenu li a { + padding-left: 30px !important; +} + +ul#anomalyDetectionMn li a, +ul#visnMn li a { + padding-left: 45px !important; +} + +@media (max-width: 1100px) { + .card-xl { + width: 100% !important; + } +} + +.th-sm-1 { + width: 10%; +} + +.th-sm { + width: 15%; +} + +.th-md { + width: 20%; +} + +.th-lg, .td-lg { + width: 30%; +} + +.th-2lg { + width: 60%; +} + +.card-xl, .th-xl { + width: 45%; +} + +.single-selector { + /* width: calc(5/6*(45% - 15px) - 12px); */ + width: 30%; +} + +.noafter:after { + display: none; +} + +.nav-text { + margin-left: 10px; + white-space: break-spaces; +} + +.status-i { + widows: 1.5em; + height: 1.5em; +} + +.status-i.green { + color: #00bc8c; +} + +.status-i.yellow { + color: #F39C12; +} + +.srtPrc .custom-control-label::before, +.srtPrc .custom-control-label::after { + top: 0.703125rem !important; +} + +.btn-secondary { + color: #000 !important; + background-color: #ced4da !important; + border-color: #ced4da !important; +} + +.btn-primary { + color: #fff !important; +} + + +#toast-container > .toast-error, +#toast-container > .toast-warning, +#toast-container > .toast-info { + background-position-y: 15px !important; +} + +.toast { + max-width: 520px !important; + width: 520px !important; +} + +.toast-title { + margin-bottom: 10px !important; + text-transform: uppercase; +} + +.toast-title, .toast-message a { + text-decoration: underline !important; +} + +.toast-message p { + color: white !important; +} + +.nav-tabs .nav-link.active { + border-top: solid #375a7f; +} + +@media (min-width: 1440px) { + .modal-xxl { + max-width: 1500px; + } +} + +@media (min-width: 1200px) { + .modal-xxl { + max-width: 1400px; + } +} + +.btn-setting { + border-radius: 0px; + background-color: #303030; + border: 1px solid #303030; +} + +.btn-setting:hover { + border: 1px solid white; +} + +.hint-text { + padding-bottom: 1px; + border-bottom: 1px solid white; +} + +.select2-container--default .select2-selection--single { + background-color: #222 !important; +} + +.select2-results { + background-color: #222 !important; + color: #fff !important; + border: 1px solid #676767; +} + +.select2-container--default .select2-results__option[aria-selected=true] { + background-color: #29f !important; +} + +.select2-container--default .select2-results__option--selected { + background-color: #29f !important; +} + +/*.selection {*/ +/* background-color: #2299ff !important;*/ +/*}*/ + +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: #fff !important; +} + +.select2-container--default .select2-selection--multiple { + background-color: #222 !important; + border: 1px solid #222 !important; +} + +.select2-container--default.select2-container--focus .select2-selection--multiple { + border: 1px solid #222 !important; +} + +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #222 !important; +} + +textarea { + color: #fff; +} + +.select2-search .select2-search--dropdown .select2-search--dropdown { + border: 1px solid #5897fb !important; + background-color: #303030 !important; + color: #ffffff !important; +} + +.select2-results__option--highlighted { + background-color: #222 !important; +} + +.select2-search__field { + background-color: #4c4c4c !important; + color: #ffffff !important; +} + +.select2-dropdown .select2-dropdown--below { + color: #000000 !important; +} + +.select2-search--dropdown { + background-color: #222 !important; + color: #ffffff !important; +} + + +.select2-container { + display: block; +} + +.select2-dropdown { + background-color: #222222; + border: 1px solid #222; +} + +.select2-container--default .select2-results__option[aria-selected=true] { + background-color: #5897fb; +} + +.select2-container--default .select2-selection--single { + background-color: #222222; + border: 1px solid #222222; +} + +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: #ffffff; + line-height: 34px; + padding-right: 0; +} + +.select2-container--default .select2-selection--single .select2-selection__arrow { + height: 32px; +} + +.select2-container--default .select2-selection--single .select2-selection__arrow b, +.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-width: 6px 3px 0 3px; + margin-left: -1px; + border-color: #ffffff transparent transparent transparent; +} + +.select2-container .select2-selection--single { + height: 35px; +} + +.select2-container--default .select2-search--dropdown .select2-search__field { + border: 1px solid #5897fb; + background-color: #303030; + color: #ffffff; +} + +.select2-selection__rendered { + /*padding-left: 1rem !important;*/ +} + +.table th, .table td { + vertical-align: middle !important; +} + +.table tbody tr td { + white-space: nowrap; +} + +.select2-container--default .select2-selection--single .select2-selection__clear { + color: white; + height: 34px; +} + +.proc-line { + margin-top: 5px; +} + + +/* for collapse configs page */ +.collapse-box { + margin: 0 10px; + height: 35px; + vertical-align: middle; + float: right; +} + +.fa-window-minimize { + margin-top: 5px; +} + +.fa-window-maximize { + margin-top: 10px; +} + +.collapsing { + animation: fadeout 1s; + -webkit-animation: fadeout 1s; /* Chrome, Safari, Opera */ + animation-fill-mode: forwards; + -webkit-animation-fill-mode: forwards; + transition-duration: 1s; +} + +.collapse.show { + animation: fadeout 1s; + -webkit-animation: fadeout 1s; /* Chrome, Safari, Opera */ + animation-fill-mode: forwards; + -webkit-animation-fill-mode: forwards; +} + +/* Standard syntax */ +@keyframes fadeout { + from { + display: none; + visibility: hidden; + opacity: 0; + } + + to { + display: block; + visibility: visible; + opacity: 1; + } +} + +.custom-control-label { + display: unset; +} + +input.select2-search__field:focus-visible { + border-radius: 3px; + border-color: #739ac2 !important; + outline: 0; + -webkit-box-shadow: 0 0 0 0.2rem rgb(55 90 127 / 25%); + box-shadow: 0 0 0 0.2rem rgb(55 90 127 / 25%); +} + +.select2-results__option { + padding: 0px 5px; + user-select: none; + -webkit-user-select: none; +} + +div.list-item { + margin-bottom: 15px; +} + +footer { + padding: 10px 0; + height: auto; + min-height: 60px; + display: flex; + flex-direction: column; + justify-content: flex-end; +} + +#mainContent { + color: white; + min-height: calc(100vh - 130px); +} + +.context-menu { + position: fixed; + border: 1px solid #444; + background: rgba(14, 12, 12, 0.8); + list-style: none; + border-radius: 3px; + padding: 0; + z-index: 10000; +} + +.menu-item { + font-size: smaller; + line-height: 24px; + padding: 0.25rem 0.5rem; + color: white; +} + +.menu-item:hover { + background: #375a7f; + text-decoration: underline; + cursor: pointer; +} + +/*tiles menu*/ +.tile-menus { + width: 130px; + border-radius: 0px; + background-color: #303030; + border: 1px solid #303030; +} + +.tile-menus.active { + background-color: #0f3b69; + border: 1px solid #0f3b69; +} + +.tile-menus:hover { + border: 1px solid white; +} + +.li-grey { + color: grey; +} + +.list-group-item:first-child, +.list-group-item:last-child { + border-radius: 0; +} + +.li-blue { + color: lightblue; +} + +.height-align { + vertical-align: middle; + height: 0; +} + +.grp-height-align { + height: 35px; + padding-top: 5px; +} + +/*TODO*/ +#traceDataForm .col-form-label { + /*padding-top: 0 !important;*/ +} + +.download-timeout::after { + content: ""; + display: block; + width: 25px; + height: 25px; + border-radius: 25px; + border: 3px solid #60ABB9; + border-right: 3px solid white; + animation: rotate 1s infinite linear; +} + +.index-inform-modal, +.setting-inform-modal { + position: relative; + display: inline-block; +} + +.index-inform, +.setting-inform { + padding: 4px 7px; + width: 29px; + height: 29px; + margin-left: 5px; + border-radius: 3px; + line-height: inherit; + vertical-align: middle; + cursor: help; +} + +.index-inform-modal { + display: none; + width: 0; +} + +.index-inform-modal.show, +.index-inform-content.show { + display: block; +} + +/*.index-inform-modal:hover .index-inform-content,*/ +.setting-inform-modal:hover .setting-inform-content { + display: block; +} + + +.index-inform-content, +.setting-inform-content { + display: none; + min-width: 475px; + border: 1px solid #444; + padding: 0.5rem; + font-size: smaller; + background: rgba(14, 12, 12, 0.8); + border-radius: 3px; + position: absolute; + right: 0; + z-index: 9999; +} + +.setting-inform-content { + right: unset; + left: 0; +} + +.setting-item { + margin-bottom: 10px; +} + +label.setting-label { + margin-bottom: 0px; +} + +.setting-dat span { + margin-right: 4px; +} + +#stContentTab { + width: 100%; + display: none; + color: white; +} + +#stContentTab .setting-inform-content { + padding-left: 50px; +} + +table .col-number { + text-align: center; + width: 2%; +} + +#index-infor-table, +#setting-infor-table { + border: none !important; + background-color: #151414; + margin-bottom: 2px; +} + +#index-infor-table th { + border: 1px solid #444444; + padding: 5px 0; +} + +#index-infor-table td, +#setting-infor-table td { + text-align: left !important; + max-width: fit-content !important; + vertical-align: top !important; + padding: 5px !important; + white-space: break-spaces !important; + min-width: 65px !important; + border: 1px solid #444444; +} + +#setting-infor-table td:nth-child(2) { + width: 200px; +} + +.setting-content { + width: 440px; +} + +#stContentTab table { + width: 500px; +} + +#saveUserSettingModal .row { + margin-bottom: 5px; +} + +#loadUserSettingModal .table thead th { + border-bottom: none; + border: 1px solid #444; +} + +#loadUserSettingModal .modal-content { + height: 85vh; +} + +#loadUserSettingModal .modal-content .modal-body { + height: calc(100% - 131px); +} + +#saveSettingConfirmModal { + background-color: #1818188c; +} + +.index-inform-content .clipboard { + float: right; + position: absolute; + top: 0; + right: 0; +} + +.setting-inform-content .clipboard { + float: right; + position: absolute; + right: 0; + padding: 0 5px; +} + +label span { + vertical-align: middle; +} + +.btn-orange.btn { + background-color: #be7b00; +} + +.section-label { + /* copy from lib */ + font-size: 1.171875rem; + font-weight: 500; + line-height: 1.2; + margin-top: 0; + margin-bottom: 0.5rem; +} + +@media (max-width: 1250px) { + .section-label { + font-size: 1.3vw; + } +} + +.sub-label { + /* copy from lib */ + font-size: 1rem; + font-weight: 400; + line-height: 1.5; +} + +.btn-orange.btn { + background-color: #be7b00; +} + +.col-medium { + min-width: 130px; +} + +.align-with-label { + /* copy from lib */ + padding-top: calc(0.375rem + 1px); + padding-bottom: calc(0.375rem + 1px); +} + +li.list-group-item { + margin-bottom: 0; +} + +li.list-group-item:hover { + border: 1px solid white; + cursor: pointer; +} + +.card-body { + padding: 0.5rem 1.25rem; +} + +.col-form-label { + padding-top: 0 !important; +} + +.margin-left-m15 { + margin-left: -15px; +} + +.col-datetime { + min-width: 130px; + max-width: 130px; + margin-right: -15px; +} + +.term-box { + padding-left: 35px; + border: 2px solid #444444; + border-radius: 3px; + padding-right: 1rem; + /*max-height: 109px;*/ + margin-bottom: 5px; +} + +.col-margin-left-15 { + margin-left: 15px; +} + +.col-margin-left-30 { + margin-left: 30px; +} + +.col-min-220 { + min-width: 220px; +} + +.col-flex-1 { + max-width: 10%; +} + +.no-padding { + padding-right: 0; +} + +body { + animation: fadeInAnimation ease 0.5s; + animation-iteration-count: 1; + animation-fill-mode: forwards; +} + +@keyframes fadeInAnimation { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +.filterCol { + min-width: 20px; +} + +.single-term { + padding-bottom: 15px; +} + +.no-margin-bot { + margin-bottom: 0; +} + +.loadingoverlay_element.loadingoverlay_fa { + font-size: 56px !important; +} + +.min-100 { + min-width: 100px; +} + +#tblUserSetting .btn { + width: 40px; +} + +.extra-info { + color: #757575; +} + +/*scrollbar usersetting*/ +#tblUserSetting_wrapper { + height: 100%; + overflow-y: scroll; + overflow-x: auto; +} + +#tblUserSetting_wrapper::-webkit-scrollbar-track { + box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); + background-color: #444; +} + +#tblUserSetting_wrapper::-webkit-scrollbar { + width: 10px; + height: 12px; + background-color: #444; +} + +#tblUserSetting_wrapper::-webkit-scrollbar-thumb { + width: 100px; + box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); + background-color: #222222; + border-radius: 25px; +} + +#tblUserSetting_wrapper::-webkit-scrollbar-corner { + background-color: #444 !important; + box-shadow: inset 0 0 6px rgba(0, 0, 0, .3) !important; + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3) !important; +} + +.bookmarked-label { + color: #90d1ff; + max-width: 13vw; +} + +.bookmark-inline { + display: inline-flex; +} + +.simple-btn { + background-color: #444; + padding: 2px 8px; +} + +#currentLoadSettingLbl { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: right; +} + +.border-white { + border: 1px solid #fff; +} + +.scale-dropdown { + padding: 4px 5px; + height: auto; +} + +.flex-row-center { + display: flex; + align-items: center; +} + +.table-fixed thead { + position: sticky; + position: -webkit-sticky; + top: 0; + z-index: 10; + box-shadow: inset 0 1px 0 #444; +} + +.table-fixed thead tr { + box-shadow: inset 0 1px 0 #444, inset 0 -1px 0 #444; +} + +.invalid, .invalid + .select2.select2-container { + border: 2px solid #65c5f1 !important; + border-radius: 3px !important; +} + +.invalid.invalid-message { + position: relative; +} + +.invalid.invalid-message:before { + content: ''; + position: absolute; + border-top: 14px solid #0a0a0a; + border-left: 9px solid transparent; + border-right: 9px solid transparent; + transform: rotate(25deg); + left: calc(67% + 10px); + top: -15px; +} + +.invalid.invalid-message:after { + content: attr(data-content); + position: absolute; + width: fit-content; + height: auto; + top: -42px; + padding: 5px; + border-radius: 3px; + left: 67%; + z-index: 999; + background-color: #0a0a0a; +} + +.optional-label span { + color: #999; +} + +.top-index { + z-index: 10; +} + +.dropdown-toggle::after { + display: block !important; + right: 5px !important; +} + +.dropdown button { + padding-right: 1.3rem; +} + +.bookmark-button span { + max-width: 200px; + display: block; +} + +.custom-scroll::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); + background-color: #444; +} + +.custom-scroll::-webkit-scrollbar { + width: 10px; + height: 10px; + background-color: #444; +} + +.custom-scroll::-webkit-scrollbar-thumb { + border-radius: 25px; + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); + background-color: #222222; +} + +#termBtnAddDateTime { + position: absolute; + bottom: 2px; + left: -40px; +} + +.datetimerange-group .remove-date svg { + font-size: 12px; + cursor: pointer; +} + +.card-body.table-bordered { + border: none; + border-bottom: 1px solid #444444; +} + +/* summary box */ +.summary-menu { + position: relative; + display: inline-block; +} + +.summary-menu:hover .summary-menu-content { + display: block; +} + +.summary-menu-content { + display: none; + min-width: 180px; + border: 1px solid #444; + padding: 0.5rem; + font-size: smaller; + background: rgba(14, 12, 12, 0.8); + border-radius: 3px; + position: absolute; + z-index: 9999; +} + +/* switch button */ +.switch { + position: relative; + /*float: right;*/ + /* display: inline-block; */ + width: 54px; + height: 28px; + margin-bottom: -10px; +} + +.switch input { + display: none; +} + +.switch input:disabled + .slider { + cursor: not-allowed; +} + +input.primary:checked + .slider { + background-color: #375a7f; +} + +.slider.round { + border-radius: 28px; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #434343; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider:before { + -webkit-transform: translateX(28px); + -ms-transform: translateX(28px); + transform: translateX(28px); +} + +.slider.round:before { + border-radius: 50%; +} + +.slider:before { + position: absolute; + content: ""; + height: 28px; + width: 28px; + left: 0px; + bottom: 0px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +.common-search-input { + width: 300px; +} + +.cursor-hint { + width: 100%; + height: 10px; + margin-right: 23px; + cursor: pointer; + top: 15px; + position: relative; + z-index: 1; +} + +.vertical-text { + height: 100%; + position: absolute; + display: flex; + align-items: center; + justify-content: center; + transform: rotate(-90deg); +} + +.vertical-text span { + display: block; +} + +.show-graph-div { + margin-top: -38px; +} + +.show-graph-div button { + z-index: 10; + position: relative; +} + +@media (max-width: 1200px) { + .show-graph-div { + margin-top: 5px; + } +} + +/* filter modal */ +.cat-exp-box { + color: #89b368; +} + +.cat-filter-modal .cat-title { + font-size: 18px; +} + +.cat-filter-modal .modal-content { + height: 100vh; +} + +.cat-filter-modal .modal-content .modal-body { + height: 100%; +} + +.cat-filter-modal .column-name { + text-align: center; + display: inline-grid; +} + +.cat-filter-modal .column-name span { + overflow: hidden !important; + text-overflow: ellipsis; + white-space: pre; +} + +.cat-filter-modal .column-datas { + border-radius: 3px; + border: 2px solid #444; + overflow-y: auto; + height: calc(90vh - 170px); + background-color: #303030; +} + +.cat-filter-modal .column-datas .list-group-item { + position: unset; + padding: unset; + margin-bottom: unset; + border-top: unset; + border-right: unset; + border-left: unset; +} + +.cat-filter-modal .filter-data { + min-width: 131px; +} + +.cat-filter-modal .filter-data:not(:last-child) { + margin-right: 0.5rem; +} + +.cat-filter-modal .column-datas > div { + border-bottom: 1px solid rgba(96, 96, 96, 1); +} + +.cat-filter-modal #catExpBox .active { + border: 1px solid #65c5f1; +} + +.cat-filter-modal .cats-exp-box .column-name span { + color: #89b368; +} + +.cat-filter-modal #categoriesBox { + width: 100%; +} + +.cat-filter-modal .category-box { + flex-grow: 1; +} + +.cat-filter-modal #categoriesBox .column-name span { + color: #65c5f1; +} + +.cat-filter-modal .filter-data .active { + border: 1px solid #65c5f1 !important; +} + +.column-datas::-webkit-scrollbar { + width: 10px; + height: auto; + background-color: #444; +} + +.column-datas::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px grey; +} + +.column-datas::-webkit-scrollbar-thumb { + border-radius: 25px; + background-color: #222222; +} + +.show-detail { + cursor: pointer; + text-decoration: underline; + z-index: 10; +} + +#filterSortBtn { + cursor: pointer; +} + +#filterSortBtn svg { + font-size: 20px; +} + +#catFilterModal .modal-dialog { + max-width: 80%; +} + +@media (max-width: 1200px) { + #catFilterModal .modal-dialog { + max-width: 90%; + } +} + +/* filter modal end */ + +.page-character { + display: inline-block; + width: 20px; +} + + +/* marquee START */ +.sidebar-marquee { + background-color: #303030; + box-shadow: 0px -1px 5px #282c33; + border-top: 1px solid #464a52; + position: relative; + width: 100%; + display: flex; + border-top: 1px solid #444444; + padding: 0; + font-size: 1em; +} + +.marquee { + height: 40px; + width: 100%; + overflow: hidden; + position: relative; + border-top: 0; + border-bottom: 0; + padding: 8px 0 8px 0; +} + +.marquee.warn { + background-color: #F39C12; +} + +.marquee.error { + background-color: #F31515; +} + +.marquee p { + white-space: nowrap; +} + +/* would need to be adjusted depending on time */ +.marquee .marquee-content { + animation: marquee 15s linear infinite; + width: fit-content; +} + +/* even out the elements */ +.marquee div { + position: absolute; + width: 100%; + left: 100%; + height: 40px; + display: flex; + justify-content: space-between; +} + +.marquee:hover div { + animation-play-state: paused; +} + +/* add delay at the end of animation so you dont start while another div is going */ +@keyframes marquee { + from { + transform: translateX(0%); + } + to { + transform: translateX(-100%) translateX(-270px); + } +} + +/* marquee END */ + +.modal-dialog { + padding-left: 60px; +} + +@media (max-width: 1200px) { + .modal { + padding-right: 0; + } + + .modal-dialog { + max-width: 95%; + margin: 1.75rem auto; + } +} + +/* his summary chart */ +.his { + float: left; + width: calc(100% / 8); + position: relative; +} + +.his::after { + content: ''; + display: block; + padding-top: 200%; +} + +.his-content { + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + margin: 3px; + border: 1px solid #444444; +} + +@media (max-width: 1200px) { + .his { + width: calc(100% / 4); + } +} + +.hist-summary { + padding-bottom: 5px; +} +@media (min-width: 1200px) { + #sidebar { + z-index: 99; + } + .modal-xl { + max-width: 95vw !important; + } +} diff --git a/histview2/static/common/css/pagination.css b/histview2/static/common/css/pagination.css new file mode 100644 index 0000000..ed20b40 --- /dev/null +++ b/histview2/static/common/css/pagination.css @@ -0,0 +1,359 @@ +.paginationjs { + line-height: 1.6; + font-family: Marmelad, "Lucida Grande", Arial, "Hiragino Sans GB", Georgia, sans-serif; + font-size: 14px; + box-sizing: initial +} + +.paginationjs:after { + display: table; + content: " "; + clear: both +} + +.paginationjs .paginationjs-pages { + float: left +} + +.paginationjs .paginationjs-pages ul { + float: left; + margin: 0; + padding: 0 +} + +.paginationjs .paginationjs-go-button, .paginationjs .paginationjs-go-input, .paginationjs .paginationjs-nav { + float: left; + margin-left: 10px; + font-size: 14px +} + +.paginationjs .paginationjs-pages li { + float: left; + border: 1px solid #aaa; + border-right: none; + list-style: none +} + +.paginationjs .paginationjs-pages li > a { + min-width: 30px; + height: 28px; + line-height: 28px; + display: block; + background: #fff; + font-size: 14px; + color: #333; + text-decoration: none; + text-align: center +} + +.paginationjs .paginationjs-pages li > a:hover { + background: #eee +} + +.paginationjs .paginationjs-pages li.active { + border: none +} + +.paginationjs .paginationjs-pages li.active > a { + height: 30px; + line-height: 30px; + background: #aaa; + color: #fff +} + +.paginationjs .paginationjs-pages li.disabled > a { + opacity: .3 +} + +.paginationjs .paginationjs-pages li.disabled > a:hover { + background: 0 0 +} + +.paginationjs .paginationjs-pages li:first-child, .paginationjs .paginationjs-pages li:first-child > a { + border-radius: 3px 0 0 3px +} + +.paginationjs .paginationjs-pages li:last-child { + border-right: 1px solid #aaa; + border-radius: 0 3px 3px 0 +} + +.paginationjs .paginationjs-pages li:last-child > a { + border-radius: 0 3px 3px 0 +} + +.paginationjs .paginationjs-go-input > input[type=text] { + width: 30px; + height: 28px; + background: #fff; + border-radius: 3px; + border: 1px solid #aaa; + padding: 0; + font-size: 14px; + text-align: center; + vertical-align: baseline; + outline: 0; + box-shadow: none; + box-sizing: initial +} + +.paginationjs .paginationjs-go-button > input[type=button] { + min-width: 40px; + height: 30px; + line-height: 28px; + background: #fff; + border-radius: 3px; + border: 1px solid #aaa; + text-align: center; + padding: 0 8px; + font-size: 14px; + vertical-align: baseline; + outline: 0; + box-shadow: none; + color: #333; + cursor: pointer; + vertical-align: middle \9 +} + +.paginationjs.paginationjs-theme-blue .paginationjs-go-input > input[type=text], .paginationjs.paginationjs-theme-blue .paginationjs-pages li { + border-color: #289de9 +} + +.paginationjs .paginationjs-go-button > input[type=button]:hover { + background-color: #f8f8f8 +} + +.paginationjs .paginationjs-nav { + height: 30px; + line-height: 30px +} + +.paginationjs .paginationjs-go-button, .paginationjs .paginationjs-go-input { + margin-left: 5px \9 +} + +.paginationjs.paginationjs-small { + font-size: 12px +} + +.paginationjs.paginationjs-small .paginationjs-pages li > a { + min-width: 26px; + height: 24px; + line-height: 24px; + font-size: 12px +} + +.paginationjs.paginationjs-small .paginationjs-pages li.active > a { + height: 26px; + line-height: 26px +} + +.paginationjs.paginationjs-small .paginationjs-go-input { + font-size: 12px +} + +.paginationjs.paginationjs-small .paginationjs-go-input > input[type=text] { + width: 26px; + height: 24px; + font-size: 12px +} + +.paginationjs.paginationjs-small .paginationjs-go-button { + font-size: 12px +} + +.paginationjs.paginationjs-small .paginationjs-go-button > input[type=button] { + min-width: 30px; + height: 26px; + line-height: 24px; + padding: 0 6px; + font-size: 12px +} + +.paginationjs.paginationjs-small .paginationjs-nav { + height: 26px; + line-height: 26px; + font-size: 12px +} + +.paginationjs.paginationjs-big { + font-size: 16px +} + +.paginationjs.paginationjs-big .paginationjs-pages li > a { + min-width: 36px; + height: 34px; + line-height: 34px; + font-size: 16px +} + +.paginationjs.paginationjs-big .paginationjs-pages li.active > a { + height: 36px; + line-height: 36px +} + +.paginationjs.paginationjs-big .paginationjs-go-input { + font-size: 16px +} + +.paginationjs.paginationjs-big .paginationjs-go-input > input[type=text] { + width: 36px; + height: 34px; + font-size: 16px +} + +.paginationjs.paginationjs-big .paginationjs-go-button { + font-size: 16px +} + +.paginationjs.paginationjs-big .paginationjs-go-button > input[type=button] { + min-width: 50px; + height: 36px; + line-height: 34px; + padding: 0 12px; + font-size: 16px +} + +.paginationjs.paginationjs-big .paginationjs-nav { + height: 36px; + line-height: 36px; + font-size: 16px +} + +.paginationjs.paginationjs-theme-blue .paginationjs-pages li > a { + color: #289de9 +} + +.paginationjs.paginationjs-theme-blue .paginationjs-pages li > a:hover { + background: #e9f4fc +} + +.paginationjs.paginationjs-theme-blue .paginationjs-pages li.active > a { + background: #289de9; + color: #fff +} + +.paginationjs.paginationjs-theme-blue .paginationjs-pages li.disabled > a:hover { + background: 0 0 +} + +.paginationjs.paginationjs-theme-blue .paginationjs-go-button > input[type=button] { + background: #289de9; + border-color: #289de9; + color: #fff +} + +.paginationjs.paginationjs-theme-green .paginationjs-go-input > input[type=text], .paginationjs.paginationjs-theme-green .paginationjs-pages li { + border-color: #449d44 +} + +.paginationjs.paginationjs-theme-blue .paginationjs-go-button > input[type=button]:hover { + background-color: #3ca5ea +} + +.paginationjs.paginationjs-theme-green .paginationjs-pages li > a { + color: #449d44 +} + +.paginationjs.paginationjs-theme-green .paginationjs-pages li > a:hover { + background: #ebf4eb +} + +.paginationjs.paginationjs-theme-green .paginationjs-pages li.active > a { + background: #449d44; + color: #fff +} + +.paginationjs.paginationjs-theme-green .paginationjs-pages li.disabled > a:hover { + background: 0 0 +} + +.paginationjs.paginationjs-theme-green .paginationjs-go-button > input[type=button] { + background: #449d44; + border-color: #449d44; + color: #fff +} + +.paginationjs.paginationjs-theme-yellow .paginationjs-go-input > input[type=text], .paginationjs.paginationjs-theme-yellow .paginationjs-pages li { + border-color: #ec971f +} + +.paginationjs.paginationjs-theme-green .paginationjs-go-button > input[type=button]:hover { + background-color: #55a555 +} + +.paginationjs.paginationjs-theme-yellow .paginationjs-pages li > a { + color: #ec971f +} + +.paginationjs.paginationjs-theme-yellow .paginationjs-pages li > a:hover { + background: #fdf5e9 +} + +.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.active > a { + background: #ec971f; + color: #fff +} + +.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.disabled > a:hover { + background: 0 0 +} + +.paginationjs.paginationjs-theme-yellow .paginationjs-go-button > input[type=button] { + background: #ec971f; + border-color: #ec971f; + color: #fff +} + +.paginationjs.paginationjs-theme-red .paginationjs-go-input > input[type=text], .paginationjs.paginationjs-theme-red .paginationjs-pages li { + border-color: #c9302c +} + +.paginationjs.paginationjs-theme-yellow .paginationjs-go-button > input[type=button]:hover { + background-color: #eea135 +} + +.paginationjs.paginationjs-theme-red .paginationjs-pages li > a { + color: #c9302c +} + +.paginationjs.paginationjs-theme-red .paginationjs-pages li > a:hover { + background: #faeaea +} + +.paginationjs.paginationjs-theme-red .paginationjs-pages li.active > a { + background: #c9302c; + color: #fff +} + +.paginationjs.paginationjs-theme-red .paginationjs-pages li.disabled > a:hover { + background: 0 0 +} + +.paginationjs.paginationjs-theme-red .paginationjs-go-button > input[type=button] { + background: #c9302c; + border-color: #c9302c; + color: #fff +} + +.paginationjs.paginationjs-theme-red .paginationjs-go-button > input[type=button]:hover { + background-color: #ce4541 +} + +.paginationjs .paginationjs-pages li.paginationjs-next { + border-right: 1px solid #aaa \9 +} + +.paginationjs .paginationjs-go-input > input[type=text] { + line-height: 28px \9; + vertical-align: middle \9 +} + +.paginationjs.paginationjs-big .paginationjs-pages li > a { + line-height: 36px \9 +} + +.paginationjs.paginationjs-big .paginationjs-go-input > input[type=text] { + height: 36px \9; + line-height: 36px \9 +} \ No newline at end of file diff --git a/histview2/static/common/css/select2.min.css b/histview2/static/common/css/select2.min.css new file mode 100644 index 0000000..c7c9754 --- /dev/null +++ b/histview2/static/common/css/select2.min.css @@ -0,0 +1 @@ +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{background-color:transparent;border:none;font-size:1em}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline;list-style:none;padding:0}.select2-container .select2-selection--multiple .select2-selection__clear{background-color:transparent;border:none;font-size:1em}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;margin-left:5px;padding:0;max-width:100%;resize:none;height:18px;vertical-align:bottom;font-family:sans-serif;overflow:hidden;word-break:keep-all}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option--selectable{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;height:26px;margin-right:20px;padding-right:0px}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;padding-bottom:5px;padding-right:5px;position:relative}.select2-container--default .select2-selection--multiple.select2-selection--clearable{padding-right:25px}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;font-weight:bold;height:20px;margin-right:10px;margin-top:5px;position:absolute;right:0;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:inline-block;margin-left:5px;margin-top:5px;padding:0;padding-left:20px;position:relative;max-width:100%;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap}.select2-container--default .select2-selection--multiple .select2-selection__choice__display{cursor:default;padding-left:2px;padding-right:5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{background-color:transparent;border:none;border-right:1px solid #aaa;border-top-left-radius:4px;border-bottom-left-radius:4px;color:#999;cursor:pointer;font-size:1em;font-weight:bold;padding:0 4px;position:absolute;left:0;top:0}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover,.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:focus{background-color:#f1f1f1;color:#333;outline:none}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__display{padding-left:5px;padding-right:2px}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{border-left:1px solid #aaa;border-right:none;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:4px;border-bottom-right-radius:4px}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__clear{float:left;margin-left:10px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--group{padding:0}.select2-container--default .select2-results__option--disabled{color:#999}.select2-container--default .select2-results__option--selected{background-color:#ddd}.select2-container--default .select2-results__option--highlighted.select2-results__option--selectable{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;height:26px;margin-right:20px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0;padding-bottom:5px;padding-right:5px}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;display:inline-block;margin-left:5px;margin-top:5px;padding:0}.select2-container--classic .select2-selection--multiple .select2-selection__choice__display{cursor:default;padding-left:2px;padding-right:5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{background-color:transparent;border:none;border-top-left-radius:4px;border-bottom-left-radius:4px;color:#888;cursor:pointer;font-size:1em;font-weight:bold;padding:0 4px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555;outline:none}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__display{padding-left:5px;padding-right:2px}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:4px;border-bottom-right-radius:4px}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option--group{padding:0}.select2-container--classic .select2-results__option--disabled{color:grey}.select2-container--classic .select2-results__option--highlighted.select2-results__option--selectable{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} diff --git a/histview2/static/common/css/toastr.css b/histview2/static/common/css/toastr.css new file mode 100644 index 0000000..49fe789 --- /dev/null +++ b/histview2/static/common/css/toastr.css @@ -0,0 +1,200 @@ +.toast-title { + font-weight: bold; +} +.toast-message { + -ms-word-wrap: break-word; + word-wrap: break-word; +} +.toast-message a, +.toast-message label { + color: #ffffff; +} +.toast-message a:hover { + color: #cccccc; + text-decoration: none; +} +.toast-close-button { + position: relative; + right: -0.3em; + top: -0.3em; + float: right; + font-size: 20px; + font-weight: bold; + color: #ffffff; + -webkit-text-shadow: 0 1px 0 #ffffff; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); +} +.toast-close-button:hover, +.toast-close-button:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); +} +/*Additional properties for button version + iOS requires the button element instead of an anchor tag. + If you want the anchor version, it requires `href="#"`.*/ +button.toast-close-button { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.toast-top-center { + top: 0; + right: 0; + width: 100%; +} +.toast-bottom-center { + bottom: 0; + right: 0; + width: 100%; +} +.toast-top-full-width { + top: 0; + right: 0; + width: 100%; +} +.toast-bottom-full-width { + bottom: 0; + right: 0; + width: 100%; +} +.toast-top-left { + top: 12px; + left: 12px; +} +.toast-top-right { + top: 12px; + right: 12px; +} +.toast-bottom-right { + right: 12px; + bottom: 12px; +} +.toast-bottom-left { + bottom: 12px; + left: 12px; +} +#toast-container { + position: fixed; + z-index: 999999; + pointer-events: none; + /*overrides*/ +} +#toast-container * { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +#toast-container > div { + position: relative; + pointer-events: auto; + overflow: hidden; + margin: 0 0 6px; + padding: 15px 15px 15px 50px; + width: 300px; + -moz-border-radius: 3px 3px 3px 3px; + -webkit-border-radius: 3px 3px 3px 3px; + border-radius: 3px 3px 3px 3px; + background-position: 15px center; + background-repeat: no-repeat; + -moz-box-shadow: 0 0 12px #999999; + -webkit-box-shadow: 0 0 12px #999999; + box-shadow: 0 0 12px #999999; + color: #ffffff; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); +} +#toast-container > :hover { + -moz-box-shadow: 0 0 12px #000000; + -webkit-box-shadow: 0 0 12px #000000; + box-shadow: 0 0 12px #000000; + opacity: 1; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + filter: alpha(opacity=100); + cursor: pointer; +} +#toast-container > .toast-info { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important; +} +#toast-container > .toast-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; +} +#toast-container > .toast-success { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important; +} +#toast-container > .toast-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important; +} +#toast-container.toast-top-center > div, +#toast-container.toast-bottom-center > div { + width: 300px; + margin-left: auto; + margin-right: auto; +} +#toast-container.toast-top-full-width > div, +#toast-container.toast-bottom-full-width > div { + width: 96%; + margin-left: auto; + margin-right: auto; +} +.toast { + background-color: #030303; +} +.toast-success { + background-color: #51a351; +} +.toast-error { + background-color: #bd362f; +} +.toast-info { + background-color: #2f96b4; +} +.toast-warning { + background-color: #f89406; +} +.toast-progress { + position: absolute; + left: 0; + bottom: 0; + height: 4px; + background-color: #000000; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); +} +/*Responsive Design*/ +@media all and (max-width: 240px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 11em; + } + #toast-container .toast-close-button { + right: -0.2em; + top: -0.2em; + } +} +@media all and (min-width: 241px) and (max-width: 480px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 18em; + } + #toast-container .toast-close-button { + right: -0.2em; + top: -0.2em; + } +} +@media all and (min-width: 481px) and (max-width: 768px) { + #toast-container > div { + padding: 15px 15px 15px 50px; + width: 25em; + } +} diff --git a/histview2/static/common/css/user-setting-table.css b/histview2/static/common/css/user-setting-table.css new file mode 100644 index 0000000..9b533c1 --- /dev/null +++ b/histview2/static/common/css/user-setting-table.css @@ -0,0 +1,79 @@ +#tblUserSetting.dataTable { + width: 100% !important; + border-collapse: collapse; + background: #303030; + font-size: 0.8vw; +} +#tblUserSetting.dataTable thead { + background: #303030; +} +#tblUserSetting.dataTable thead th { + padding: 5px !important; + font-weight: bold; + text-align: center; +} +#tblUserSetting.dataTable thead input { + height: auto; + padding: 3px 5px; +} +#tblUserSetting.dataTable.no-footer { + border-bottom: none; +} + +#tblUserSetting.dataTable tbody th, +#tblUserSetting.dataTable tbody td{ + padding: 3px 5px; + /*min-width: 50px;*/ + /*text-align: center;*/ + border: 1px solid #444; +} +#tblUserSetting.dataTable tbody td.action { + text-align: center; +} +#tblUserSetting.dataTable tbody tr { + background: #303030; +} + +#tblUserSetting.dataTable th:nth-child(1){ + min-width: 50px !important; +} +#tblUserSetting.dataTable th:nth-child(2){ + width: 100px !important; +} + +#tblUserSetting.dataTable th:nth-child(6), +#tblUserSetting.dataTable th:nth-child(7), +#tblUserSetting.dataTable th:nth-child(8){ + width: 70px !important; +} + +#tblUserSetting.dataTable td:nth-child(5){ + /*width: 180px !important;*/ + font-size: 0.8vw; +} +.col-date { + width: 150px; +} +.col-with-button { + width: 100px !important; +} + +#tblUserSetting.dataTable th:nth-child(10){ + width: 200px !important; +} + +#tblUserSetting.dataTable button{ + font-size: 0.6vw !important; + padding-left: 8px !important; + padding-right: 8px !important; +} + +#tblUserSetting.dataTable select { + font-size: 0.8vw; +} + +#tblUserSetting.dataTable td:nth-child(1), +#tblUserSetting.dataTable td:nth-child(2), +#tblUserSetting.dataTable td:nth-child(5){ + text-align: center; +} \ No newline at end of file diff --git a/histview2/static/common/custom-jquery/images/ui-icons_cc0000_256x240.png b/histview2/static/common/custom-jquery/images/ui-icons_cc0000_256x240.png new file mode 100644 index 0000000..45ac778 Binary files /dev/null and b/histview2/static/common/custom-jquery/images/ui-icons_cc0000_256x240.png differ diff --git a/histview2/static/common/custom-jquery/images/ui-icons_ffffff_256x240.png b/histview2/static/common/custom-jquery/images/ui-icons_ffffff_256x240.png new file mode 100644 index 0000000..fe41d2d Binary files /dev/null and b/histview2/static/common/custom-jquery/images/ui-icons_ffffff_256x240.png differ diff --git a/histview2/static/common/custom-jquery/jquery-ui.css b/histview2/static/common/custom-jquery/jquery-ui.css new file mode 100644 index 0000000..507cde2 --- /dev/null +++ b/histview2/static/common/custom-jquery/jquery-ui.css @@ -0,0 +1,697 @@ +/*! jQuery UI - v1.12.1 - 2019-07-17 +* http://jqueryui.com +* Includes: core.css, datepicker.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=custom-theme&bgImgOpacityError=&bgImgOpacityHighlight=&bgImgOpacityActive=&bgImgOpacityHover=&bgImgOpacityDefault=&bgImgOpacityContent=&bgImgOpacityHeader=&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=%23666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=%23aaaaaa&iconColorError=%23cc0000&fcError=%235f3f3f&borderColorError=%23f1a899&bgTextureError=flat&bgColorError=%23fddfdf&iconColorHighlight=%23ffffff&fcHighlight=%23ffffff&borderColorHighlight=%23375a7f&bgTextureHighlight=flat&bgColorHighlight=%23375a7f&iconColorActive=%23ffffff&fcActive=%23ffffff&borderColorActive=%23375a7f&bgTextureActive=flat&bgColorActive=%23375a7f&iconColorHover=%23ffffff&fcHover=%23ffffff&borderColorHover=%23375a7f&bgTextureHover=flat&bgColorHover=%23375a7f&iconColorDefault=%23ffffff&fcDefault=%23ffffff&borderColorDefault=%23303030&bgTextureDefault=flat&bgColorDefault=%23303030&iconColorContent=%23ffffff&fcContent=%23ffffff&borderColorContent=%23222222&bgTextureContent=flat&bgColorContent=%23222222&iconColorHeader=%23ffffff&fcHeader=%23fff&borderColorHeader=%23303030&bgTextureHeader=flat&bgColorHeader=%23303030&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); /* support: IE8 */ +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; + pointer-events: none; +} + + +/* Icons +----------------------------------*/ +.ui-icon { + display: inline-block; + vertical-align: middle; + margin-top: -.25em; + position: relative; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + +.ui-widget-icon-block { + left: 50%; + margin-left: -8px; + display: block; +} + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +/* .ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} */ +.ui-datepicker .ui-datepicker-prev { + left: 1px; +} +.ui-datepicker .ui-datepicker-next { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +/* .ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} */ +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 45%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} + +/* Icons */ +.ui-datepicker .ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; + left: .5em; + top: .3em; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Arial,Helvetica,sans-serif; + font-size: 1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Arial,Helvetica,sans-serif; + font-size: 1em; +} +.ui-widget.ui-widget-content { + border: 1px solid #303030; +} +.ui-widget-content { + border: 1px solid #222222; + background: #222222; + color: #ffffff; +} +.ui-widget-content a { + color: #ffffff; +} +.ui-widget-header { + border: 1px solid #303030; + background: #303030; + color: #fff; + font-weight: bold; +} +.ui-widget-header a { + color: #fff; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default, +.ui-button, + +/* We use html here because we need a greater specificity to make sure disabled +works properly when clicked or hovered */ +html .ui-button.ui-state-disabled:hover, +html .ui-button.ui-state-disabled:active { + border: 1px solid #303030; + background: #303030; + font-weight: normal; + color: #ffffff; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited, +a.ui-button, +a:link.ui-button, +a:visited.ui-button, +.ui-button { + color: #ffffff; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus, +.ui-button:hover, +.ui-button:focus { + border: 1px solid #375a7f; + background: #375a7f; + font-weight: normal; + color: #ffffff; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited, +.ui-state-focus a, +.ui-state-focus a:hover, +.ui-state-focus a:link, +.ui-state-focus a:visited, +a.ui-button:hover, +a.ui-button:focus { + color: #ffffff; + text-decoration: none; +} + +.ui-visual-focus { + box-shadow: 0 0 3px 1px rgb(94, 158, 214); +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, +.ui-button.ui-state-active:hover { + border: 1px solid #375a7f; + background: #375a7f; + font-weight: normal; + color: #ffffff; +} +.ui-icon-background, +.ui-state-active .ui-icon-background { + border: #375a7f; + background-color: #ffffff; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #ffffff; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #375a7f; + background: #375a7f; + color: #ffffff; +} +.ui-state-checked { + border: 1px solid #375a7f; + background: #375a7f; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #ffffff; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #f1a899; + background: #fddfdf; + color: #5f3f3f; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #5f3f3f; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #5f3f3f; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); /* support: IE8 */ + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); /* support: IE8 */ + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url("images/ui-icons_ffffff_256x240.png"); +} +.ui-widget-header .ui-icon { + background-image: url("images/ui-icons_ffffff_256x240.png"); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon, +.ui-button:hover .ui-icon, +.ui-button:focus .ui-icon { + background-image: url("images/ui-icons_ffffff_256x240.png"); +} +.ui-state-active .ui-icon, +.ui-button:active .ui-icon { + background-image: url("images/ui-icons_ffffff_256x240.png"); +} +.ui-state-highlight .ui-icon, +.ui-button .ui-state-highlight.ui-icon { + background-image: url("images/ui-icons_ffffff_256x240.png"); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url("images/ui-icons_cc0000_256x240.png"); +} +.ui-button .ui-icon { + background-image: url("images/ui-icons_ffffff_256x240.png"); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-caret-1-n { background-position: 0 0; } +.ui-icon-caret-1-ne { background-position: -16px 0; } +.ui-icon-caret-1-e { background-position: -32px 0; } +.ui-icon-caret-1-se { background-position: -48px 0; } +.ui-icon-caret-1-s { background-position: -65px 0; } +.ui-icon-caret-1-sw { background-position: -80px 0; } +.ui-icon-caret-1-w { background-position: -96px 0; } +.ui-icon-caret-1-nw { background-position: -112px 0; } +.ui-icon-caret-2-n-s { background-position: -128px 0; } +.ui-icon-caret-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -65px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -65px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 1px -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 3px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 3px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 3px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 3px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #aaaaaa; + opacity: .3; + filter: Alpha(Opacity=30); /* support: IE8 */ +} +.ui-widget-shadow { + -webkit-box-shadow: 0px 0px 5px #666666; + box-shadow: 0px 0px 5px #666666; +} diff --git a/histview2/static/common/custom-jquery/jquery-ui.js b/histview2/static/common/custom-jquery/jquery-ui.js new file mode 100644 index 0000000..605b350 --- /dev/null +++ b/histview2/static/common/custom-jquery/jquery-ui.js @@ -0,0 +1,2166 @@ +/*! jQuery UI - v1.12.1 - 2019-07-17 +* http://jqueryui.com +* Includes: keycode.js, widgets/datepicker.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define([ "jquery" ], factory ); + } else { + + // Browser globals + factory( jQuery ); + } +}(function( $ ) { + +$.ui = $.ui || {}; + +var version = $.ui.version = "1.12.1"; + + +/*! + * jQuery UI Keycode 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Keycode +//>>group: Core +//>>description: Provide keycodes as keynames +//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/ + + +var keycode = $.ui.keyCode = { + BACKSPACE: 8, + COMMA: 188, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + LEFT: 37, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SPACE: 32, + TAB: 9, + UP: 38 +}; + + +// jscs:disable maximumLineLength +/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ +/*! + * jQuery UI Datepicker 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Datepicker +//>>group: Widgets +//>>description: Displays a calendar from an input or inline for selecting dates. +//>>docs: http://api.jqueryui.com/datepicker/ +//>>demos: http://jqueryui.com/datepicker/ +//>>css.structure: ../../themes/base/core.css +//>>css.structure: ../../themes/base/datepicker.css +//>>css.theme: ../../themes/base/theme.css + + + +$.extend( $.ui, { datepicker: { version: "1.12.1" } } ); + +var datepicker_instActive; + +function datepicker_getZindex( elem ) { + var position, value; + while ( elem.length && elem[ 0 ] !== document ) { + + // Ignore z-index if position is set to a value where z-index is ignored by the browser + // This makes behavior of this function consistent across browsers + // WebKit always returns auto if the element is positioned + position = elem.css( "position" ); + if ( position === "absolute" || position === "relative" || position === "fixed" ) { + + // IE returns 0 when zIndex is not specified + // other browsers return a string + // we ignore the case of nested elements with an explicit value of 0 + //
          + value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } + + return 0; +} +/* Date picker manager. + Use the singleton instance of this class, $.datepicker, to interact with the date picker. + Settings for (groups of) date pickers are maintained in an instance object, + allowing multiple different settings on the same page. */ + +function Datepicker() { + this._curInst = null; // The current instance in use + this._keyEvent = false; // If the last event was a key event + this._disabledInputs = []; // List of date picker inputs that have been disabled + this._datepickerShowing = false; // True if the popup picker is showing , false if not + this._inDialog = false; // True if showing within a "dialog", false if not + this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division + this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class + this._appendClass = "ui-datepicker-append"; // The name of the append marker class + this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class + this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class + this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class + this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class + this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class + this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class + this.regional = []; // Available regional settings, indexed by language code + this.regional[ "" ] = { // Default regional settings + closeText: "Done", // Display text for close link + prevText: "Prev", // Display text for previous month link + nextText: "Next", // Display text for next month link + currentText: "Today", // Display text for current month link + monthNames: [ "January","February","March","April","May","June", + "July","August","September","October","November","December" ], // Names of months for drop-down and formatting + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], // For formatting + dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], // For formatting + dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], // For formatting + dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ], // Column headings for days starting at Sunday + weekHeader: "Wk", // Column header for week of the year + dateFormat: "mm/dd/yy", // See format options on parseDate + firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... + isRTL: false, // True if right-to-left language, false if left-to-right + showMonthAfterYear: false, // True if the year select precedes month, false for month then year + yearSuffix: "" // Additional text to append to the year in the month headers + }; + this._defaults = { // Global defaults for all the date picker instances + showOn: "focus", // "focus" for popup on focus, + // "button" for trigger button, or "both" for either + showAnim: "fadeIn", // Name of jQuery animation for popup + showOptions: {}, // Options for enhanced animations + defaultDate: null, // Used when field is blank: actual date, + // +/-number for offset from today, null for today + appendText: "", // Display text following the input box, e.g. showing the format + buttonText: "...", // Text for trigger button + buttonImage: "", // URL for trigger button image + buttonImageOnly: false, // True if the image appears alone, false if it appears on a button + hideIfNoPrevNext: false, // True to hide next/previous month links + // if not applicable, false to just disable them + navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links + gotoCurrent: false, // True if today link goes back to current selection instead + changeMonth: false, // True if month can be selected directly, false if only prev/next + changeYear: false, // True if year can be selected directly, false if only prev/next + yearRange: "c-10:c+10", // Range of years to display in drop-down, + // either relative to today's year (-nn:+nn), relative to currently displayed year + // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n) + showOtherMonths: false, // True to show dates in other months, false to leave blank + selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable + showWeek: false, // True to show week of the year, false to not show it + calculateWeek: this.iso8601Week, // How to calculate the week of the year, + // takes a Date and returns the number of the week for it + shortYearCutoff: "+10", // Short year values < this are in the current century, + // > this are in the previous century, + // string value starting with "+" for current year + value + minDate: null, // The earliest selectable date, or null for no limit + maxDate: null, // The latest selectable date, or null for no limit + duration: "fast", // Duration of display/closure + beforeShowDay: null, // Function that takes a date and returns an array with + // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "", + // [2] = cell title (optional), e.g. $.datepicker.noWeekends + beforeShow: null, // Function that takes an input field and + // returns a set of custom settings for the date picker + onSelect: null, // Define a callback function when a date is selected + onChangeMonthYear: null, // Define a callback function when the month or year is changed + onClose: null, // Define a callback function when the datepicker is closed + numberOfMonths: 1, // Number of months to show at a time + showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) + stepMonths: 1, // Number of months to step back/forward + stepBigMonths: 12, // Number of months to step back/forward for the big links + altField: "", // Selector for an alternate field to store selected dates into + altFormat: "", // The date format to use for the alternate field + constrainInput: true, // The input is constrained by the current date format + showButtonPanel: false, // True to show button panel, false to not show it + autoSize: false, // True to size the input for the date format, false to leave as is + disabled: false // The initial disabled state + }; + $.extend( this._defaults, this.regional[ "" ] ); + this.regional.en = $.extend( true, {}, this.regional[ "" ] ); + this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en ); + this.dpDiv = datepicker_bindHover( $( "
          " ) ); +} + +$.extend( Datepicker.prototype, { + /* Class name added to elements to indicate already configured with a date picker. */ + markerClassName: "hasDatepicker", + + //Keep track of the maximum number of rows displayed (see #7043) + maxRows: 4, + + // TODO rename to "widget" when switching to widget factory + _widgetDatepicker: function() { + return this.dpDiv; + }, + + /* Override the default settings for all instances of the date picker. + * @param settings object - the new settings to use as defaults (anonymous object) + * @return the manager object + */ + setDefaults: function( settings ) { + datepicker_extendRemove( this._defaults, settings || {} ); + return this; + }, + + /* Attach the date picker to a jQuery selection. + * @param target element - the target input field or division or span + * @param settings object - the new settings to use for this date picker instance (anonymous) + */ + _attachDatepicker: function( target, settings ) { + var nodeName, inline, inst; + nodeName = target.nodeName.toLowerCase(); + inline = ( nodeName === "div" || nodeName === "span" ); + if ( !target.id ) { + this.uuid += 1; + target.id = "dp" + this.uuid; + } + inst = this._newInst( $( target ), inline ); + inst.settings = $.extend( {}, settings || {} ); + if ( nodeName === "input" ) { + this._connectDatepicker( target, inst ); + } else if ( inline ) { + this._inlineDatepicker( target, inst ); + } + }, + + /* Create a new instance object. */ + _newInst: function( target, inline ) { + var id = target[ 0 ].id.replace( /([^A-Za-z0-9_\-])/g, "\\\\$1" ); // escape jQuery meta chars + return { id: id, input: target, // associated target + selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection + drawMonth: 0, drawYear: 0, // month being drawn + inline: inline, // is datepicker inline or not + dpDiv: ( !inline ? this.dpDiv : // presentation div + datepicker_bindHover( $( "
          " ) ) ) }; + }, + + /* Attach the date picker to an input field. */ + _connectDatepicker: function( target, inst ) { + var input = $( target ); + inst.append = $( [] ); + inst.trigger = $( [] ); + if ( input.hasClass( this.markerClassName ) ) { + return; + } + this._attachments( input, inst ); + input.addClass( this.markerClassName ).on( "keydown", this._doKeyDown ). + on( "keypress", this._doKeyPress ).on( "keyup", this._doKeyUp ); + this._autoSize( inst ); + $.data( target, "datepicker", inst ); + + //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665) + if ( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + }, + + /* Make attachments based on settings. */ + _attachments: function( input, inst ) { + var showOn, buttonText, buttonImage, + appendText = this._get( inst, "appendText" ), + isRTL = this._get( inst, "isRTL" ); + + if ( inst.append ) { + inst.append.remove(); + } + if ( appendText ) { + inst.append = $( "" + appendText + "" ); + input[ isRTL ? "before" : "after" ]( inst.append ); + } + + input.off( "focus", this._showDatepicker ); + + if ( inst.trigger ) { + inst.trigger.remove(); + } + + showOn = this._get( inst, "showOn" ); + if ( showOn === "focus" || showOn === "both" ) { // pop-up date picker when in the marked field + input.on( "focus", this._showDatepicker ); + } + if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked + buttonText = this._get( inst, "buttonText" ); + buttonImage = this._get( inst, "buttonImage" ); + inst.trigger = $( this._get( inst, "buttonImageOnly" ) ? + $( "" ).addClass( this._triggerClass ). + attr( { src: buttonImage, alt: buttonText, title: buttonText } ) : + $( "" ).addClass( this._triggerClass ). + html( !buttonImage ? buttonText : $( "" ).attr( + { src:buttonImage, alt:buttonText, title:buttonText } ) ) ); + input[ isRTL ? "before" : "after" ]( inst.trigger ); + inst.trigger.on( "click", function() { + if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) { + $.datepicker._hideDatepicker(); + } else if ( $.datepicker._datepickerShowing && $.datepicker._lastInput !== input[ 0 ] ) { + $.datepicker._hideDatepicker(); + $.datepicker._showDatepicker( input[ 0 ] ); + } else { + $.datepicker._showDatepicker( input[ 0 ] ); + } + return false; + } ); + } + }, + + /* Apply the maximum length for the date format. */ + _autoSize: function( inst ) { + if ( this._get( inst, "autoSize" ) && !inst.inline ) { + var findMax, max, maxI, i, + date = new Date( 2009, 12 - 1, 20 ), // Ensure double digits + dateFormat = this._get( inst, "dateFormat" ); + + if ( dateFormat.match( /[DM]/ ) ) { + findMax = function( names ) { + max = 0; + maxI = 0; + for ( i = 0; i < names.length; i++ ) { + if ( names[ i ].length > max ) { + max = names[ i ].length; + maxI = i; + } + } + return maxI; + }; + date.setMonth( findMax( this._get( inst, ( dateFormat.match( /MM/ ) ? + "monthNames" : "monthNamesShort" ) ) ) ); + date.setDate( findMax( this._get( inst, ( dateFormat.match( /DD/ ) ? + "dayNames" : "dayNamesShort" ) ) ) + 20 - date.getDay() ); + } + inst.input.attr( "size", this._formatDate( inst, date ).length ); + } + }, + + /* Attach an inline date picker to a div. */ + _inlineDatepicker: function( target, inst ) { + var divSpan = $( target ); + if ( divSpan.hasClass( this.markerClassName ) ) { + return; + } + divSpan.addClass( this.markerClassName ).append( inst.dpDiv ); + $.data( target, "datepicker", inst ); + this._setDate( inst, this._getDefaultDate( inst ), true ); + this._updateDatepicker( inst ); + this._updateAlternate( inst ); + + //If disabled option is true, disable the datepicker before showing it (see ticket #5665) + if ( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + + // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements + // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height + inst.dpDiv.css( "display", "block" ); + }, + + /* Pop-up the date picker in a "dialog" box. + * @param input element - ignored + * @param date string or Date - the initial date to display + * @param onSelect function - the function to call when a date is selected + * @param settings object - update the dialog date picker instance's settings (anonymous object) + * @param pos int[2] - coordinates for the dialog's position within the screen or + * event - with x/y coordinates or + * leave empty for default (screen centre) + * @return the manager object + */ + _dialogDatepicker: function( input, date, onSelect, settings, pos ) { + var id, browserWidth, browserHeight, scrollX, scrollY, + inst = this._dialogInst; // internal instance + + if ( !inst ) { + this.uuid += 1; + id = "dp" + this.uuid; + this._dialogInput = $( "" ); + this._dialogInput.on( "keydown", this._doKeyDown ); + $( "body" ).append( this._dialogInput ); + inst = this._dialogInst = this._newInst( this._dialogInput, false ); + inst.settings = {}; + $.data( this._dialogInput[ 0 ], "datepicker", inst ); + } + datepicker_extendRemove( inst.settings, settings || {} ); + date = ( date && date.constructor === Date ? this._formatDate( inst, date ) : date ); + this._dialogInput.val( date ); + + this._pos = ( pos ? ( pos.length ? pos : [ pos.pageX, pos.pageY ] ) : null ); + if ( !this._pos ) { + browserWidth = document.documentElement.clientWidth; + browserHeight = document.documentElement.clientHeight; + scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; + scrollY = document.documentElement.scrollTop || document.body.scrollTop; + this._pos = // should use actual width/height below + [ ( browserWidth / 2 ) - 100 + scrollX, ( browserHeight / 2 ) - 150 + scrollY ]; + } + + // Move input on screen for focus, but hidden behind dialog + this._dialogInput.css( "left", ( this._pos[ 0 ] + 20 ) + "px" ).css( "top", this._pos[ 1 ] + "px" ); + inst.settings.onSelect = onSelect; + this._inDialog = true; + this.dpDiv.addClass( this._dialogClass ); + this._showDatepicker( this._dialogInput[ 0 ] ); + if ( $.blockUI ) { + $.blockUI( this.dpDiv ); + } + $.data( this._dialogInput[ 0 ], "datepicker", inst ); + return this; + }, + + /* Detach a datepicker from its control. + * @param target element - the target input field or division or span + */ + _destroyDatepicker: function( target ) { + var nodeName, + $target = $( target ), + inst = $.data( target, "datepicker" ); + + if ( !$target.hasClass( this.markerClassName ) ) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + $.removeData( target, "datepicker" ); + if ( nodeName === "input" ) { + inst.append.remove(); + inst.trigger.remove(); + $target.removeClass( this.markerClassName ). + off( "focus", this._showDatepicker ). + off( "keydown", this._doKeyDown ). + off( "keypress", this._doKeyPress ). + off( "keyup", this._doKeyUp ); + } else if ( nodeName === "div" || nodeName === "span" ) { + $target.removeClass( this.markerClassName ).empty(); + } + + if ( datepicker_instActive === inst ) { + datepicker_instActive = null; + } + }, + + /* Enable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _enableDatepicker: function( target ) { + var nodeName, inline, + $target = $( target ), + inst = $.data( target, "datepicker" ); + + if ( !$target.hasClass( this.markerClassName ) ) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if ( nodeName === "input" ) { + target.disabled = false; + inst.trigger.filter( "button" ). + each( function() { this.disabled = false; } ).end(). + filter( "img" ).css( { opacity: "1.0", cursor: "" } ); + } else if ( nodeName === "div" || nodeName === "span" ) { + inline = $target.children( "." + this._inlineClass ); + inline.children().removeClass( "ui-state-disabled" ); + inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). + prop( "disabled", false ); + } + this._disabledInputs = $.map( this._disabledInputs, + function( value ) { return ( value === target ? null : value ); } ); // delete entry + }, + + /* Disable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _disableDatepicker: function( target ) { + var nodeName, inline, + $target = $( target ), + inst = $.data( target, "datepicker" ); + + if ( !$target.hasClass( this.markerClassName ) ) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if ( nodeName === "input" ) { + target.disabled = true; + inst.trigger.filter( "button" ). + each( function() { this.disabled = true; } ).end(). + filter( "img" ).css( { opacity: "0.5", cursor: "default" } ); + } else if ( nodeName === "div" || nodeName === "span" ) { + inline = $target.children( "." + this._inlineClass ); + inline.children().addClass( "ui-state-disabled" ); + inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). + prop( "disabled", true ); + } + this._disabledInputs = $.map( this._disabledInputs, + function( value ) { return ( value === target ? null : value ); } ); // delete entry + this._disabledInputs[ this._disabledInputs.length ] = target; + }, + + /* Is the first field in a jQuery collection disabled as a datepicker? + * @param target element - the target input field or division or span + * @return boolean - true if disabled, false if enabled + */ + _isDisabledDatepicker: function( target ) { + if ( !target ) { + return false; + } + for ( var i = 0; i < this._disabledInputs.length; i++ ) { + if ( this._disabledInputs[ i ] === target ) { + return true; + } + } + return false; + }, + + /* Retrieve the instance data for the target control. + * @param target element - the target input field or division or span + * @return object - the associated instance data + * @throws error if a jQuery problem getting data + */ + _getInst: function( target ) { + try { + return $.data( target, "datepicker" ); + } + catch ( err ) { + throw "Missing instance data for this datepicker"; + } + }, + + /* Update or retrieve the settings for a date picker attached to an input field or division. + * @param target element - the target input field or division or span + * @param name object - the new settings to update or + * string - the name of the setting to change or retrieve, + * when retrieving also "all" for all instance settings or + * "defaults" for all global defaults + * @param value any - the new value for the setting + * (omit if above is an object or to retrieve a value) + */ + _optionDatepicker: function( target, name, value ) { + var settings, date, minDate, maxDate, + inst = this._getInst( target ); + + if ( arguments.length === 2 && typeof name === "string" ) { + return ( name === "defaults" ? $.extend( {}, $.datepicker._defaults ) : + ( inst ? ( name === "all" ? $.extend( {}, inst.settings ) : + this._get( inst, name ) ) : null ) ); + } + + settings = name || {}; + if ( typeof name === "string" ) { + settings = {}; + settings[ name ] = value; + } + + if ( inst ) { + if ( this._curInst === inst ) { + this._hideDatepicker(); + } + + date = this._getDateDatepicker( target, true ); + minDate = this._getMinMaxDate( inst, "min" ); + maxDate = this._getMinMaxDate( inst, "max" ); + datepicker_extendRemove( inst.settings, settings ); + + // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided + if ( minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined ) { + inst.settings.minDate = this._formatDate( inst, minDate ); + } + if ( maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined ) { + inst.settings.maxDate = this._formatDate( inst, maxDate ); + } + if ( "disabled" in settings ) { + if ( settings.disabled ) { + this._disableDatepicker( target ); + } else { + this._enableDatepicker( target ); + } + } + this._attachments( $( target ), inst ); + this._autoSize( inst ); + this._setDate( inst, date ); + this._updateAlternate( inst ); + this._updateDatepicker( inst ); + } + }, + + // Change method deprecated + _changeDatepicker: function( target, name, value ) { + this._optionDatepicker( target, name, value ); + }, + + /* Redraw the date picker attached to an input field or division. + * @param target element - the target input field or division or span + */ + _refreshDatepicker: function( target ) { + var inst = this._getInst( target ); + if ( inst ) { + this._updateDatepicker( inst ); + } + }, + + /* Set the dates for a jQuery selection. + * @param target element - the target input field or division or span + * @param date Date - the new date + */ + _setDateDatepicker: function( target, date ) { + var inst = this._getInst( target ); + if ( inst ) { + this._setDate( inst, date ); + this._updateDatepicker( inst ); + this._updateAlternate( inst ); + } + }, + + /* Get the date(s) for the first entry in a jQuery selection. + * @param target element - the target input field or division or span + * @param noDefault boolean - true if no default date is to be used + * @return Date - the current date + */ + _getDateDatepicker: function( target, noDefault ) { + var inst = this._getInst( target ); + if ( inst && !inst.inline ) { + this._setDateFromField( inst, noDefault ); + } + return ( inst ? this._getDate( inst ) : null ); + }, + + /* Handle keystrokes. */ + _doKeyDown: function( event ) { + var onSelect, dateStr, sel, + inst = $.datepicker._getInst( event.target ), + handled = true, + isRTL = inst.dpDiv.is( ".ui-datepicker-rtl" ); + + inst._keyEvent = true; + if ( $.datepicker._datepickerShowing ) { + switch ( event.keyCode ) { + case 9: $.datepicker._hideDatepicker(); + handled = false; + break; // hide on tab out + case 13: sel = $( "td." + $.datepicker._dayOverClass + ":not(." + + $.datepicker._currentClass + ")", inst.dpDiv ); + if ( sel[ 0 ] ) { + $.datepicker._selectDay( event.target, inst.selectedMonth, inst.selectedYear, sel[ 0 ] ); + } + + onSelect = $.datepicker._get( inst, "onSelect" ); + if ( onSelect ) { + dateStr = $.datepicker._formatDate( inst ); + + // Trigger custom callback + onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); + } else { + $.datepicker._hideDatepicker(); + } + + return false; // don't submit the form + case 27: $.datepicker._hideDatepicker(); + break; // hide on escape + case 33: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? + -$.datepicker._get( inst, "stepBigMonths" ) : + -$.datepicker._get( inst, "stepMonths" ) ), "M" ); + break; // previous month/year on page up/+ ctrl + case 34: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? + +$.datepicker._get( inst, "stepBigMonths" ) : + +$.datepicker._get( inst, "stepMonths" ) ), "M" ); + break; // next month/year on page down/+ ctrl + case 35: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._clearDate( event.target ); + } + handled = event.ctrlKey || event.metaKey; + break; // clear on ctrl or command +end + case 36: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._gotoToday( event.target ); + } + handled = event.ctrlKey || event.metaKey; + break; // current on ctrl or command +home + case 37: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._adjustDate( event.target, ( isRTL ? +1 : -1 ), "D" ); + } + handled = event.ctrlKey || event.metaKey; + + // -1 day on ctrl or command +left + if ( event.originalEvent.altKey ) { + $.datepicker._adjustDate( event.target, ( event.ctrlKey ? + -$.datepicker._get( inst, "stepBigMonths" ) : + -$.datepicker._get( inst, "stepMonths" ) ), "M" ); + } + + // next month/year on alt +left on Mac + break; + case 38: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._adjustDate( event.target, -7, "D" ); + } + handled = event.ctrlKey || event.metaKey; + break; // -1 week on ctrl or command +up + case 39: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._adjustDate( event.target, ( isRTL ? -1 : +1 ), "D" ); + } + handled = event.ctrlKey || event.metaKey; + + // +1 day on ctrl or command +right + if ( event.originalEvent.altKey ) { + $.datepicker._adjustDate( event.target, ( event.ctrlKey ? + +$.datepicker._get( inst, "stepBigMonths" ) : + +$.datepicker._get( inst, "stepMonths" ) ), "M" ); + } + + // next month/year on alt +right + break; + case 40: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._adjustDate( event.target, +7, "D" ); + } + handled = event.ctrlKey || event.metaKey; + break; // +1 week on ctrl or command +down + default: handled = false; + } + } else if ( event.keyCode === 36 && event.ctrlKey ) { // display the date picker on ctrl+home + $.datepicker._showDatepicker( this ); + } else { + handled = false; + } + + if ( handled ) { + event.preventDefault(); + event.stopPropagation(); + } + }, + + /* Filter entered characters - based on date format. */ + _doKeyPress: function( event ) { + var chars, chr, + inst = $.datepicker._getInst( event.target ); + + if ( $.datepicker._get( inst, "constrainInput" ) ) { + chars = $.datepicker._possibleChars( $.datepicker._get( inst, "dateFormat" ) ); + chr = String.fromCharCode( event.charCode == null ? event.keyCode : event.charCode ); + return event.ctrlKey || event.metaKey || ( chr < " " || !chars || chars.indexOf( chr ) > -1 ); + } + }, + + /* Synchronise manual entry and field/alternate field. */ + _doKeyUp: function( event ) { + var date, + inst = $.datepicker._getInst( event.target ); + + if ( inst.input.val() !== inst.lastVal ) { + try { + date = $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), + ( inst.input ? inst.input.val() : null ), + $.datepicker._getFormatConfig( inst ) ); + + if ( date ) { // only if valid + $.datepicker._setDateFromField( inst ); + $.datepicker._updateAlternate( inst ); + $.datepicker._updateDatepicker( inst ); + } + } + catch ( err ) { + } + } + return true; + }, + + /* Pop-up the date picker for a given input field. + * If false returned from beforeShow event handler do not show. + * @param input element - the input field attached to the date picker or + * event - if triggered by focus + */ + _showDatepicker: function( input ) { + input = input.target || input; + if ( input.nodeName.toLowerCase() !== "input" ) { // find from button/image trigger + input = $( "input", input.parentNode )[ 0 ]; + } + + if ( $.datepicker._isDisabledDatepicker( input ) || $.datepicker._lastInput === input ) { // already here + return; + } + + var inst, beforeShow, beforeShowSettings, isFixed, + offset, showAnim, duration; + + inst = $.datepicker._getInst( input ); + if ( $.datepicker._curInst && $.datepicker._curInst !== inst ) { + $.datepicker._curInst.dpDiv.stop( true, true ); + if ( inst && $.datepicker._datepickerShowing ) { + $.datepicker._hideDatepicker( $.datepicker._curInst.input[ 0 ] ); + } + } + + beforeShow = $.datepicker._get( inst, "beforeShow" ); + beforeShowSettings = beforeShow ? beforeShow.apply( input, [ input, inst ] ) : {}; + if ( beforeShowSettings === false ) { + return; + } + datepicker_extendRemove( inst.settings, beforeShowSettings ); + + inst.lastVal = null; + $.datepicker._lastInput = input; + $.datepicker._setDateFromField( inst ); + + if ( $.datepicker._inDialog ) { // hide cursor + input.value = ""; + } + if ( !$.datepicker._pos ) { // position below input + $.datepicker._pos = $.datepicker._findPos( input ); + $.datepicker._pos[ 1 ] += input.offsetHeight; // add the height + } + + isFixed = false; + $( input ).parents().each( function() { + isFixed |= $( this ).css( "position" ) === "fixed"; + return !isFixed; + } ); + + offset = { left: $.datepicker._pos[ 0 ], top: $.datepicker._pos[ 1 ] }; + $.datepicker._pos = null; + + //to avoid flashes on Firefox + inst.dpDiv.empty(); + + // determine sizing offscreen + inst.dpDiv.css( { position: "absolute", display: "block", top: "-1000px" } ); + $.datepicker._updateDatepicker( inst ); + + // fix width for dynamic number of date pickers + // and adjust position before showing + offset = $.datepicker._checkOffset( inst, offset, isFixed ); + inst.dpDiv.css( { position: ( $.datepicker._inDialog && $.blockUI ? + "static" : ( isFixed ? "fixed" : "absolute" ) ), display: "none", + left: offset.left + "px", top: offset.top + "px" } ); + + if ( !inst.inline ) { + showAnim = $.datepicker._get( inst, "showAnim" ); + duration = $.datepicker._get( inst, "duration" ); + inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 ); + $.datepicker._datepickerShowing = true; + + if ( $.effects && $.effects.effect[ showAnim ] ) { + inst.dpDiv.show( showAnim, $.datepicker._get( inst, "showOptions" ), duration ); + } else { + inst.dpDiv[ showAnim || "show" ]( showAnim ? duration : null ); + } + + if ( $.datepicker._shouldFocusInput( inst ) ) { + inst.input.trigger( "focus" ); + } + + $.datepicker._curInst = inst; + } + }, + + /* Generate the date picker content. */ + _updateDatepicker: function( inst ) { + this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) + datepicker_instActive = inst; // for delegate hover events + inst.dpDiv.empty().append( this._generateHTML( inst ) ); + this._attachHandlers( inst ); + + var origyearshtml, + numMonths = this._getNumberOfMonths( inst ), + cols = numMonths[ 1 ], + width = 17, + activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ); + + if ( activeCell.length > 0 ) { + datepicker_handleMouseover.apply( activeCell.get( 0 ) ); + } + + inst.dpDiv.removeClass( "ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4" ).width( "" ); + if ( cols > 1 ) { + inst.dpDiv.addClass( "ui-datepicker-multi-" + cols ).css( "width", ( width * cols ) + "em" ); + } + inst.dpDiv[ ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ? "add" : "remove" ) + + "Class" ]( "ui-datepicker-multi" ); + inst.dpDiv[ ( this._get( inst, "isRTL" ) ? "add" : "remove" ) + + "Class" ]( "ui-datepicker-rtl" ); + + if ( inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { + inst.input.trigger( "focus" ); + } + + // Deffered render of the years select (to avoid flashes on Firefox) + if ( inst.yearshtml ) { + origyearshtml = inst.yearshtml; + setTimeout( function() { + + //assure that inst.yearshtml didn't change. + if ( origyearshtml === inst.yearshtml && inst.yearshtml ) { + inst.dpDiv.find( "select.ui-datepicker-year:first" ).replaceWith( inst.yearshtml ); + } + origyearshtml = inst.yearshtml = null; + }, 0 ); + } + }, + + // #6694 - don't focus the input if it's already focused + // this breaks the change event in IE + // Support: IE and jQuery <1.9 + _shouldFocusInput: function( inst ) { + return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); + }, + + /* Check positioning to remain on screen. */ + _checkOffset: function( inst, offset, isFixed ) { + var dpWidth = inst.dpDiv.outerWidth(), + dpHeight = inst.dpDiv.outerHeight(), + inputWidth = inst.input ? inst.input.outerWidth() : 0, + inputHeight = inst.input ? inst.input.outerHeight() : 0, + viewWidth = document.documentElement.clientWidth + ( isFixed ? 0 : $( document ).scrollLeft() ), + viewHeight = document.documentElement.clientHeight + ( isFixed ? 0 : $( document ).scrollTop() ); + + offset.left -= ( this._get( inst, "isRTL" ) ? ( dpWidth - inputWidth ) : 0 ); + offset.left -= ( isFixed && offset.left === inst.input.offset().left ) ? $( document ).scrollLeft() : 0; + offset.top -= ( isFixed && offset.top === ( inst.input.offset().top + inputHeight ) ) ? $( document ).scrollTop() : 0; + + // Now check if datepicker is showing outside window viewport - move to a better place if so. + offset.left -= Math.min( offset.left, ( offset.left + dpWidth > viewWidth && viewWidth > dpWidth ) ? + Math.abs( offset.left + dpWidth - viewWidth ) : 0 ); + offset.top -= Math.min( offset.top, ( offset.top + dpHeight > viewHeight && viewHeight > dpHeight ) ? + Math.abs( dpHeight + inputHeight ) : 0 ); + + return offset; + }, + + /* Find an object's position on the screen. */ + _findPos: function( obj ) { + var position, + inst = this._getInst( obj ), + isRTL = this._get( inst, "isRTL" ); + + while ( obj && ( obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden( obj ) ) ) { + obj = obj[ isRTL ? "previousSibling" : "nextSibling" ]; + } + + position = $( obj ).offset(); + return [ position.left, position.top ]; + }, + + /* Hide the date picker from view. + * @param input element - the input field attached to the date picker + */ + _hideDatepicker: function( input ) { + var showAnim, duration, postProcess, onClose, + inst = this._curInst; + + if ( !inst || ( input && inst !== $.data( input, "datepicker" ) ) ) { + return; + } + + if ( this._datepickerShowing ) { + showAnim = this._get( inst, "showAnim" ); + duration = this._get( inst, "duration" ); + postProcess = function() { + $.datepicker._tidyDialog( inst ); + }; + + // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed + if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { + inst.dpDiv.hide( showAnim, $.datepicker._get( inst, "showOptions" ), duration, postProcess ); + } else { + inst.dpDiv[ ( showAnim === "slideDown" ? "slideUp" : + ( showAnim === "fadeIn" ? "fadeOut" : "hide" ) ) ]( ( showAnim ? duration : null ), postProcess ); + } + + if ( !showAnim ) { + postProcess(); + } + this._datepickerShowing = false; + + onClose = this._get( inst, "onClose" ); + if ( onClose ) { + onClose.apply( ( inst.input ? inst.input[ 0 ] : null ), [ ( inst.input ? inst.input.val() : "" ), inst ] ); + } + + this._lastInput = null; + if ( this._inDialog ) { + this._dialogInput.css( { position: "absolute", left: "0", top: "-100px" } ); + if ( $.blockUI ) { + $.unblockUI(); + $( "body" ).append( this.dpDiv ); + } + } + this._inDialog = false; + } + }, + + /* Tidy up after a dialog display. */ + _tidyDialog: function( inst ) { + inst.dpDiv.removeClass( this._dialogClass ).off( ".ui-datepicker-calendar" ); + }, + + /* Close date picker if clicked elsewhere. */ + _checkExternalClick: function( event ) { + if ( !$.datepicker._curInst ) { + return; + } + + var $target = $( event.target ), + inst = $.datepicker._getInst( $target[ 0 ] ); + + if ( ( ( $target[ 0 ].id !== $.datepicker._mainDivId && + $target.parents( "#" + $.datepicker._mainDivId ).length === 0 && + !$target.hasClass( $.datepicker.markerClassName ) && + !$target.closest( "." + $.datepicker._triggerClass ).length && + $.datepicker._datepickerShowing && !( $.datepicker._inDialog && $.blockUI ) ) ) || + ( $target.hasClass( $.datepicker.markerClassName ) && $.datepicker._curInst !== inst ) ) { + $.datepicker._hideDatepicker(); + } + }, + + /* Adjust one of the date sub-fields. */ + _adjustDate: function( id, offset, period ) { + var target = $( id ), + inst = this._getInst( target[ 0 ] ); + + if ( this._isDisabledDatepicker( target[ 0 ] ) ) { + return; + } + this._adjustInstDate( inst, offset + + ( period === "M" ? this._get( inst, "showCurrentAtPos" ) : 0 ), // undo positioning + period ); + this._updateDatepicker( inst ); + }, + + /* Action for current link. */ + _gotoToday: function( id ) { + var date, + target = $( id ), + inst = this._getInst( target[ 0 ] ); + + if ( this._get( inst, "gotoCurrent" ) && inst.currentDay ) { + inst.selectedDay = inst.currentDay; + inst.drawMonth = inst.selectedMonth = inst.currentMonth; + inst.drawYear = inst.selectedYear = inst.currentYear; + } else { + date = new Date(); + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + } + this._notifyChange( inst ); + this._adjustDate( target ); + }, + + /* Action for selecting a new month/year. */ + _selectMonthYear: function( id, select, period ) { + var target = $( id ), + inst = this._getInst( target[ 0 ] ); + + inst[ "selected" + ( period === "M" ? "Month" : "Year" ) ] = + inst[ "draw" + ( period === "M" ? "Month" : "Year" ) ] = + parseInt( select.options[ select.selectedIndex ].value, 10 ); + + this._notifyChange( inst ); + this._adjustDate( target ); + }, + + /* Action for selecting a day. */ + _selectDay: function( id, month, year, td ) { + var inst, + target = $( id ); + + if ( $( td ).hasClass( this._unselectableClass ) || this._isDisabledDatepicker( target[ 0 ] ) ) { + return; + } + + inst = this._getInst( target[ 0 ] ); + inst.selectedDay = inst.currentDay = $( "a", td ).html(); + inst.selectedMonth = inst.currentMonth = month; + inst.selectedYear = inst.currentYear = year; + this._selectDate( id, this._formatDate( inst, + inst.currentDay, inst.currentMonth, inst.currentYear ) ); + }, + + /* Erase the input field and hide the date picker. */ + _clearDate: function( id ) { + var target = $( id ); + this._selectDate( target, "" ); + }, + + /* Update the input field with the selected date. */ + _selectDate: function( id, dateStr ) { + var onSelect, + target = $( id ), + inst = this._getInst( target[ 0 ] ); + + dateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) ); + if ( inst.input ) { + inst.input.val( dateStr ); + } + this._updateAlternate( inst ); + + onSelect = this._get( inst, "onSelect" ); + if ( onSelect ) { + onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); // trigger custom callback + } else if ( inst.input ) { + inst.input.trigger( "change" ); // fire the change event + } + + if ( inst.inline ) { + this._updateDatepicker( inst ); + } else { + this._hideDatepicker(); + this._lastInput = inst.input[ 0 ]; + if ( typeof( inst.input[ 0 ] ) !== "object" ) { + inst.input.trigger( "focus" ); // restore focus + } + this._lastInput = null; + } + }, + + /* Update any alternate field to synchronise with the main field. */ + _updateAlternate: function( inst ) { + var altFormat, date, dateStr, + altField = this._get( inst, "altField" ); + + if ( altField ) { // update alternate field too + altFormat = this._get( inst, "altFormat" ) || this._get( inst, "dateFormat" ); + date = this._getDate( inst ); + dateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) ); + $( altField ).val( dateStr ); + } + }, + + /* Set as beforeShowDay function to prevent selection of weekends. + * @param date Date - the date to customise + * @return [boolean, string] - is this date selectable?, what is its CSS class? + */ + noWeekends: function( date ) { + var day = date.getDay(); + return [ ( day > 0 && day < 6 ), "" ]; + }, + + /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + * @param date Date - the date to get the week for + * @return number - the number of the week within the year that contains this date + */ + iso8601Week: function( date ) { + var time, + checkDate = new Date( date.getTime() ); + + // Find Thursday of this week starting on Monday + checkDate.setDate( checkDate.getDate() + 4 - ( checkDate.getDay() || 7 ) ); + + time = checkDate.getTime(); + checkDate.setMonth( 0 ); // Compare with Jan 1 + checkDate.setDate( 1 ); + return Math.floor( Math.round( ( time - checkDate ) / 86400000 ) / 7 ) + 1; + }, + + /* Parse a string value into a date object. + * See formatDate below for the possible formats. + * + * @param format string - the expected format of the date + * @param value string - the date in the above format + * @param settings Object - attributes include: + * shortYearCutoff number - the cutoff year for determining the century (optional) + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return Date - the extracted date value or null if value is blank + */ + parseDate: function( format, value, settings ) { + if ( format == null || value == null ) { + throw "Invalid arguments"; + } + + value = ( typeof value === "object" ? value.toString() : value + "" ); + if ( value === "" ) { + return null; + } + + var iFormat, dim, extra, + iValue = 0, + shortYearCutoffTemp = ( settings ? settings.shortYearCutoff : null ) || this._defaults.shortYearCutoff, + shortYearCutoff = ( typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : + new Date().getFullYear() % 100 + parseInt( shortYearCutoffTemp, 10 ) ), + dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, + dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, + monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, + monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, + year = -1, + month = -1, + day = -1, + doy = -1, + literal = false, + date, + + // Check whether a format character is doubled + lookAhead = function( match ) { + var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); + if ( matches ) { + iFormat++; + } + return matches; + }, + + // Extract a number from the string value + getNumber = function( match ) { + var isDoubled = lookAhead( match ), + size = ( match === "@" ? 14 : ( match === "!" ? 20 : + ( match === "y" && isDoubled ? 4 : ( match === "o" ? 3 : 2 ) ) ) ), + minSize = ( match === "y" ? size : 1 ), + digits = new RegExp( "^\\d{" + minSize + "," + size + "}" ), + num = value.substring( iValue ).match( digits ); + if ( !num ) { + throw "Missing number at position " + iValue; + } + iValue += num[ 0 ].length; + return parseInt( num[ 0 ], 10 ); + }, + + // Extract a name from the string value and convert to an index + getName = function( match, shortNames, longNames ) { + var index = -1, + names = $.map( lookAhead( match ) ? longNames : shortNames, function( v, k ) { + return [ [ k, v ] ]; + } ).sort( function( a, b ) { + return -( a[ 1 ].length - b[ 1 ].length ); + } ); + + $.each( names, function( i, pair ) { + var name = pair[ 1 ]; + if ( value.substr( iValue, name.length ).toLowerCase() === name.toLowerCase() ) { + index = pair[ 0 ]; + iValue += name.length; + return false; + } + } ); + if ( index !== -1 ) { + return index + 1; + } else { + throw "Unknown name at position " + iValue; + } + }, + + // Confirm that a literal character matches the string value + checkLiteral = function() { + if ( value.charAt( iValue ) !== format.charAt( iFormat ) ) { + throw "Unexpected literal at position " + iValue; + } + iValue++; + }; + + for ( iFormat = 0; iFormat < format.length; iFormat++ ) { + if ( literal ) { + if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { + literal = false; + } else { + checkLiteral(); + } + } else { + switch ( format.charAt( iFormat ) ) { + case "d": + day = getNumber( "d" ); + break; + case "D": + getName( "D", dayNamesShort, dayNames ); + break; + case "o": + doy = getNumber( "o" ); + break; + case "m": + month = getNumber( "m" ); + break; + case "M": + month = getName( "M", monthNamesShort, monthNames ); + break; + case "y": + year = getNumber( "y" ); + break; + case "@": + date = new Date( getNumber( "@" ) ); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "!": + date = new Date( ( getNumber( "!" ) - this._ticksTo1970 ) / 10000 ); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "'": + if ( lookAhead( "'" ) ) { + checkLiteral(); + } else { + literal = true; + } + break; + default: + checkLiteral(); + } + } + } + + if ( iValue < value.length ) { + extra = value.substr( iValue ); + if ( !/^\s+/.test( extra ) ) { + throw "Extra/unparsed characters found in date: " + extra; + } + } + + if ( year === -1 ) { + year = new Date().getFullYear(); + } else if ( year < 100 ) { + year += new Date().getFullYear() - new Date().getFullYear() % 100 + + ( year <= shortYearCutoff ? 0 : -100 ); + } + + if ( doy > -1 ) { + month = 1; + day = doy; + do { + dim = this._getDaysInMonth( year, month - 1 ); + if ( day <= dim ) { + break; + } + month++; + day -= dim; + } while ( true ); + } + + date = this._daylightSavingAdjust( new Date( year, month - 1, day ) ); + if ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) { + throw "Invalid date"; // E.g. 31/02/00 + } + return date; + }, + + /* Standard date formats. */ + ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) + COOKIE: "D, dd M yy", + ISO_8601: "yy-mm-dd", + RFC_822: "D, d M y", + RFC_850: "DD, dd-M-y", + RFC_1036: "D, d M y", + RFC_1123: "D, d M yy", + RFC_2822: "D, d M yy", + RSS: "D, d M y", // RFC 822 + TICKS: "!", + TIMESTAMP: "@", + W3C: "yy-mm-dd", // ISO 8601 + + _ticksTo1970: ( ( ( 1970 - 1 ) * 365 + Math.floor( 1970 / 4 ) - Math.floor( 1970 / 100 ) + + Math.floor( 1970 / 400 ) ) * 24 * 60 * 60 * 10000000 ), + + /* Format a date object into a string value. + * The format can be combinations of the following: + * d - day of month (no leading zero) + * dd - day of month (two digit) + * o - day of year (no leading zeros) + * oo - day of year (three digit) + * D - day name short + * DD - day name long + * m - month of year (no leading zero) + * mm - month of year (two digit) + * M - month name short + * MM - month name long + * y - year (two digit) + * yy - year (four digit) + * @ - Unix timestamp (ms since 01/01/1970) + * ! - Windows ticks (100ns since 01/01/0001) + * "..." - literal text + * '' - single quote + * + * @param format string - the desired format of the date + * @param date Date - the date value to format + * @param settings Object - attributes include: + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return string - the date in the above format + */ + formatDate: function( format, date, settings ) { + if ( !date ) { + return ""; + } + + var iFormat, + dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, + dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, + monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, + monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, + + // Check whether a format character is doubled + lookAhead = function( match ) { + var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); + if ( matches ) { + iFormat++; + } + return matches; + }, + + // Format a number, with leading zero if necessary + formatNumber = function( match, value, len ) { + var num = "" + value; + if ( lookAhead( match ) ) { + while ( num.length < len ) { + num = "0" + num; + } + } + return num; + }, + + // Format a name, short or long as requested + formatName = function( match, value, shortNames, longNames ) { + return ( lookAhead( match ) ? longNames[ value ] : shortNames[ value ] ); + }, + output = "", + literal = false; + + if ( date ) { + for ( iFormat = 0; iFormat < format.length; iFormat++ ) { + if ( literal ) { + if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { + literal = false; + } else { + output += format.charAt( iFormat ); + } + } else { + switch ( format.charAt( iFormat ) ) { + case "d": + output += formatNumber( "d", date.getDate(), 2 ); + break; + case "D": + output += formatName( "D", date.getDay(), dayNamesShort, dayNames ); + break; + case "o": + output += formatNumber( "o", + Math.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 ); + break; + case "m": + output += formatNumber( "m", date.getMonth() + 1, 2 ); + break; + case "M": + output += formatName( "M", date.getMonth(), monthNamesShort, monthNames ); + break; + case "y": + output += ( lookAhead( "y" ) ? date.getFullYear() : + ( date.getFullYear() % 100 < 10 ? "0" : "" ) + date.getFullYear() % 100 ); + break; + case "@": + output += date.getTime(); + break; + case "!": + output += date.getTime() * 10000 + this._ticksTo1970; + break; + case "'": + if ( lookAhead( "'" ) ) { + output += "'"; + } else { + literal = true; + } + break; + default: + output += format.charAt( iFormat ); + } + } + } + } + return output; + }, + + /* Extract all possible characters from the date format. */ + _possibleChars: function( format ) { + var iFormat, + chars = "", + literal = false, + + // Check whether a format character is doubled + lookAhead = function( match ) { + var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); + if ( matches ) { + iFormat++; + } + return matches; + }; + + for ( iFormat = 0; iFormat < format.length; iFormat++ ) { + if ( literal ) { + if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { + literal = false; + } else { + chars += format.charAt( iFormat ); + } + } else { + switch ( format.charAt( iFormat ) ) { + case "d": case "m": case "y": case "@": + chars += "0123456789"; + break; + case "D": case "M": + return null; // Accept anything + case "'": + if ( lookAhead( "'" ) ) { + chars += "'"; + } else { + literal = true; + } + break; + default: + chars += format.charAt( iFormat ); + } + } + } + return chars; + }, + + /* Get a setting value, defaulting if necessary. */ + _get: function( inst, name ) { + return inst.settings[ name ] !== undefined ? + inst.settings[ name ] : this._defaults[ name ]; + }, + + /* Parse existing date and initialise date picker. */ + _setDateFromField: function( inst, noDefault ) { + if ( inst.input.val() === inst.lastVal ) { + return; + } + + var dateFormat = this._get( inst, "dateFormat" ), + dates = inst.lastVal = inst.input ? inst.input.val() : null, + defaultDate = this._getDefaultDate( inst ), + date = defaultDate, + settings = this._getFormatConfig( inst ); + + try { + date = this.parseDate( dateFormat, dates, settings ) || defaultDate; + } catch ( event ) { + dates = ( noDefault ? "" : dates ); + } + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + inst.currentDay = ( dates ? date.getDate() : 0 ); + inst.currentMonth = ( dates ? date.getMonth() : 0 ); + inst.currentYear = ( dates ? date.getFullYear() : 0 ); + this._adjustInstDate( inst ); + }, + + /* Retrieve the default date shown on opening. */ + _getDefaultDate: function( inst ) { + return this._restrictMinMax( inst, + this._determineDate( inst, this._get( inst, "defaultDate" ), new Date() ) ); + }, + + /* A date may be specified as an exact value or a relative one. */ + _determineDate: function( inst, date, defaultDate ) { + var offsetNumeric = function( offset ) { + var date = new Date(); + date.setDate( date.getDate() + offset ); + return date; + }, + offsetString = function( offset ) { + try { + return $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), + offset, $.datepicker._getFormatConfig( inst ) ); + } + catch ( e ) { + + // Ignore + } + + var date = ( offset.toLowerCase().match( /^c/ ) ? + $.datepicker._getDate( inst ) : null ) || new Date(), + year = date.getFullYear(), + month = date.getMonth(), + day = date.getDate(), + pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, + matches = pattern.exec( offset ); + + while ( matches ) { + switch ( matches[ 2 ] || "d" ) { + case "d" : case "D" : + day += parseInt( matches[ 1 ], 10 ); break; + case "w" : case "W" : + day += parseInt( matches[ 1 ], 10 ) * 7; break; + case "m" : case "M" : + month += parseInt( matches[ 1 ], 10 ); + day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); + break; + case "y": case "Y" : + year += parseInt( matches[ 1 ], 10 ); + day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); + break; + } + matches = pattern.exec( offset ); + } + return new Date( year, month, day ); + }, + newDate = ( date == null || date === "" ? defaultDate : ( typeof date === "string" ? offsetString( date ) : + ( typeof date === "number" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) ); + + newDate = ( newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate ); + if ( newDate ) { + newDate.setHours( 0 ); + newDate.setMinutes( 0 ); + newDate.setSeconds( 0 ); + newDate.setMilliseconds( 0 ); + } + return this._daylightSavingAdjust( newDate ); + }, + + /* Handle switch to/from daylight saving. + * Hours may be non-zero on daylight saving cut-over: + * > 12 when midnight changeover, but then cannot generate + * midnight datetime, so jump to 1AM, otherwise reset. + * @param date (Date) the date to check + * @return (Date) the corrected date + */ + _daylightSavingAdjust: function( date ) { + if ( !date ) { + return null; + } + date.setHours( date.getHours() > 12 ? date.getHours() + 2 : 0 ); + return date; + }, + + /* Set the date(s) directly. */ + _setDate: function( inst, date, noChange ) { + var clear = !date, + origMonth = inst.selectedMonth, + origYear = inst.selectedYear, + newDate = this._restrictMinMax( inst, this._determineDate( inst, date, new Date() ) ); + + inst.selectedDay = inst.currentDay = newDate.getDate(); + inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); + inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); + if ( ( origMonth !== inst.selectedMonth || origYear !== inst.selectedYear ) && !noChange ) { + this._notifyChange( inst ); + } + this._adjustInstDate( inst ); + if ( inst.input ) { + inst.input.val( clear ? "" : this._formatDate( inst ) ); + } + }, + + /* Retrieve the date(s) directly. */ + _getDate: function( inst ) { + var startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === "" ) ? null : + this._daylightSavingAdjust( new Date( + inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); + return startDate; + }, + + /* Attach the onxxx handlers. These are declared statically so + * they work with static code transformers like Caja. + */ + _attachHandlers: function( inst ) { + var stepMonths = this._get( inst, "stepMonths" ), + id = "#" + inst.id.replace( /\\\\/g, "\\" ); + inst.dpDiv.find( "[data-handler]" ).map( function() { + var handler = { + prev: function() { + $.datepicker._adjustDate( id, -stepMonths, "M" ); + }, + next: function() { + $.datepicker._adjustDate( id, +stepMonths, "M" ); + }, + hide: function() { + $.datepicker._hideDatepicker(); + }, + today: function() { + $.datepicker._gotoToday( id ); + }, + selectDay: function() { + $.datepicker._selectDay( id, +this.getAttribute( "data-month" ), +this.getAttribute( "data-year" ), this ); + return false; + }, + selectMonth: function() { + $.datepicker._selectMonthYear( id, this, "M" ); + return false; + }, + selectYear: function() { + $.datepicker._selectMonthYear( id, this, "Y" ); + return false; + } + }; + $( this ).on( this.getAttribute( "data-event" ), handler[ this.getAttribute( "data-handler" ) ] ); + } ); + }, + + /* Generate the HTML for the current state of the date picker. */ + _generateHTML: function( inst ) { + var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, + controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, + monthNames, monthNamesShort, beforeShowDay, showOtherMonths, + selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, + cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, + printDate, dRow, tbody, daySettings, otherMonth, unselectable, + tempDate = new Date(), + today = this._daylightSavingAdjust( + new Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time + isRTL = this._get( inst, "isRTL" ), + showButtonPanel = this._get( inst, "showButtonPanel" ), + hideIfNoPrevNext = this._get( inst, "hideIfNoPrevNext" ), + navigationAsDateFormat = this._get( inst, "navigationAsDateFormat" ), + numMonths = this._getNumberOfMonths( inst ), + showCurrentAtPos = this._get( inst, "showCurrentAtPos" ), + stepMonths = this._get( inst, "stepMonths" ), + isMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ), + currentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) : + new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ), + minDate = this._getMinMaxDate( inst, "min" ), + maxDate = this._getMinMaxDate( inst, "max" ), + drawMonth = inst.drawMonth - showCurrentAtPos, + drawYear = inst.drawYear; + + if ( drawMonth < 0 ) { + drawMonth += 12; + drawYear--; + } + if ( maxDate ) { + maxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(), + maxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) ); + maxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw ); + while ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) { + drawMonth--; + if ( drawMonth < 0 ) { + drawMonth = 11; + drawYear--; + } + } + } + inst.drawMonth = drawMonth; + inst.drawYear = drawYear; + + prevText = this._get( inst, "prevText" ); + prevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText, + this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ), + this._getFormatConfig( inst ) ) ); + + prev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ? + "
          " + prevText + "" : + ( hideIfNoPrevNext ? "" : "" + prevText + "" ) ); + + nextText = this._get( inst, "nextText" ); + nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText, + this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ), + this._getFormatConfig( inst ) ) ); + + next = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ? + "" + nextText + "" : + ( hideIfNoPrevNext ? "" : "" + nextText + "" ) ); + + currentText = this._get( inst, "currentText" ); + gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today ); + currentText = ( !navigationAsDateFormat ? currentText : + this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) ); + + controls = ( !inst.inline ? "" : "" ); + + buttonPanel = ( showButtonPanel ) ? "
          " + ( isRTL ? controls : "" ) + + ( this._isInRange( inst, gotoDate ) ? "" : "" ) + ( isRTL ? "" : controls ) + "
          " : ""; + + firstDay = parseInt( this._get( inst, "firstDay" ), 10 ); + firstDay = ( isNaN( firstDay ) ? 0 : firstDay ); + + showWeek = this._get( inst, "showWeek" ); + dayNames = this._get( inst, "dayNames" ); + dayNamesMin = this._get( inst, "dayNamesMin" ); + monthNames = this._get( inst, "monthNames" ); + monthNamesShort = this._get( inst, "monthNamesShort" ); + beforeShowDay = this._get( inst, "beforeShowDay" ); + showOtherMonths = this._get( inst, "showOtherMonths" ); + selectOtherMonths = this._get( inst, "selectOtherMonths" ); + defaultDate = this._getDefaultDate( inst ); + html = ""; + + for ( row = 0; row < numMonths[ 0 ]; row++ ) { + group = ""; + this.maxRows = 4; + for ( col = 0; col < numMonths[ 1 ]; col++ ) { + selectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) ); + cornerClass = " ui-corner-all"; + calender = ""; + if ( isMultiMonth ) { + calender += "
          "; + } + calender += "
          " + + ( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : "" ) + + ( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : "" ) + + this._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate, + row > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers + "
          " + + ""; + thead = ( showWeek ? "" : "" ); + for ( dow = 0; dow < 7; dow++ ) { // days of the week + day = ( dow + firstDay ) % 7; + thead += ""; + } + calender += thead + ""; + daysInMonth = this._getDaysInMonth( drawYear, drawMonth ); + if ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) { + inst.selectedDay = Math.min( inst.selectedDay, daysInMonth ); + } + leadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7; + curRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate + numRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043) + this.maxRows = numRows; + printDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) ); + for ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows + calender += ""; + tbody = ( !showWeek ? "" : "" ); + for ( dow = 0; dow < 7; dow++ ) { // create date picker days + daySettings = ( beforeShowDay ? + beforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, "" ] ); + otherMonth = ( printDate.getMonth() !== drawMonth ); + unselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] || + ( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate ); + tbody += ""; // display selectable date + printDate.setDate( printDate.getDate() + 1 ); + printDate = this._daylightSavingAdjust( printDate ); + } + calender += tbody + ""; + } + drawMonth++; + if ( drawMonth > 11 ) { + drawMonth = 0; + drawYear++; + } + calender += "
          " + this._get( inst, "weekHeader" ) + "= 5 ? " class='ui-datepicker-week-end'" : "" ) + ">" + + "" + dayNamesMin[ day ] + "
          " + + this._get( inst, "calculateWeek" )( printDate ) + "" + // actions + ( otherMonth && !showOtherMonths ? " " : // display for other months + ( unselectable ? "" + printDate.getDate() + "" : "" + printDate.getDate() + "" ) ) + "
          " + ( isMultiMonth ? "
          " + + ( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? "
          " : "" ) : "" ); + group += calender; + } + html += group; + } + html += buttonPanel; + inst._keyEvent = false; + return html; + }, + + /* Generate the month and year header. */ + _generateMonthYearHeader: function( inst, drawMonth, drawYear, minDate, maxDate, + secondary, monthNames, monthNamesShort ) { + + var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, + changeMonth = this._get( inst, "changeMonth" ), + changeYear = this._get( inst, "changeYear" ), + showMonthAfterYear = this._get( inst, "showMonthAfterYear" ), + html = "
          ", + monthHtml = ""; + + // Month selection + if ( secondary || !changeMonth ) { + monthHtml += "" + monthNames[ drawMonth ] + ""; + } else { + inMinYear = ( minDate && minDate.getFullYear() === drawYear ); + inMaxYear = ( maxDate && maxDate.getFullYear() === drawYear ); + monthHtml += ""; + } + + if ( !showMonthAfterYear ) { + html += monthHtml + ( secondary || !( changeMonth && changeYear ) ? " " : "" ); + } + + // Year selection + if ( !inst.yearshtml ) { + inst.yearshtml = ""; + if ( secondary || !changeYear ) { + html += "" + drawYear + ""; + } else { + + // determine range of years to display + years = this._get( inst, "yearRange" ).split( ":" ); + thisYear = new Date().getFullYear(); + determineYear = function( value ) { + var year = ( value.match( /c[+\-].*/ ) ? drawYear + parseInt( value.substring( 1 ), 10 ) : + ( value.match( /[+\-].*/ ) ? thisYear + parseInt( value, 10 ) : + parseInt( value, 10 ) ) ); + return ( isNaN( year ) ? thisYear : year ); + }; + year = determineYear( years[ 0 ] ); + endYear = Math.max( year, determineYear( years[ 1 ] || "" ) ); + year = ( minDate ? Math.max( year, minDate.getFullYear() ) : year ); + endYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear ); + inst.yearshtml += ""; + + html += inst.yearshtml; + inst.yearshtml = null; + } + } + + html += this._get( inst, "yearSuffix" ); + if ( showMonthAfterYear ) { + html += ( secondary || !( changeMonth && changeYear ) ? " " : "" ) + monthHtml; + } + html += "
          "; // Close datepicker_header + return html; + }, + + /* Adjust one of the date sub-fields. */ + _adjustInstDate: function( inst, offset, period ) { + var year = inst.selectedYear + ( period === "Y" ? offset : 0 ), + month = inst.selectedMonth + ( period === "M" ? offset : 0 ), + day = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === "D" ? offset : 0 ), + date = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) ); + + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + if ( period === "M" || period === "Y" ) { + this._notifyChange( inst ); + } + }, + + /* Ensure a date is within any min/max bounds. */ + _restrictMinMax: function( inst, date ) { + var minDate = this._getMinMaxDate( inst, "min" ), + maxDate = this._getMinMaxDate( inst, "max" ), + newDate = ( minDate && date < minDate ? minDate : date ); + return ( maxDate && newDate > maxDate ? maxDate : newDate ); + }, + + /* Notify change of month/year. */ + _notifyChange: function( inst ) { + var onChange = this._get( inst, "onChangeMonthYear" ); + if ( onChange ) { + onChange.apply( ( inst.input ? inst.input[ 0 ] : null ), + [ inst.selectedYear, inst.selectedMonth + 1, inst ] ); + } + }, + + /* Determine the number of months to show. */ + _getNumberOfMonths: function( inst ) { + var numMonths = this._get( inst, "numberOfMonths" ); + return ( numMonths == null ? [ 1, 1 ] : ( typeof numMonths === "number" ? [ 1, numMonths ] : numMonths ) ); + }, + + /* Determine the current maximum date - ensure no time components are set. */ + _getMinMaxDate: function( inst, minMax ) { + return this._determineDate( inst, this._get( inst, minMax + "Date" ), null ); + }, + + /* Find the number of days in a given month. */ + _getDaysInMonth: function( year, month ) { + return 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate(); + }, + + /* Find the day of the week of the first of a month. */ + _getFirstDayOfMonth: function( year, month ) { + return new Date( year, month, 1 ).getDay(); + }, + + /* Determines if we should allow a "next/prev" month display change. */ + _canAdjustMonth: function( inst, offset, curYear, curMonth ) { + var numMonths = this._getNumberOfMonths( inst ), + date = this._daylightSavingAdjust( new Date( curYear, + curMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) ); + + if ( offset < 0 ) { + date.setDate( this._getDaysInMonth( date.getFullYear(), date.getMonth() ) ); + } + return this._isInRange( inst, date ); + }, + + /* Is the given date in the accepted range? */ + _isInRange: function( inst, date ) { + var yearSplit, currentYear, + minDate = this._getMinMaxDate( inst, "min" ), + maxDate = this._getMinMaxDate( inst, "max" ), + minYear = null, + maxYear = null, + years = this._get( inst, "yearRange" ); + if ( years ) { + yearSplit = years.split( ":" ); + currentYear = new Date().getFullYear(); + minYear = parseInt( yearSplit[ 0 ], 10 ); + maxYear = parseInt( yearSplit[ 1 ], 10 ); + if ( yearSplit[ 0 ].match( /[+\-].*/ ) ) { + minYear += currentYear; + } + if ( yearSplit[ 1 ].match( /[+\-].*/ ) ) { + maxYear += currentYear; + } + } + + return ( ( !minDate || date.getTime() >= minDate.getTime() ) && + ( !maxDate || date.getTime() <= maxDate.getTime() ) && + ( !minYear || date.getFullYear() >= minYear ) && + ( !maxYear || date.getFullYear() <= maxYear ) ); + }, + + /* Provide the configuration settings for formatting/parsing. */ + _getFormatConfig: function( inst ) { + var shortYearCutoff = this._get( inst, "shortYearCutoff" ); + shortYearCutoff = ( typeof shortYearCutoff !== "string" ? shortYearCutoff : + new Date().getFullYear() % 100 + parseInt( shortYearCutoff, 10 ) ); + return { shortYearCutoff: shortYearCutoff, + dayNamesShort: this._get( inst, "dayNamesShort" ), dayNames: this._get( inst, "dayNames" ), + monthNamesShort: this._get( inst, "monthNamesShort" ), monthNames: this._get( inst, "monthNames" ) }; + }, + + /* Format the given date for display. */ + _formatDate: function( inst, day, month, year ) { + if ( !day ) { + inst.currentDay = inst.selectedDay; + inst.currentMonth = inst.selectedMonth; + inst.currentYear = inst.selectedYear; + } + var date = ( day ? ( typeof day === "object" ? day : + this._daylightSavingAdjust( new Date( year, month, day ) ) ) : + this._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); + return this.formatDate( this._get( inst, "dateFormat" ), date, this._getFormatConfig( inst ) ); + } +} ); + +/* + * Bind hover events for datepicker elements. + * Done via delegate so the binding only occurs once in the lifetime of the parent div. + * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. + */ +function datepicker_bindHover( dpDiv ) { + var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; + return dpDiv.on( "mouseout", selector, function() { + $( this ).removeClass( "ui-state-hover" ); + if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { + $( this ).removeClass( "ui-datepicker-prev-hover" ); + } + if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { + $( this ).removeClass( "ui-datepicker-next-hover" ); + } + } ) + .on( "mouseover", selector, datepicker_handleMouseover ); +} + +function datepicker_handleMouseover() { + if ( !$.datepicker._isDisabledDatepicker( datepicker_instActive.inline ? datepicker_instActive.dpDiv.parent()[ 0 ] : datepicker_instActive.input[ 0 ] ) ) { + $( this ).parents( ".ui-datepicker-calendar" ).find( "a" ).removeClass( "ui-state-hover" ); + $( this ).addClass( "ui-state-hover" ); + if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { + $( this ).addClass( "ui-datepicker-prev-hover" ); + } + if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { + $( this ).addClass( "ui-datepicker-next-hover" ); + } + } +} + +/* jQuery extend now ignores nulls! */ +function datepicker_extendRemove( target, props ) { + $.extend( target, props ); + for ( var name in props ) { + if ( props[ name ] == null ) { + target[ name ] = props[ name ]; + } + } + return target; +} + +/* Invoke the datepicker functionality. + @param options string - a command, optionally followed by additional parameters or + Object - settings for attaching new datepicker functionality + @return jQuery object */ +$.fn.datepicker = function( options ) { + + /* Verify an empty collection wasn't passed - Fixes #6976 */ + if ( !this.length ) { + return this; + } + + /* Initialise the date picker. */ + if ( !$.datepicker.initialized ) { + $( document ).on( "mousedown", $.datepicker._checkExternalClick ); + $.datepicker.initialized = true; + } + + /* Append datepicker main container to body if not exist. */ + if ( $( "#" + $.datepicker._mainDivId ).length === 0 ) { + $( "body" ).append( $.datepicker.dpDiv ); + } + + var otherArgs = Array.prototype.slice.call( arguments, 1 ); + if ( typeof options === "string" && ( options === "isDisabled" || options === "getDate" || options === "widget" ) ) { + return $.datepicker[ "_" + options + "Datepicker" ]. + apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); + } + if ( options === "option" && arguments.length === 2 && typeof arguments[ 1 ] === "string" ) { + return $.datepicker[ "_" + options + "Datepicker" ]. + apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); + } + return this.each( function() { + typeof options === "string" ? + $.datepicker[ "_" + options + "Datepicker" ]. + apply( $.datepicker, [ this ].concat( otherArgs ) ) : + $.datepicker._attachDatepicker( this, options ); + } ); +}; + +$.datepicker = new Datepicker(); // singleton instance +$.datepicker.initialized = false; +$.datepicker.uuid = new Date().getTime(); +$.datepicker.version = "1.12.1"; + +var widgetsDatepicker = $.datepicker; + + + + +})); \ No newline at end of file diff --git a/histview2/static/common/icons/CHM.ico b/histview2/static/common/icons/CHM.ico new file mode 100644 index 0000000..b3d850a Binary files /dev/null and b/histview2/static/common/icons/CHM.ico differ diff --git a/histview2/static/common/icons/COG.ico b/histview2/static/common/icons/COG.ico new file mode 100644 index 0000000..fcf0f71 Binary files /dev/null and b/histview2/static/common/icons/COG.ico differ diff --git a/histview2/static/common/icons/Config.ico b/histview2/static/common/icons/Config.ico new file mode 100644 index 0000000..f518142 Binary files /dev/null and b/histview2/static/common/icons/Config.ico differ diff --git a/histview2/static/common/icons/FPP.ico b/histview2/static/common/icons/FPP.ico new file mode 100644 index 0000000..261f6f9 Binary files /dev/null and b/histview2/static/common/icons/FPP.ico differ diff --git a/histview2/static/common/icons/Filter.ico b/histview2/static/common/icons/Filter.ico new file mode 100644 index 0000000..5bb9a8f Binary files /dev/null and b/histview2/static/common/icons/Filter.ico differ diff --git a/histview2/static/common/icons/Job.ico b/histview2/static/common/icons/Job.ico new file mode 100644 index 0000000..3203923 Binary files /dev/null and b/histview2/static/common/icons/Job.ico differ diff --git a/histview2/static/common/icons/MSP.ico b/histview2/static/common/icons/MSP.ico new file mode 100644 index 0000000..b807d63 Binary files /dev/null and b/histview2/static/common/icons/MSP.ico differ diff --git a/histview2/static/common/icons/Master.ico b/histview2/static/common/icons/Master.ico new file mode 100644 index 0000000..2ba4bd0 Binary files /dev/null and b/histview2/static/common/icons/Master.ico differ diff --git a/histview2/static/common/icons/PCA.ico b/histview2/static/common/icons/PCA.ico new file mode 100644 index 0000000..01a0ea5 Binary files /dev/null and b/histview2/static/common/icons/PCA.ico differ diff --git a/histview2/static/common/icons/PCP.ico b/histview2/static/common/icons/PCP.ico new file mode 100644 index 0000000..e23ac08 Binary files /dev/null and b/histview2/static/common/icons/PCP.ico differ diff --git a/histview2/static/common/icons/RLP.ico b/histview2/static/common/icons/RLP.ico new file mode 100644 index 0000000..e9693a1 Binary files /dev/null and b/histview2/static/common/icons/RLP.ico differ diff --git a/histview2/static/common/icons/ScP.ico b/histview2/static/common/icons/ScP.ico new file mode 100644 index 0000000..5a2cd5a Binary files /dev/null and b/histview2/static/common/icons/ScP.ico differ diff --git a/histview2/static/common/icons/SkD.ico b/histview2/static/common/icons/SkD.ico new file mode 100644 index 0000000..3890eec Binary files /dev/null and b/histview2/static/common/icons/SkD.ico differ diff --git a/histview2/static/common/icons/StP.ico b/histview2/static/common/icons/StP.ico new file mode 100644 index 0000000..f4277b4 Binary files /dev/null and b/histview2/static/common/icons/StP.ico differ diff --git a/histview2/static/common/icons/TSP.ico b/histview2/static/common/icons/TSP.ico new file mode 100644 index 0000000..26578b2 Binary files /dev/null and b/histview2/static/common/icons/TSP.ico differ diff --git a/histview2/static/common/icons/TView.ico b/histview2/static/common/icons/TView.ico new file mode 100644 index 0000000..1084a93 Binary files /dev/null and b/histview2/static/common/icons/TView.ico differ diff --git a/histview2/static/common/icons/favicon.ico b/histview2/static/common/icons/favicon.ico new file mode 100644 index 0000000..3114dca Binary files /dev/null and b/histview2/static/common/icons/favicon.ico differ diff --git a/histview2/static/common/images/logo.png b/histview2/static/common/images/logo.png new file mode 100644 index 0000000..71fb3fe Binary files /dev/null and b/histview2/static/common/images/logo.png differ diff --git a/histview2/static/common/js/Chart.bundle.min.js b/histview2/static/common/js/Chart.bundle.min.js new file mode 100644 index 0000000..7232597 --- /dev/null +++ b/histview2/static/common/js/Chart.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Chart.js v2.9.4 + * https://www.chartjs.org + * (c) 2020 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Chart=e()}(this,(function(){"use strict";"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function t(){throw new Error("Dynamic requires are not currently supported by rollup-plugin-commonjs")}function e(t,e){return t(e={exports:{}},e.exports),e.exports}var n={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},i=e((function(t){var e={};for(var i in n)n.hasOwnProperty(i)&&(e[n[i]]=i);var a=t.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var r in a)if(a.hasOwnProperty(r)){if(!("channels"in a[r]))throw new Error("missing channels property: "+r);if(!("labels"in a[r]))throw new Error("missing channel labels property: "+r);if(a[r].labels.length!==a[r].channels)throw new Error("channel and label counts mismatch: "+r);var o=a[r].channels,s=a[r].labels;delete a[r].channels,delete a[r].labels,Object.defineProperty(a[r],"channels",{value:o}),Object.defineProperty(a[r],"labels",{value:s})}a.rgb.hsl=function(t){var e,n,i=t[0]/255,a=t[1]/255,r=t[2]/255,o=Math.min(i,a,r),s=Math.max(i,a,r),l=s-o;return s===o?e=0:i===s?e=(a-r)/l:a===s?e=2+(r-i)/l:r===s&&(e=4+(i-a)/l),(e=Math.min(60*e,360))<0&&(e+=360),n=(o+s)/2,[e,100*(s===o?0:n<=.5?l/(s+o):l/(2-s-o)),100*n]},a.rgb.hsv=function(t){var e,n,i,a,r,o=t[0]/255,s=t[1]/255,l=t[2]/255,u=Math.max(o,s,l),d=u-Math.min(o,s,l),h=function(t){return(u-t)/6/d+.5};return 0===d?a=r=0:(r=d/u,e=h(o),n=h(s),i=h(l),o===u?a=i-n:s===u?a=1/3+e-i:l===u&&(a=2/3+n-e),a<0?a+=1:a>1&&(a-=1)),[360*a,100*r,100*u]},a.rgb.hwb=function(t){var e=t[0],n=t[1],i=t[2];return[a.rgb.hsl(t)[0],100*(1/255*Math.min(e,Math.min(n,i))),100*(i=1-1/255*Math.max(e,Math.max(n,i)))]},a.rgb.cmyk=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255;return[100*((1-n-(e=Math.min(1-n,1-i,1-a)))/(1-e)||0),100*((1-i-e)/(1-e)||0),100*((1-a-e)/(1-e)||0),100*e]},a.rgb.keyword=function(t){var i=e[t];if(i)return i;var a,r,o,s=1/0;for(var l in n)if(n.hasOwnProperty(l)){var u=n[l],d=(r=t,o=u,Math.pow(r[0]-o[0],2)+Math.pow(r[1]-o[1],2)+Math.pow(r[2]-o[2],2));d.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)+.1805*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)),100*(.2126*e+.7152*n+.0722*i),100*(.0193*e+.1192*n+.9505*i)]},a.rgb.lab=function(t){var e=a.rgb.xyz(t),n=e[0],i=e[1],r=e[2];return i/=100,r/=108.883,n=(n/=95.047)>.008856?Math.pow(n,1/3):7.787*n+16/116,[116*(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116)-16,500*(n-i),200*(i-(r=r>.008856?Math.pow(r,1/3):7.787*r+16/116))]},a.hsl.rgb=function(t){var e,n,i,a,r,o=t[0]/360,s=t[1]/100,l=t[2]/100;if(0===s)return[r=255*l,r,r];e=2*l-(n=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var u=0;u<3;u++)(i=o+1/3*-(u-1))<0&&i++,i>1&&i--,r=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*r;return a},a.hsl.hsv=function(t){var e=t[0],n=t[1]/100,i=t[2]/100,a=n,r=Math.max(i,.01);return n*=(i*=2)<=1?i:2-i,a*=r<=1?r:2-r,[e,100*(0===i?2*a/(r+a):2*n/(i+n)),100*((i+n)/2)]},a.hsv.rgb=function(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,r=e-Math.floor(e),o=255*i*(1-n),s=255*i*(1-n*r),l=255*i*(1-n*(1-r));switch(i*=255,a){case 0:return[i,l,o];case 1:return[s,i,o];case 2:return[o,i,l];case 3:return[o,s,i];case 4:return[l,o,i];case 5:return[i,o,s]}},a.hsv.hsl=function(t){var e,n,i,a=t[0],r=t[1]/100,o=t[2]/100,s=Math.max(o,.01);return i=(2-r)*o,n=r*s,[a,100*(n=(n/=(e=(2-r)*s)<=1?e:2-e)||0),100*(i/=2)]},a.hwb.rgb=function(t){var e,n,i,a,r,o,s,l=t[0]/360,u=t[1]/100,d=t[2]/100,h=u+d;switch(h>1&&(u/=h,d/=h),i=6*l-(e=Math.floor(6*l)),0!=(1&e)&&(i=1-i),a=u+i*((n=1-d)-u),e){default:case 6:case 0:r=n,o=a,s=u;break;case 1:r=a,o=n,s=u;break;case 2:r=u,o=n,s=a;break;case 3:r=u,o=a,s=n;break;case 4:r=a,o=u,s=n;break;case 5:r=n,o=u,s=a}return[255*r,255*o,255*s]},a.cmyk.rgb=function(t){var e=t[0]/100,n=t[1]/100,i=t[2]/100,a=t[3]/100;return[255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a)),255*(1-Math.min(1,i*(1-a)+a))]},a.xyz.rgb=function(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100;return n=-.9689*a+1.8758*r+.0415*o,i=.0557*a+-.204*r+1.057*o,e=(e=3.2406*a+-1.5372*r+-.4986*o)>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:12.92*i,[255*(e=Math.min(Math.max(0,e),1)),255*(n=Math.min(Math.max(0,n),1)),255*(i=Math.min(Math.max(0,i),1))]},a.xyz.lab=function(t){var e=t[0],n=t[1],i=t[2];return n/=100,i/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(e-n),200*(n-(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116))]},a.lab.xyz=function(t){var e,n,i,a=t[0];e=t[1]/500+(n=(a+16)/116),i=n-t[2]/200;var r=Math.pow(n,3),o=Math.pow(e,3),s=Math.pow(i,3);return n=r>.008856?r:(n-16/116)/7.787,e=o>.008856?o:(e-16/116)/7.787,i=s>.008856?s:(i-16/116)/7.787,[e*=95.047,n*=100,i*=108.883]},a.lab.lch=function(t){var e,n=t[0],i=t[1],a=t[2];return(e=360*Math.atan2(a,i)/2/Math.PI)<0&&(e+=360),[n,Math.sqrt(i*i+a*a),e]},a.lch.lab=function(t){var e,n=t[0],i=t[1];return e=t[2]/360*2*Math.PI,[n,i*Math.cos(e),i*Math.sin(e)]},a.rgb.ansi16=function(t){var e=t[0],n=t[1],i=t[2],r=1 in arguments?arguments[1]:a.rgb.hsv(t)[2];if(0===(r=Math.round(r/50)))return 30;var o=30+(Math.round(i/255)<<2|Math.round(n/255)<<1|Math.round(e/255));return 2===r&&(o+=60),o},a.hsv.ansi16=function(t){return a.rgb.ansi16(a.hsv.rgb(t),t[2])},a.rgb.ansi256=function(t){var e=t[0],n=t[1],i=t[2];return e===n&&n===i?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(n/255*5)+Math.round(i/255*5)},a.ansi16.rgb=function(t){var e=t%10;if(0===e||7===e)return t>50&&(e+=3.5),[e=e/10.5*255,e,e];var n=.5*(1+~~(t>50));return[(1&e)*n*255,(e>>1&1)*n*255,(e>>2&1)*n*255]},a.ansi256.rgb=function(t){if(t>=232){var e=10*(t-232)+8;return[e,e,e]}var n;return t-=16,[Math.floor(t/36)/5*255,Math.floor((n=t%36)/6)/5*255,n%6/5*255]},a.rgb.hex=function(t){var e=(((255&Math.round(t[0]))<<16)+((255&Math.round(t[1]))<<8)+(255&Math.round(t[2]))).toString(16).toUpperCase();return"000000".substring(e.length)+e},a.hex.rgb=function(t){var e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];var n=e[0];3===e[0].length&&(n=n.split("").map((function(t){return t+t})).join(""));var i=parseInt(n,16);return[i>>16&255,i>>8&255,255&i]},a.rgb.hcg=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255,r=Math.max(Math.max(n,i),a),o=Math.min(Math.min(n,i),a),s=r-o;return e=s<=0?0:r===n?(i-a)/s%6:r===i?2+(a-n)/s:4+(n-i)/s+4,e/=6,[360*(e%=1),100*s,100*(s<1?o/(1-s):0)]},a.hsl.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=1,a=0;return(i=n<.5?2*e*n:2*e*(1-n))<1&&(a=(n-.5*i)/(1-i)),[t[0],100*i,100*a]},a.hsv.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=e*n,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.hcg.rgb=function(t){var e=t[0]/360,n=t[1]/100,i=t[2]/100;if(0===n)return[255*i,255*i,255*i];var a,r=[0,0,0],o=e%1*6,s=o%1,l=1-s;switch(Math.floor(o)){case 0:r[0]=1,r[1]=s,r[2]=0;break;case 1:r[0]=l,r[1]=1,r[2]=0;break;case 2:r[0]=0,r[1]=1,r[2]=s;break;case 3:r[0]=0,r[1]=l,r[2]=1;break;case 4:r[0]=s,r[1]=0,r[2]=1;break;default:r[0]=1,r[1]=0,r[2]=l}return a=(1-n)*i,[255*(n*r[0]+a),255*(n*r[1]+a),255*(n*r[2]+a)]},a.hcg.hsv=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e),i=0;return n>0&&(i=e/n),[t[0],100*i,100*n]},a.hcg.hsl=function(t){var e=t[1]/100,n=t[2]/100*(1-e)+.5*e,i=0;return n>0&&n<.5?i=e/(2*n):n>=.5&&n<1&&(i=e/(2*(1-n))),[t[0],100*i,100*n]},a.hcg.hwb=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e);return[t[0],100*(n-e),100*(1-n)]},a.hwb.hcg=function(t){var e=t[1]/100,n=1-t[2]/100,i=n-e,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]},a.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]},a.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]},a.gray.hsl=a.gray.hsv=function(t){return[0,0,t[0]]},a.gray.hwb=function(t){return[0,100,t[0]]},a.gray.cmyk=function(t){return[0,0,0,t[0]]},a.gray.lab=function(t){return[t[0],0,0]},a.gray.hex=function(t){var e=255&Math.round(t[0]/100*255),n=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(n.length)+n},a.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}}));i.rgb,i.hsl,i.hsv,i.hwb,i.cmyk,i.xyz,i.lab,i.lch,i.hex,i.keyword,i.ansi16,i.ansi256,i.hcg,i.apple,i.gray;function a(t){var e=function(){for(var t={},e=Object.keys(i),n=e.length,a=0;a1&&(e=Array.prototype.slice.call(arguments));var n=t(e);if("object"==typeof n)for(var i=n.length,a=0;a1&&(e=Array.prototype.slice.call(arguments)),t(e))};return"conversion"in t&&(e.conversion=t.conversion),e}(i)}))}));var l=s,u={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},d={getRgba:h,getHsla:c,getRgb:function(t){var e=h(t);return e&&e.slice(0,3)},getHsl:function(t){var e=c(t);return e&&e.slice(0,3)},getHwb:f,getAlpha:function(t){var e=h(t);if(e)return e[3];if(e=c(t))return e[3];if(e=f(t))return e[3]},hexString:function(t,e){e=void 0!==e&&3===t.length?e:t[3];return"#"+b(t[0])+b(t[1])+b(t[2])+(e>=0&&e<1?b(Math.round(255*e)):"")},rgbString:function(t,e){if(e<1||t[3]&&t[3]<1)return g(t,e);return"rgb("+t[0]+", "+t[1]+", "+t[2]+")"},rgbaString:g,percentString:function(t,e){if(e<1||t[3]&&t[3]<1)return m(t,e);var n=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+n+"%, "+i+"%, "+a+"%)"},percentaString:m,hslString:function(t,e){if(e<1||t[3]&&t[3]<1)return p(t,e);return"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"},hslaString:p,hwbString:function(t,e){void 0===e&&(e=void 0!==t[3]?t[3]:1);return"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"},keyword:function(t){return y[t.slice(0,3)]}};function h(t){if(t){var e=[0,0,0],n=1,i=t.match(/^#([a-fA-F0-9]{3,4})$/i),a="";if(i){a=(i=i[1])[3];for(var r=0;rn?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=t,i=void 0===e?.5:e,a=2*i-1,r=this.alpha()-n.alpha(),o=((a*r==-1?a:(a+r)/(1+a*r))+1)/2,s=1-o;return this.rgb(o*this.red()+s*n.red(),o*this.green()+s*n.green(),o*this.blue()+s*n.blue()).alpha(this.alpha()*i+n.alpha()*(1-i))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new _,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},_.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},_.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},_.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i=0;a--)e.call(n,t[a],a);else for(a=0;a=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n))},easeOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/n)+1)},easeInOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:2==(t/=.5)?1:(n||(n=.45),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-C.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*C.easeInBounce(2*t):.5*C.easeOutBounce(2*t-1)+.5}},P={effects:C};D.easingEffects=C;var T=Math.PI,O=T/180,A=2*T,F=T/2,I=T/4,L=2*T/3,R={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,r){if(r){var o=Math.min(r,a/2,i/2),s=e+o,l=n+o,u=e+i-o,d=n+a-o;t.moveTo(e,l),se.left-1e-6&&t.xe.top-1e-6&&t.y0&&this.requestAnimationFrame()},advance:function(){for(var t,e,n,i,a=this.animations,r=0;r=n?(B.callback(t.onAnimationComplete,[t],e),e.animating=!1,a.splice(r,1)):++r}},tt=B.options.resolve,et=["push","pop","shift","splice","unshift"];function nt(t,e){var n=t._chartjs;if(n){var i=n.listeners,a=i.indexOf(e);-1!==a&&i.splice(a,1),i.length>0||(et.forEach((function(e){delete t[e]})),delete t._chartjs)}}var it=function(t,e){this.initialize(t,e)};B.extend(it.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements(),n._type=n.getMeta().type},updateIndex:function(t){this.index=t},linkScales:function(){var t=this.getMeta(),e=this.chart,n=e.scales,i=this.getDataset(),a=e.options.scales;null!==t.xAxisID&&t.xAxisID in n&&!i.xAxisID||(t.xAxisID=i.xAxisID||a.xAxes[0].id),null!==t.yAxisID&&t.yAxisID in n&&!i.yAxisID||(t.yAxisID=i.yAxisID||a.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&nt(this._data,this)},createMetaDataset:function(){var t=this.datasetElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(t){var e=this.dataElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index,_index:t})},addElements:function(){var t,e,n=this.getMeta(),i=this.getDataset().data||[],a=n.data;for(t=0,e=i.length;tn&&this.insertElements(n,i-n)},insertElements:function(t,e){for(var n=0;na?(r=a/e.innerRadius,t.arc(o,s,e.innerRadius-a,i+r,n-r,!0)):t.arc(o,s,a,i+Math.PI/2,n-Math.PI/2),t.closePath(),t.clip()}function st(t,e,n){var i="inner"===e.borderAlign;i?(t.lineWidth=2*e.borderWidth,t.lineJoin="round"):(t.lineWidth=e.borderWidth,t.lineJoin="bevel"),n.fullCircles&&function(t,e,n,i){var a,r=n.endAngle;for(i&&(n.endAngle=n.startAngle+rt,ot(t,n),n.endAngle=r,n.endAngle===n.startAngle&&n.fullCircles&&(n.endAngle+=rt,n.fullCircles--)),t.beginPath(),t.arc(n.x,n.y,n.innerRadius,n.startAngle+rt,n.startAngle,!0),a=0;as;)a-=rt;for(;a=o&&a<=s,u=r>=n.innerRadius&&r<=n.outerRadius;return l&&u}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t,e=this._chart.ctx,n=this._view,i="inner"===n.borderAlign?.33:0,a={x:n.x,y:n.y,innerRadius:n.innerRadius,outerRadius:Math.max(n.outerRadius-i,0),pixelMargin:i,startAngle:n.startAngle,endAngle:n.endAngle,fullCircles:Math.floor(n.circumference/rt)};if(e.save(),e.fillStyle=n.backgroundColor,e.strokeStyle=n.borderColor,a.fullCircles){for(a.endAngle=a.startAngle+rt,e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),t=0;tt.x&&(e=yt(e,"left","right")):t.basen?n:i,r:l.right||a<0?0:a>e?e:a,b:l.bottom||r<0?0:r>n?n:r,l:l.left||o<0?0:o>e?e:o}}function _t(t,e,n){var i=null===e,a=null===n,r=!(!t||i&&a)&&bt(t);return r&&(i||e>=r.left&&e<=r.right)&&(a||n>=r.top&&n<=r.bottom)}Y._set("global",{elements:{rectangle:{backgroundColor:pt,borderColor:pt,borderSkipped:"bottom",borderWidth:0}}});var wt=X.extend({_type:"rectangle",draw:function(){var t=this._chart.ctx,e=this._view,n=function(t){var e=bt(t),n=e.right-e.left,i=e.bottom-e.top,a=xt(t,n/2,i/2);return{outer:{x:e.left,y:e.top,w:n,h:i},inner:{x:e.left+a.l,y:e.top+a.t,w:n-a.l-a.r,h:i-a.t-a.b}}}(e),i=n.outer,a=n.inner;t.fillStyle=e.backgroundColor,t.fillRect(i.x,i.y,i.w,i.h),i.w===a.w&&i.h===a.h||(t.save(),t.beginPath(),t.rect(i.x,i.y,i.w,i.h),t.clip(),t.fillStyle=e.borderColor,t.rect(a.x,a.y,a.w,a.h),t.fill("evenodd"),t.restore())},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){return _t(this._view,t,e)},inLabelRange:function(t,e){var n=this._view;return vt(n)?_t(n,t,null):_t(n,null,e)},inXRange:function(t){return _t(this._view,t,null)},inYRange:function(t){return _t(this._view,null,t)},getCenterPoint:function(){var t,e,n=this._view;return vt(n)?(t=n.x,e=(n.y+n.base)/2):(t=(n.x+n.base)/2,e=n.y),{x:t,y:e}},getArea:function(){var t=this._view;return vt(t)?t.width*Math.abs(t.y-t.base):t.height*Math.abs(t.x-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}}),kt={},Mt=lt,St=ht,Dt=mt,Ct=wt;kt.Arc=Mt,kt.Line=St,kt.Point=Dt,kt.Rectangle=Ct;var Pt=B._deprecated,Tt=B.valueOrDefault;function Ot(t,e,n){var i,a,r=n.barThickness,o=e.stackCount,s=e.pixels[t],l=B.isNullOrUndef(r)?function(t,e){var n,i,a,r,o=t._length;for(a=1,r=e.length;a0?Math.min(o,Math.abs(i-n)):o,n=i;return o}(e.scale,e.pixels):-1;return B.isNullOrUndef(r)?(i=l*n.categoryPercentage,a=n.barPercentage):(i=r*o,a=1),{chunk:i/o,ratio:a,start:s-i/2}}Y._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}}),Y._set("global",{datasets:{bar:{categoryPercentage:.8,barPercentage:.9}}});var At=at.extend({dataElementType:kt.Rectangle,_dataElementOptions:["backgroundColor","borderColor","borderSkipped","borderWidth","barPercentage","barThickness","categoryPercentage","maxBarThickness","minBarLength"],initialize:function(){var t,e,n=this;at.prototype.initialize.apply(n,arguments),(t=n.getMeta()).stack=n.getDataset().stack,t.bar=!0,e=n._getIndexScale().options,Pt("bar chart",e.barPercentage,"scales.[x/y]Axes.barPercentage","dataset.barPercentage"),Pt("bar chart",e.barThickness,"scales.[x/y]Axes.barThickness","dataset.barThickness"),Pt("bar chart",e.categoryPercentage,"scales.[x/y]Axes.categoryPercentage","dataset.categoryPercentage"),Pt("bar chart",n._getValueScale().options.minBarLength,"scales.[x/y]Axes.minBarLength","dataset.minBarLength"),Pt("bar chart",e.maxBarThickness,"scales.[x/y]Axes.maxBarThickness","dataset.maxBarThickness")},update:function(t){var e,n,i=this.getMeta().data;for(this._ruler=this.getRuler(),e=0,n=i.length;e=0&&m.min>=0?m.min:m.max,x=void 0===m.start?m.end:m.max>=0&&m.min>=0?m.max-m.min:m.min-m.max,_=g.length;if(v||void 0===v&&void 0!==b)for(i=0;i<_&&(a=g[i]).index!==t;++i)a.stack===b&&(r=void 0===(u=h._parseValue(f[a.index].data[e])).start?u.end:u.min>=0&&u.max>=0?u.max:u.min,(m.min<0&&r<0||m.max>=0&&r>0)&&(y+=r));return o=h.getPixelForValue(y),l=(s=h.getPixelForValue(y+x))-o,void 0!==p&&Math.abs(l)=0&&!c||x<0&&c?o-p:o+p),{size:l,base:o,head:s,center:s+l/2}},calculateBarIndexPixels:function(t,e,n,i){var a="flex"===i.barThickness?function(t,e,n){var i,a=e.pixels,r=a[t],o=t>0?a[t-1]:null,s=t=Nt?-Wt:b<-Nt?Wt:0)+p,x=Math.cos(b),_=Math.sin(b),w=Math.cos(y),k=Math.sin(y),M=b<=0&&y>=0||y>=Wt,S=b<=Yt&&y>=Yt||y>=Wt+Yt,D=b<=-Yt&&y>=-Yt||y>=Nt+Yt,C=b===-Nt||y>=Nt?-1:Math.min(x,x*m,w,w*m),P=D?-1:Math.min(_,_*m,k,k*m),T=M?1:Math.max(x,x*m,w,w*m),O=S?1:Math.max(_,_*m,k,k*m);u=(T-C)/2,d=(O-P)/2,h=-(T+C)/2,c=-(O+P)/2}for(i=0,a=g.length;i0&&!isNaN(t)?Wt*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){var e,n,i,a,r,o,s,l,u=0,d=this.chart;if(!t)for(e=0,n=d.data.datasets.length;e(u=s>u?s:u)?l:u);return u},setHoverStyle:function(t){var e=t._model,n=t._options,i=B.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=Rt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Rt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Rt(n.hoverBorderWidth,n.borderWidth)},_getRingWeightOffset:function(t){for(var e=0,n=0;n0&&Bt(l[t-1]._model,s)&&(n.controlPointPreviousX=u(n.controlPointPreviousX,s.left,s.right),n.controlPointPreviousY=u(n.controlPointPreviousY,s.top,s.bottom)),t0&&(r=t.getDatasetMeta(r[0]._datasetIndex).data),r},"x-axis":function(t,e){return re(t,e,{intersect:!1})},point:function(t,e){return ne(t,te(e,t))},nearest:function(t,e,n){var i=te(e,t);n.axis=n.axis||"xy";var a=ae(n.axis);return ie(t,i,n.intersect,a)},x:function(t,e,n){var i=te(e,t),a=[],r=!1;return ee(t,(function(t){t.inXRange(i.x)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a},y:function(t,e,n){var i=te(e,t),a=[],r=!1;return ee(t,(function(t){t.inYRange(i.y)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a}}},se=B.extend;function le(t,e){return B.where(t,(function(t){return t.pos===e}))}function ue(t,e){return t.sort((function(t,n){var i=e?n:t,a=e?t:n;return i.weight===a.weight?i.index-a.index:i.weight-a.weight}))}function de(t,e,n,i){return Math.max(t[n],e[n])+Math.max(t[i],e[i])}function he(t,e,n){var i,a,r=n.box,o=t.maxPadding;if(n.size&&(t[n.pos]-=n.size),n.size=n.horizontal?r.height:r.width,t[n.pos]+=n.size,r.getPadding){var s=r.getPadding();o.top=Math.max(o.top,s.top),o.left=Math.max(o.left,s.left),o.bottom=Math.max(o.bottom,s.bottom),o.right=Math.max(o.right,s.right)}if(i=e.outerWidth-de(o,t,"left","right"),a=e.outerHeight-de(o,t,"top","bottom"),i!==t.w||a!==t.h){t.w=i,t.h=a;var l=n.horizontal?[i,t.w]:[a,t.h];return!(l[0]===l[1]||isNaN(l[0])&&isNaN(l[1]))}}function ce(t,e){var n=e.maxPadding;function i(t){var i={left:0,top:0,right:0,bottom:0};return t.forEach((function(t){i[t]=Math.max(e[t],n[t])})),i}return i(t?["left","right"]:["top","bottom"])}function fe(t,e,n){var i,a,r,o,s,l,u=[];for(i=0,a=t.length;idiv{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}"}))&&me.default||me,be="$chartjs",ye="chartjs-size-monitor",xe="chartjs-render-monitor",_e="chartjs-render-animation",we=["animationstart","webkitAnimationStart"],ke={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function Me(t,e){var n=B.getStyle(t,e),i=n&&n.match(/^(\d+)(\.\d+)?px$/);return i?Number(i[1]):void 0}var Se=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};function De(t,e,n){t.addEventListener(e,n,Se)}function Ce(t,e,n){t.removeEventListener(e,n,Se)}function Pe(t,e,n,i,a){return{type:t,chart:e,native:a||null,x:void 0!==n?n:null,y:void 0!==i?i:null}}function Te(t){var e=document.createElement("div");return e.className=t||"",e}function Oe(t,e,n){var i,a,r,o,s=t[be]||(t[be]={}),l=s.resizer=function(t){var e=Te(ye),n=Te(ye+"-expand"),i=Te(ye+"-shrink");n.appendChild(Te()),i.appendChild(Te()),e.appendChild(n),e.appendChild(i),e._reset=function(){n.scrollLeft=1e6,n.scrollTop=1e6,i.scrollLeft=1e6,i.scrollTop=1e6};var a=function(){e._reset(),t()};return De(n,"scroll",a.bind(n,"expand")),De(i,"scroll",a.bind(i,"shrink")),e}((i=function(){if(s.resizer){var i=n.options.maintainAspectRatio&&t.parentNode,a=i?i.clientWidth:0;e(Pe("resize",n)),i&&i.clientWidth0){var r=t[0];r.label?n=r.label:r.xLabel?n=r.xLabel:a>0&&r.index-1?t.split("\n"):t}function He(t){var e=Y.global;return{xPadding:t.xPadding,yPadding:t.yPadding,xAlign:t.xAlign,yAlign:t.yAlign,rtl:t.rtl,textDirection:t.textDirection,bodyFontColor:t.bodyFontColor,_bodyFontFamily:We(t.bodyFontFamily,e.defaultFontFamily),_bodyFontStyle:We(t.bodyFontStyle,e.defaultFontStyle),_bodyAlign:t.bodyAlign,bodyFontSize:We(t.bodyFontSize,e.defaultFontSize),bodySpacing:t.bodySpacing,titleFontColor:t.titleFontColor,_titleFontFamily:We(t.titleFontFamily,e.defaultFontFamily),_titleFontStyle:We(t.titleFontStyle,e.defaultFontStyle),titleFontSize:We(t.titleFontSize,e.defaultFontSize),_titleAlign:t.titleAlign,titleSpacing:t.titleSpacing,titleMarginBottom:t.titleMarginBottom,footerFontColor:t.footerFontColor,_footerFontFamily:We(t.footerFontFamily,e.defaultFontFamily),_footerFontStyle:We(t.footerFontStyle,e.defaultFontStyle),footerFontSize:We(t.footerFontSize,e.defaultFontSize),_footerAlign:t.footerAlign,footerSpacing:t.footerSpacing,footerMarginTop:t.footerMarginTop,caretSize:t.caretSize,cornerRadius:t.cornerRadius,backgroundColor:t.backgroundColor,opacity:0,legendColorBackground:t.multiKeyBackground,displayColors:t.displayColors,borderColor:t.borderColor,borderWidth:t.borderWidth}}function Be(t,e){return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-t.xPadding:t.x+t.xPadding}function je(t){return Ee([],Ve(t))}var Ue=X.extend({initialize:function(){this._model=He(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options,n=e.callbacks,i=n.beforeTitle.apply(t,arguments),a=n.title.apply(t,arguments),r=n.afterTitle.apply(t,arguments),o=[];return o=Ee(o,Ve(i)),o=Ee(o,Ve(a)),o=Ee(o,Ve(r))},getBeforeBody:function(){return je(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(t,e){var n=this,i=n._options.callbacks,a=[];return B.each(t,(function(t){var r={before:[],lines:[],after:[]};Ee(r.before,Ve(i.beforeLabel.call(n,t,e))),Ee(r.lines,i.label.call(n,t,e)),Ee(r.after,Ve(i.afterLabel.call(n,t,e))),a.push(r)})),a},getAfterBody:function(){return je(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var t=this,e=t._options.callbacks,n=e.beforeFooter.apply(t,arguments),i=e.footer.apply(t,arguments),a=e.afterFooter.apply(t,arguments),r=[];return r=Ee(r,Ve(n)),r=Ee(r,Ve(i)),r=Ee(r,Ve(a))},update:function(t){var e,n,i,a,r,o,s,l,u,d,h=this,c=h._options,f=h._model,g=h._model=He(c),m=h._active,p=h._data,v={xAlign:f.xAlign,yAlign:f.yAlign},b={x:f.x,y:f.y},y={width:f.width,height:f.height},x={x:f.caretX,y:f.caretY};if(m.length){g.opacity=1;var _=[],w=[];x=ze[c.position].call(h,m,h._eventPosition);var k=[];for(e=0,n=m.length;ei.width&&(a=i.width-e.width),a<0&&(a=0)),"top"===d?r+=h:r-="bottom"===d?e.height+h:e.height/2,"center"===d?"left"===u?a+=h:"right"===u&&(a-=h):"left"===u?a-=c:"right"===u&&(a+=c),{x:a,y:r}}(g,y,v=function(t,e){var n,i,a,r,o,s=t._model,l=t._chart,u=t._chart.chartArea,d="center",h="center";s.yl.height-e.height&&(h="bottom");var c=(u.left+u.right)/2,f=(u.top+u.bottom)/2;"center"===h?(n=function(t){return t<=c},i=function(t){return t>c}):(n=function(t){return t<=e.width/2},i=function(t){return t>=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},r=function(t){return t-e.width-s.caretSize-s.caretPadding<0},o=function(t){return t<=f?"top":"bottom"},n(s.x)?(d="left",a(s.x)&&(d="center",h=o(s.y))):i(s.x)&&(d="right",r(s.x)&&(d="center",h=o(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:d,yAlign:g.yAlign?g.yAlign:h}}(this,y),h._chart)}else g.opacity=0;return g.xAlign=v.xAlign,g.yAlign=v.yAlign,g.x=b.x,g.y=b.y,g.width=y.width,g.height=y.height,g.caretX=x.x,g.caretY=x.y,h._model=g,t&&c.custom&&c.custom.call(h,g),h},drawCaret:function(t,e){var n=this._chart.ctx,i=this._view,a=this.getCaretPosition(t,e,i);n.lineTo(a.x1,a.y1),n.lineTo(a.x2,a.y2),n.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,n){var i,a,r,o,s,l,u=n.caretSize,d=n.cornerRadius,h=n.xAlign,c=n.yAlign,f=t.x,g=t.y,m=e.width,p=e.height;if("center"===c)s=g+p/2,"left"===h?(a=(i=f)-u,r=i,o=s+u,l=s-u):(a=(i=f+m)+u,r=i,o=s-u,l=s+u);else if("left"===h?(i=(a=f+d+u)-u,r=a+u):"right"===h?(i=(a=f+m-d-u)-u,r=a+u):(i=(a=n.caretX)-u,r=a+u),"top"===c)s=(o=g)-u,l=o;else{s=(o=g+p)+u,l=o;var v=r;r=i,i=v}return{x1:i,x2:a,x3:r,y1:o,y2:s,y3:l}},drawTitle:function(t,e,n){var i,a,r,o=e.title,s=o.length;if(s){var l=Ye(e.rtl,e.x,e.width);for(t.x=Be(e,e._titleAlign),n.textAlign=l.textAlign(e._titleAlign),n.textBaseline="middle",i=e.titleFontSize,a=e.titleSpacing,n.fillStyle=e.titleFontColor,n.font=B.fontString(i,e._titleFontStyle,e._titleFontFamily),r=0;r0&&n.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n={width:e.width,height:e.height},i={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&r&&(t.save(),t.globalAlpha=a,this.drawBackground(i,e,t,n),i.y+=e.yPadding,B.rtl.overrideTextDirection(t,e.textDirection),this.drawTitle(i,e,t),this.drawBody(i,e,t),this.drawFooter(i,e,t),B.rtl.restoreTextDirection(t,e.textDirection),t.restore())}},handleEvent:function(t){var e,n=this,i=n._options;return n._lastActive=n._lastActive||[],"mouseout"===t.type?n._active=[]:(n._active=n._chart.getElementsAtEventForMode(t,i.mode,i),i.reverse&&n._active.reverse()),(e=!B.arrayEquals(n._active,n._lastActive))&&(n._lastActive=n._active,(i.enabled||i.custom)&&(n._eventPosition={x:t.x,y:t.y},n.update(!0),n.pivot())),e}}),Ge=ze,qe=Ue;qe.positioners=Ge;var Ze=B.valueOrDefault;function $e(){return B.merge(Object.create(null),[].slice.call(arguments),{merger:function(t,e,n,i){if("xAxes"===t||"yAxes"===t){var a,r,o,s=n[t].length;for(e[t]||(e[t]=[]),a=0;a=e[t].length&&e[t].push({}),!e[t][a].type||o.type&&o.type!==e[t][a].type?B.merge(e[t][a],[Ne.getScaleDefaults(r),o]):B.merge(e[t][a],o)}else B._merger(t,e,n,i)}})}function Xe(){return B.merge(Object.create(null),[].slice.call(arguments),{merger:function(t,e,n,i){var a=e[t]||Object.create(null),r=n[t];"scales"===t?e[t]=$e(a,r):"scale"===t?e[t]=B.merge(a,[Ne.getScaleDefaults(r.type),r]):B._merger(t,e,n,i)}})}function Ke(t){var e=t.options;B.each(t.scales,(function(e){pe.removeBox(t,e)})),e=Xe(Y.global,Y[t.config.type],e),t.options=t.config.options=e,t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.tooltip._options=e.tooltips,t.tooltip.initialize()}function Je(t,e,n){var i,a=function(t){return t.id===i};do{i=e+n++}while(B.findIndex(t,a)>=0);return i}function Qe(t){return"top"===t||"bottom"===t}function tn(t,e){return function(n,i){return n[t]===i[t]?n[e]-i[e]:n[t]-i[t]}}Y._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var en=function(t,e){return this.construct(t,e),this};B.extend(en.prototype,{construct:function(t,e){var n=this;e=function(t){var e=(t=t||Object.create(null)).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=Xe(Y.global,Y[t.type],t.options||{}),t}(e);var i=Le.acquireContext(t,e),a=i&&i.canvas,r=a&&a.height,o=a&&a.width;n.id=B.uid(),n.ctx=i,n.canvas=a,n.config=e,n.width=o,n.height=r,n.aspectRatio=r?o/r:null,n.options=e.options,n._bufferedRender=!1,n._layers=[],n.chart=n,n.controller=n,en.instances[n.id]=n,Object.defineProperty(n,"data",{get:function(){return n.config.data},set:function(t){n.config.data=t}}),i&&a?(n.initialize(),n.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return Re.notify(t,"beforeInit"),B.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.initToolTip(),Re.notify(t,"afterInit"),t},clear:function(){return B.canvas.clear(this),this},stop:function(){return Q.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,a=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(B.getMaximumWidth(i))),o=Math.max(0,Math.floor(a?r/a:B.getMaximumHeight(i)));if((e.width!==r||e.height!==o)&&(i.width=e.width=r,i.height=e.height=o,i.style.width=r+"px",i.style.height=o+"px",B.retinaScale(e,n.devicePixelRatio),!t)){var s={width:r,height:o};Re.notify(e,"resize",[s]),n.onResize&&n.onResize(e,s),e.stop(),e.update({duration:n.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;B.each(e.xAxes,(function(t,n){t.id||(t.id=Je(e.xAxes,"x-axis-",n))})),B.each(e.yAxes,(function(t,n){t.id||(t.id=Je(e.yAxes,"y-axis-",n))})),n&&(n.id=n.id||"scale")},buildOrUpdateScales:function(){var t=this,e=t.options,n=t.scales||{},i=[],a=Object.keys(n).reduce((function(t,e){return t[e]=!1,t}),{});e.scales&&(i=i.concat((e.scales.xAxes||[]).map((function(t){return{options:t,dtype:"category",dposition:"bottom"}})),(e.scales.yAxes||[]).map((function(t){return{options:t,dtype:"linear",dposition:"left"}})))),e.scale&&i.push({options:e.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),B.each(i,(function(e){var i=e.options,r=i.id,o=Ze(i.type,e.dtype);Qe(i.position)!==Qe(e.dposition)&&(i.position=e.dposition),a[r]=!0;var s=null;if(r in n&&n[r].type===o)(s=n[r]).options=i,s.ctx=t.ctx,s.chart=t;else{var l=Ne.getScaleConstructor(o);if(!l)return;s=new l({id:r,type:o,options:i,ctx:t.ctx,chart:t}),n[s.id]=s}s.mergeTicksOptions(),e.isDefault&&(t.scale=s)})),B.each(a,(function(t,e){t||delete n[e]})),t.scales=n,Ne.addScalesToLayout(this)},buildOrUpdateControllers:function(){var t,e,n=this,i=[],a=n.data.datasets;for(t=0,e=a.length;t=0;--n)this.drawDataset(e[n],t);Re.notify(this,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var n={meta:t,index:t.index,easingValue:e};!1!==Re.notify(this,"beforeDatasetDraw",[n])&&(t.controller.draw(e),Re.notify(this,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this.tooltip,n={tooltip:e,easingValue:t};!1!==Re.notify(this,"beforeTooltipDraw",[n])&&(e.draw(),Re.notify(this,"afterTooltipDraw",[n]))},getElementAtEvent:function(t){return oe.modes.single(this,t)},getElementsAtEvent:function(t){return oe.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return oe.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,n){var i=oe.modes[e];return"function"==typeof i?i(this,t,n):[]},getDatasetAtEvent:function(t){return oe.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var n=e._meta[this.id];return n||(n=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e.order||0,index:t}),n},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;e3?n[2]-n[1]:n[1]-n[0];Math.abs(i)>1&&t!==Math.floor(t)&&(i=t-Math.floor(t));var a=B.log10(Math.abs(i)),r="";if(0!==t)if(Math.max(Math.abs(n[0]),Math.abs(n[n.length-1]))<1e-4){var o=B.log10(Math.abs(t)),s=Math.floor(o)-Math.floor(a);s=Math.max(Math.min(s,20),0),r=t.toExponential(s)}else{var l=-1*Math.floor(a);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r},logarithmic:function(t,e,n){var i=t/Math.pow(10,Math.floor(B.log10(t)));return 0===t?"0":1===i||2===i||5===i||0===e||e===n.length-1?t.toExponential():""}}},ln=B.isArray,un=B.isNullOrUndef,dn=B.valueOrDefault,hn=B.valueAtIndexOrDefault;function cn(t,e,n){var i,a=t.getTicks().length,r=Math.min(e,a-1),o=t.getPixelForTick(r),s=t._startPixel,l=t._endPixel;if(!(n&&(i=1===a?Math.max(o-s,l-o):0===e?(t.getPixelForTick(1)-o)/2:(o-t.getPixelForTick(r-1))/2,(o+=rl+1e-6)))return o}function fn(t,e,n,i){var a,r,o,s,l,u,d,h,c,f,g,m,p,v=n.length,b=[],y=[],x=[],_=0,w=0;for(a=0;ae){for(n=0;n=c||d<=1||!s.isHorizontal()?s.labelRotation=h:(e=(t=s._getLabelSizes()).widest.width,n=t.highest.height-t.highest.offset,i=Math.min(s.maxWidth,s.chart.width-e),e+6>(a=l.offset?s.maxWidth/d:i/(d-1))&&(a=i/(d-(l.offset?.5:1)),r=s.maxHeight-gn(l.gridLines)-u.padding-mn(l.scaleLabel),o=Math.sqrt(e*e+n*n),f=B.toDegrees(Math.min(Math.asin(Math.min((t.highest.height+6)/a,1)),Math.asin(Math.min(r/o,1))-Math.asin(n/o))),f=Math.max(h,Math.min(c,f))),s.labelRotation=f)},afterCalculateTickRotation:function(){B.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){B.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},n=t.chart,i=t.options,a=i.ticks,r=i.scaleLabel,o=i.gridLines,s=t._isVisible(),l="bottom"===i.position,u=t.isHorizontal();if(u?e.width=t.maxWidth:s&&(e.width=gn(o)+mn(r)),u?s&&(e.height=gn(o)+mn(r)):e.height=t.maxHeight,a.display&&s){var d=vn(a),h=t._getLabelSizes(),c=h.first,f=h.last,g=h.widest,m=h.highest,p=.4*d.minor.lineHeight,v=a.padding;if(u){var b=0!==t.labelRotation,y=B.toRadians(t.labelRotation),x=Math.cos(y),_=Math.sin(y),w=_*g.width+x*(m.height-(b?m.offset:0))+(b?0:p);e.height=Math.min(t.maxHeight,e.height+w+v);var k,M,S=t.getPixelForTick(0)-t.left,D=t.right-t.getPixelForTick(t.getTicks().length-1);b?(k=l?x*c.width+_*c.offset:_*(c.height-c.offset),M=l?_*(f.height-f.offset):x*f.width+_*f.offset):(k=c.width/2,M=f.width/2),t.paddingLeft=Math.max((k-S)*t.width/(t.width-S),0)+3,t.paddingRight=Math.max((M-D)*t.width/(t.width-D),0)+3}else{var C=a.mirror?0:g.width+v+p;e.width=Math.min(t.maxWidth,e.width+C),t.paddingTop=c.height/2,t.paddingBottom=f.height/2}}t.handleMargins(),u?(t.width=t._length=n.width-t.margins.left-t.margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=n.height-t.margins.top-t.margins.bottom)},handleMargins:function(){var t=this;t.margins&&(t.margins.left=Math.max(t.paddingLeft,t.margins.left),t.margins.top=Math.max(t.paddingTop,t.margins.top),t.margins.right=Math.max(t.paddingRight,t.margins.right),t.margins.bottom=Math.max(t.paddingBottom,t.margins.bottom))},afterFit:function(){B.callback(this.options.afterFit,[this])},isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(un(t))return NaN;if(("number"==typeof t||t instanceof Number)&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},_convertTicksToLabels:function(t){var e,n,i,a=this;for(a.ticks=t.map((function(t){return t.value})),a.beforeTickToLabelConversion(),e=a.convertTicksToLabels(t)||a.ticks,a.afterTickToLabelConversion(),n=0,i=t.length;nn-1?null:this.getPixelForDecimal(t*i+(e?i/2:0))},getPixelForDecimal:function(t){return this._reversePixels&&(t=1-t),this._startPixel+t*this._length},getDecimalForPixel:function(t){var e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0},_autoSkip:function(t){var e,n,i,a,r=this.options.ticks,o=this._length,s=r.maxTicksLimit||o/this._tickSize()+1,l=r.major.enabled?function(t){var e,n,i=[];for(e=0,n=t.length;es)return function(t,e,n){var i,a,r=0,o=e[0];for(n=Math.ceil(n),i=0;iu)return r;return Math.max(u,1)}(l,t,0,s),u>0){for(e=0,n=u-1;e1?(h-d)/(u-1):null,yn(t,i,B.isNullOrUndef(a)?0:d-a,d),yn(t,i,h,B.isNullOrUndef(a)?t.length:h+a),bn(t)}return yn(t,i),bn(t)},_tickSize:function(){var t=this.options.ticks,e=B.toRadians(this.labelRotation),n=Math.abs(Math.cos(e)),i=Math.abs(Math.sin(e)),a=this._getLabelSizes(),r=t.autoSkipPadding||0,o=a?a.widest.width+r:0,s=a?a.highest.height+r:0;return this.isHorizontal()?s*n>o*i?o/n:s/i:s*i=0&&(o=t),void 0!==r&&(t=n.indexOf(r))>=0&&(s=t),e.minIndex=o,e.maxIndex=s,e.min=n[o],e.max=n[s]},buildTicks:function(){var t=this._getLabels(),e=this.minIndex,n=this.maxIndex;this.ticks=0===e&&n===t.length-1?t:t.slice(e,n+1)},getLabelForIndex:function(t,e){var n=this.chart;return n.getDatasetMeta(e).controller._getValueScaleId()===this.id?this.getRightValue(n.data.datasets[e].data[t]):this._getLabels()[t]},_configure:function(){var t=this,e=t.options.offset,n=t.ticks;_n.prototype._configure.call(t),t.isHorizontal()||(t._reversePixels=!t._reversePixels),n&&(t._startValue=t.minIndex-(e?.5:0),t._valueRange=Math.max(n.length-(e?0:1),1))},getPixelForValue:function(t,e,n){var i,a,r,o=this;return wn(e)||wn(n)||(t=o.chart.data.datasets[n].data[e]),wn(t)||(i=o.isHorizontal()?t.x:t.y),(void 0!==i||void 0!==t&&isNaN(e))&&(a=o._getLabels(),t=B.valueOrDefault(i,t),e=-1!==(r=a.indexOf(t))?r:e,isNaN(e)&&(e=t)),o.getPixelForDecimal((e-o._startValue)/o._valueRange)},getPixelForTick:function(t){var e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t],t+this.minIndex)},getValueForPixel:function(t){var e=Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange);return Math.min(Math.max(e,0),this.ticks.length-1)},getBasePixel:function(){return this.bottom}}),Mn={position:"bottom"};kn._defaults=Mn;var Sn=B.noop,Dn=B.isNullOrUndef;var Cn=_n.extend({getRightValue:function(t){return"string"==typeof t?+t:_n.prototype.getRightValue.call(this,t)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=B.sign(t.min),i=B.sign(t.max);n<0&&i<0?t.max=0:n>0&&i>0&&(t.min=0)}var a=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),a!==r&&t.min>=t.max&&(a?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:function(){var t,e=this.options.ticks,n=e.stepSize,i=e.maxTicksLimit;return n?t=Math.ceil(this.max/n)-Math.floor(this.min/n)+1:(t=this._computeTickLimit(),i=i||11),i&&(t=Math.min(i,t)),t},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:Sn,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),i={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,precision:e.precision,stepSize:B.valueOrDefault(e.fixedStepSize,e.stepSize)},a=t.ticks=function(t,e){var n,i,a,r,o=[],s=t.stepSize,l=s||1,u=t.maxTicks-1,d=t.min,h=t.max,c=t.precision,f=e.min,g=e.max,m=B.niceNum((g-f)/u/l)*l;if(m<1e-14&&Dn(d)&&Dn(h))return[f,g];(r=Math.ceil(g/m)-Math.floor(f/m))>u&&(m=B.niceNum(r*m/u/l)*l),s||Dn(c)?n=Math.pow(10,B._decimalPlaces(m)):(n=Math.pow(10,c),m=Math.ceil(m*n)/n),i=Math.floor(f/m)*m,a=Math.ceil(g/m)*m,s&&(!Dn(d)&&B.almostWhole(d/m,m/1e3)&&(i=d),!Dn(h)&&B.almostWhole(h/m,m/1e3)&&(a=h)),r=(a-i)/m,r=B.almostEquals(r,Math.round(r),m/1e3)?Math.round(r):Math.ceil(r),i=Math.round(i*n)/n,a=Math.round(a*n)/n,o.push(Dn(d)?i:d);for(var p=1;pe.length-1?null:this.getPixelForValue(e[t])}}),Fn=Pn;An._defaults=Fn;var In=B.valueOrDefault,Ln=B.math.log10;var Rn={position:"left",ticks:{callback:sn.formatters.logarithmic}};function Nn(t,e){return B.isFinite(t)&&t>=0?t:e}var Wn=_n.extend({determineDataLimits:function(){var t,e,n,i,a,r,o=this,s=o.options,l=o.chart,u=l.data.datasets,d=o.isHorizontal();function h(t){return d?t.xAxisID===o.id:t.yAxisID===o.id}o.min=Number.POSITIVE_INFINITY,o.max=Number.NEGATIVE_INFINITY,o.minNotZero=Number.POSITIVE_INFINITY;var c=s.stacked;if(void 0===c)for(t=0;t0){var e=B.min(t),n=B.max(t);o.min=Math.min(o.min,e),o.max=Math.max(o.max,n)}}))}else for(t=0;t0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(Ln(t.max))):t.minNotZero=1)},buildTicks:function(){var t=this,e=t.options.ticks,n=!t.isHorizontal(),i={min:Nn(e.min),max:Nn(e.max)},a=t.ticks=function(t,e){var n,i,a=[],r=In(t.min,Math.pow(10,Math.floor(Ln(e.min)))),o=Math.floor(Ln(e.max)),s=Math.ceil(e.max/Math.pow(10,o));0===r?(n=Math.floor(Ln(e.minNotZero)),i=Math.floor(e.minNotZero/Math.pow(10,n)),a.push(r),r=i*Math.pow(10,n)):(n=Math.floor(Ln(r)),i=Math.floor(r/Math.pow(10,n)));var l=n<0?Math.pow(10,Math.abs(n)):1;do{a.push(r),10===++i&&(i=1,l=++n>=0?1:l),r=Math.round(i*Math.pow(10,n)*l)/l}while(ne.length-1?null:this.getPixelForValue(e[t])},_getFirstTickValue:function(t){var e=Math.floor(Ln(t));return Math.floor(t/Math.pow(10,e))*Math.pow(10,e)},_configure:function(){var t=this,e=t.min,n=0;_n.prototype._configure.call(t),0===e&&(e=t._getFirstTickValue(t.minNotZero),n=In(t.options.ticks.fontSize,Y.global.defaultFontSize)/t._length),t._startValue=Ln(e),t._valueOffset=n,t._valueRange=(Ln(t.max)-Ln(e))/(1-n)},getPixelForValue:function(t){var e=this,n=0;return(t=+e.getRightValue(t))>e.min&&t>0&&(n=(Ln(t)-e._startValue)/e._valueRange+e._valueOffset),e.getPixelForDecimal(n)},getValueForPixel:function(t){var e=this,n=e.getDecimalForPixel(t);return 0===n&&0===e.min?0:Math.pow(10,e._startValue+(n-e._valueOffset)*e._valueRange)}}),Yn=Rn;Wn._defaults=Yn;var zn=B.valueOrDefault,En=B.valueAtIndexOrDefault,Vn=B.options.resolve,Hn={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:sn.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(t){return t}}};function Bn(t){var e=t.ticks;return e.display&&t.display?zn(e.fontSize,Y.global.defaultFontSize)+2*e.backdropPaddingY:0}function jn(t,e,n,i,a){return t===i||t===a?{start:e-n/2,end:e+n/2}:ta?{start:e-n,end:e}:{start:e,end:e+n}}function Un(t){return 0===t||180===t?"center":t<180?"left":"right"}function Gn(t,e,n,i){var a,r,o=n.y+i/2;if(B.isArray(e))for(a=0,r=e.length;a270||t<90)&&(n.y-=e.h)}function Zn(t){return B.isNumber(t)?t:0}var $n=Cn.extend({setDimensions:function(){var t=this;t.width=t.maxWidth,t.height=t.maxHeight,t.paddingTop=Bn(t.options)/2,t.xCenter=Math.floor(t.width/2),t.yCenter=Math.floor((t.height-t.paddingTop)/2),t.drawingArea=Math.min(t.height-t.paddingTop,t.width)/2},determineDataLimits:function(){var t=this,e=t.chart,n=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;B.each(e.data.datasets,(function(a,r){if(e.isDatasetVisible(r)){var o=e.getDatasetMeta(r);B.each(a.data,(function(e,a){var r=+t.getRightValue(e);isNaN(r)||o.data[a].hidden||(n=Math.min(r,n),i=Math.max(r,i))}))}})),t.min=n===Number.POSITIVE_INFINITY?0:n,t.max=i===Number.NEGATIVE_INFINITY?0:i,t.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/Bn(this.options))},convertTicksToLabels:function(){var t=this;Cn.prototype.convertTicksToLabels.call(t),t.pointLabels=t.chart.data.labels.map((function(){var e=B.callback(t.options.pointLabels.callback,arguments,t);return e||0===e?e:""}))},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t=this.options;t.display&&t.pointLabels.display?function(t){var e,n,i,a=B.options._parseFont(t.options.pointLabels),r={l:0,r:t.width,t:0,b:t.height-t.paddingTop},o={};t.ctx.font=a.string,t._pointLabelSizes=[];var s,l,u,d=t.chart.data.labels.length;for(e=0;er.r&&(r.r=f.end,o.r=h),g.startr.b&&(r.b=g.end,o.b=h)}t.setReductions(t.drawingArea,r,o)}(this):this.setCenterPoint(0,0,0,0)},setReductions:function(t,e,n){var i=this,a=e.l/Math.sin(n.l),r=Math.max(e.r-i.width,0)/Math.sin(n.r),o=-e.t/Math.cos(n.t),s=-Math.max(e.b-(i.height-i.paddingTop),0)/Math.cos(n.b);a=Zn(a),r=Zn(r),o=Zn(o),s=Zn(s),i.drawingArea=Math.min(Math.floor(t-(a+r)/2),Math.floor(t-(o+s)/2)),i.setCenterPoint(a,r,o,s)},setCenterPoint:function(t,e,n,i){var a=this,r=a.width-e-a.drawingArea,o=t+a.drawingArea,s=n+a.drawingArea,l=a.height-a.paddingTop-i-a.drawingArea;a.xCenter=Math.floor((o+r)/2+a.left),a.yCenter=Math.floor((s+l)/2+a.top+a.paddingTop)},getIndexAngle:function(t){var e=this.chart,n=(t*(360/e.data.labels.length)+((e.options||{}).startAngle||0))%360;return(n<0?n+360:n)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){var e=this;if(B.isNullOrUndef(t))return NaN;var n=e.drawingArea/(e.max-e.min);return e.options.ticks.reverse?(e.max-t)*n:(t-e.min)*n},getPointPosition:function(t,e){var n=this.getIndexAngle(t)-Math.PI/2;return{x:Math.cos(n)*e+this.xCenter,y:Math.sin(n)*e+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(t){var e=this.min,n=this.max;return this.getPointPositionForValue(t||0,this.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0)},_drawGrid:function(){var t,e,n,i=this,a=i.ctx,r=i.options,o=r.gridLines,s=r.angleLines,l=zn(s.lineWidth,o.lineWidth),u=zn(s.color,o.color);if(r.pointLabels.display&&function(t){var e=t.ctx,n=t.options,i=n.pointLabels,a=Bn(n),r=t.getDistanceFromCenterForValue(n.ticks.reverse?t.min:t.max),o=B.options._parseFont(i);e.save(),e.font=o.string,e.textBaseline="middle";for(var s=t.chart.data.labels.length-1;s>=0;s--){var l=0===s?a/2:0,u=t.getPointPosition(s,r+l+5),d=En(i.fontColor,s,Y.global.defaultFontColor);e.fillStyle=d;var h=t.getIndexAngle(s),c=B.toDegrees(h);e.textAlign=Un(c),qn(c,t._pointLabelSizes[s],u),Gn(e,t.pointLabels[s],u,o.lineHeight)}e.restore()}(i),o.display&&B.each(i.ticks,(function(t,n){0!==n&&(e=i.getDistanceFromCenterForValue(i.ticksAsNumbers[n]),function(t,e,n,i){var a,r=t.ctx,o=e.circular,s=t.chart.data.labels.length,l=En(e.color,i-1),u=En(e.lineWidth,i-1);if((o||s)&&l&&u){if(r.save(),r.strokeStyle=l,r.lineWidth=u,r.setLineDash&&(r.setLineDash(e.borderDash||[]),r.lineDashOffset=e.borderDashOffset||0),r.beginPath(),o)r.arc(t.xCenter,t.yCenter,n,0,2*Math.PI);else{a=t.getPointPosition(0,n),r.moveTo(a.x,a.y);for(var d=1;d=0;t--)e=i.getDistanceFromCenterForValue(r.ticks.reverse?i.min:i.max),n=i.getPointPosition(t,e),a.beginPath(),a.moveTo(i.xCenter,i.yCenter),a.lineTo(n.x,n.y),a.stroke();a.restore()}},_drawLabels:function(){var t=this,e=t.ctx,n=t.options.ticks;if(n.display){var i,a,r=t.getIndexAngle(0),o=B.options._parseFont(n),s=zn(n.fontColor,Y.global.defaultFontColor);e.save(),e.font=o.string,e.translate(t.xCenter,t.yCenter),e.rotate(r),e.textAlign="center",e.textBaseline="middle",B.each(t.ticks,(function(r,l){(0!==l||n.reverse)&&(i=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),n.showLabelBackdrop&&(a=e.measureText(r).width,e.fillStyle=n.backdropColor,e.fillRect(-a/2-n.backdropPaddingX,-i-o.size/2-n.backdropPaddingY,a+2*n.backdropPaddingX,o.size+2*n.backdropPaddingY)),e.fillStyle=s,e.fillText(r,0,-i))})),e.restore()}},_drawTitle:B.noop}),Xn=Hn;$n._defaults=Xn;var Kn=B._deprecated,Jn=B.options.resolve,Qn=B.valueOrDefault,ti=Number.MIN_SAFE_INTEGER||-9007199254740991,ei=Number.MAX_SAFE_INTEGER||9007199254740991,ni={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ii=Object.keys(ni);function ai(t,e){return t-e}function ri(t){return B.valueOrDefault(t.time.min,t.ticks.min)}function oi(t){return B.valueOrDefault(t.time.max,t.ticks.max)}function si(t,e,n,i){var a=function(t,e,n){for(var i,a,r,o=0,s=t.length-1;o>=0&&o<=s;){if(a=t[(i=o+s>>1)-1]||null,r=t[i],!a)return{lo:null,hi:r};if(r[e]n))return{lo:a,hi:r};s=i-1}}return{lo:r,hi:null}}(t,e,n),r=a.lo?a.hi?a.lo:t[t.length-2]:t[0],o=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=o[e]-r[e],l=s?(n-r[e])/s:0,u=(o[i]-r[i])*l;return r[i]+u}function li(t,e){var n=t._adapter,i=t.options.time,a=i.parser,r=a||i.format,o=e;return"function"==typeof a&&(o=a(o)),B.isFinite(o)||(o="string"==typeof r?n.parse(o,r):n.parse(o)),null!==o?+o:(a||"function"!=typeof r||(o=r(e),B.isFinite(o)||(o=n.parse(o))),o)}function ui(t,e){if(B.isNullOrUndef(e))return null;var n=t.options.time,i=li(t,t.getRightValue(e));return null===i?i:(n.round&&(i=+t._adapter.startOf(i,n.round)),i)}function di(t,e,n,i){var a,r,o,s=ii.length;for(a=ii.indexOf(t);a=0&&(e[r].major=!0);return e}(t,r,o,n):r}var ci=_n.extend({initialize:function(){this.mergeTicksOptions(),_n.prototype.initialize.call(this)},update:function(){var t=this,e=t.options,n=e.time||(e.time={}),i=t._adapter=new on._date(e.adapters.date);return Kn("time scale",n.format,"time.format","time.parser"),Kn("time scale",n.min,"time.min","ticks.min"),Kn("time scale",n.max,"time.max","ticks.max"),B.mergeIf(n.displayFormats,i.formats()),_n.prototype.update.apply(t,arguments)},getRightValue:function(t){return t&&void 0!==t.t&&(t=t.t),_n.prototype.getRightValue.call(this,t)},determineDataLimits:function(){var t,e,n,i,a,r,o,s=this,l=s.chart,u=s._adapter,d=s.options,h=d.time.unit||"day",c=ei,f=ti,g=[],m=[],p=[],v=s._getLabels();for(t=0,n=v.length;t1?function(t){var e,n,i,a={},r=[];for(e=0,n=t.length;e1e5*u)throw e+" and "+n+" are too far apart with stepSize of "+u+" "+l;for(a=h;a=a&&n<=r&&d.push(n);return i.min=a,i.max=r,i._unit=l.unit||(s.autoSkip?di(l.minUnit,i.min,i.max,h):function(t,e,n,i,a){var r,o;for(r=ii.length-1;r>=ii.indexOf(n);r--)if(o=ii[r],ni[o].common&&t._adapter.diff(a,i,o)>=e-1)return o;return ii[n?ii.indexOf(n):0]}(i,d.length,l.minUnit,i.min,i.max)),i._majorUnit=s.major.enabled&&"year"!==i._unit?function(t){for(var e=ii.indexOf(t)+1,n=ii.length;ee&&s=0&&t0?s:1}}),fi={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};ci._defaults=fi;var gi={category:kn,linear:An,logarithmic:Wn,radialLinear:$n,time:ci},mi=e((function(e,n){e.exports=function(){var n,i;function a(){return n.apply(null,arguments)}function r(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function o(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return void 0===t}function l(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function u(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function d(t,e){var n,i=[];for(n=0;n>>0,i=0;i0)for(n=0;n=0?n?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+i}var E=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,V=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,H={},B={};function j(t,e,n,i){var a=i;"string"==typeof i&&(a=function(){return this[i]()}),t&&(B[t]=a),e&&(B[e[0]]=function(){return z(a.apply(this,arguments),e[1],e[2])}),n&&(B[n]=function(){return this.localeData().ordinal(a.apply(this,arguments),t)})}function U(t,e){return t.isValid()?(e=G(e,t.localeData()),H[e]=H[e]||function(t){var e,n,i,a=t.match(E);for(e=0,n=a.length;e=0&&V.test(t);)t=t.replace(V,i),V.lastIndex=0,n-=1;return t}var q=/\d/,Z=/\d\d/,$=/\d{3}/,X=/\d{4}/,K=/[+-]?\d{6}/,J=/\d\d?/,Q=/\d\d\d\d?/,tt=/\d\d\d\d\d\d?/,et=/\d{1,3}/,nt=/\d{1,4}/,it=/[+-]?\d{1,6}/,at=/\d+/,rt=/[+-]?\d+/,ot=/Z|[+-]\d\d:?\d\d/gi,st=/Z|[+-]\d\d(?::?\d\d)?/gi,lt=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,ut={};function dt(t,e,n){ut[t]=O(e)?e:function(t,i){return t&&n?n:e}}function ht(t,e){return h(ut,t)?ut[t](e._strict,e._locale):new RegExp(ct(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,(function(t,e,n,i,a){return e||n||i||a}))))}function ct(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var ft={};function gt(t,e){var n,i=e;for("string"==typeof t&&(t=[t]),l(e)&&(i=function(t,n){n[e]=k(t)}),n=0;n68?1900:2e3)};var Pt,Tt=Ot("FullYear",!0);function Ot(t,e){return function(n){return null!=n?(Ft(this,t,n),a.updateOffset(this,e),this):At(this,t)}}function At(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function Ft(t,e,n){t.isValid()&&!isNaN(n)&&("FullYear"===e&&Ct(t.year())&&1===t.month()&&29===t.date()?t._d["set"+(t._isUTC?"UTC":"")+e](n,t.month(),It(n,t.month())):t._d["set"+(t._isUTC?"UTC":"")+e](n))}function It(t,e){if(isNaN(t)||isNaN(e))return NaN;var n=function(t,e){return(t%e+e)%e}(e,12);return t+=(e-n)/12,1===n?Ct(t)?29:28:31-n%7%2}Pt=Array.prototype.indexOf?Array.prototype.indexOf:function(t){var e;for(e=0;e=0?(s=new Date(t+400,e,n,i,a,r,o),isFinite(s.getFullYear())&&s.setFullYear(t)):s=new Date(t,e,n,i,a,r,o),s}function jt(t){var e;if(t<100&&t>=0){var n=Array.prototype.slice.call(arguments);n[0]=t+400,e=new Date(Date.UTC.apply(null,n)),isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t)}else e=new Date(Date.UTC.apply(null,arguments));return e}function Ut(t,e,n){var i=7+e-n;return-(7+jt(t,0,i).getUTCDay()-e)%7+i-1}function Gt(t,e,n,i,a){var r,o,s=1+7*(e-1)+(7+n-i)%7+Ut(t,i,a);return s<=0?o=Dt(r=t-1)+s:s>Dt(t)?(r=t+1,o=s-Dt(t)):(r=t,o=s),{year:r,dayOfYear:o}}function qt(t,e,n){var i,a,r=Ut(t.year(),e,n),o=Math.floor((t.dayOfYear()-r-1)/7)+1;return o<1?i=o+Zt(a=t.year()-1,e,n):o>Zt(t.year(),e,n)?(i=o-Zt(t.year(),e,n),a=t.year()+1):(a=t.year(),i=o),{week:i,year:a}}function Zt(t,e,n){var i=Ut(t,e,n),a=Ut(t+1,e,n);return(Dt(t)-i+a)/7}function $t(t,e){return t.slice(e,7).concat(t.slice(0,e))}j("w",["ww",2],"wo","week"),j("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),Y("week",5),Y("isoWeek",5),dt("w",J),dt("ww",J,Z),dt("W",J),dt("WW",J,Z),mt(["w","ww","W","WW"],(function(t,e,n,i){e[i.substr(0,1)]=k(t)})),j("d",0,"do","day"),j("dd",0,0,(function(t){return this.localeData().weekdaysMin(this,t)})),j("ddd",0,0,(function(t){return this.localeData().weekdaysShort(this,t)})),j("dddd",0,0,(function(t){return this.localeData().weekdays(this,t)})),j("e",0,0,"weekday"),j("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),Y("day",11),Y("weekday",11),Y("isoWeekday",11),dt("d",J),dt("e",J),dt("E",J),dt("dd",(function(t,e){return e.weekdaysMinRegex(t)})),dt("ddd",(function(t,e){return e.weekdaysShortRegex(t)})),dt("dddd",(function(t,e){return e.weekdaysRegex(t)})),mt(["dd","ddd","dddd"],(function(t,e,n,i){var a=n._locale.weekdaysParse(t,i,n._strict);null!=a?e.d=a:g(n).invalidWeekday=t})),mt(["d","e","E"],(function(t,e,n,i){e[i]=k(t)}));var Xt="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Kt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Jt="Su_Mo_Tu_We_Th_Fr_Sa".split("_");function Qt(t,e,n){var i,a,r,o=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],i=0;i<7;++i)r=f([2e3,1]).day(i),this._minWeekdaysParse[i]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[i]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[i]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===e?-1!==(a=Pt.call(this._weekdaysParse,o))?a:null:"ddd"===e?-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:null:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:null:"dddd"===e?-1!==(a=Pt.call(this._weekdaysParse,o))?a:-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:null:"ddd"===e?-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:-1!==(a=Pt.call(this._weekdaysParse,o))?a:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:null:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:-1!==(a=Pt.call(this._weekdaysParse,o))?a:-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:null}var te=lt,ee=lt,ne=lt;function ie(){function t(t,e){return e.length-t.length}var e,n,i,a,r,o=[],s=[],l=[],u=[];for(e=0;e<7;e++)n=f([2e3,1]).day(e),i=this.weekdaysMin(n,""),a=this.weekdaysShort(n,""),r=this.weekdays(n,""),o.push(i),s.push(a),l.push(r),u.push(i),u.push(a),u.push(r);for(o.sort(t),s.sort(t),l.sort(t),u.sort(t),e=0;e<7;e++)s[e]=ct(s[e]),l[e]=ct(l[e]),u[e]=ct(u[e]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function ae(){return this.hours()%12||12}function re(t,e){j(t,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)}))}function oe(t,e){return e._meridiemParse}j("H",["HH",2],0,"hour"),j("h",["hh",2],0,ae),j("k",["kk",2],0,(function(){return this.hours()||24})),j("hmm",0,0,(function(){return""+ae.apply(this)+z(this.minutes(),2)})),j("hmmss",0,0,(function(){return""+ae.apply(this)+z(this.minutes(),2)+z(this.seconds(),2)})),j("Hmm",0,0,(function(){return""+this.hours()+z(this.minutes(),2)})),j("Hmmss",0,0,(function(){return""+this.hours()+z(this.minutes(),2)+z(this.seconds(),2)})),re("a",!0),re("A",!1),L("hour","h"),Y("hour",13),dt("a",oe),dt("A",oe),dt("H",J),dt("h",J),dt("k",J),dt("HH",J,Z),dt("hh",J,Z),dt("kk",J,Z),dt("hmm",Q),dt("hmmss",tt),dt("Hmm",Q),dt("Hmmss",tt),gt(["H","HH"],xt),gt(["k","kk"],(function(t,e,n){var i=k(t);e[xt]=24===i?0:i})),gt(["a","A"],(function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t})),gt(["h","hh"],(function(t,e,n){e[xt]=k(t),g(n).bigHour=!0})),gt("hmm",(function(t,e,n){var i=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i)),g(n).bigHour=!0})),gt("hmmss",(function(t,e,n){var i=t.length-4,a=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i,2)),e[wt]=k(t.substr(a)),g(n).bigHour=!0})),gt("Hmm",(function(t,e,n){var i=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i))})),gt("Hmmss",(function(t,e,n){var i=t.length-4,a=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i,2)),e[wt]=k(t.substr(a))}));var se,le=Ot("Hours",!0),ue={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Rt,monthsShort:Nt,week:{dow:0,doy:6},weekdays:Xt,weekdaysMin:Jt,weekdaysShort:Kt,meridiemParse:/[ap]\.?m?\.?/i},de={},he={};function ce(t){return t?t.toLowerCase().replace("_","-"):t}function fe(n){var i=null;if(!de[n]&&e&&e.exports)try{i=se._abbr,t(),ge(i)}catch(t){}return de[n]}function ge(t,e){var n;return t&&((n=s(e)?pe(t):me(t,e))?se=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),se._abbr}function me(t,e){if(null!==e){var n,i=ue;if(e.abbr=t,null!=de[t])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),i=de[t]._config;else if(null!=e.parentLocale)if(null!=de[e.parentLocale])i=de[e.parentLocale]._config;else{if(null==(n=fe(e.parentLocale)))return he[e.parentLocale]||(he[e.parentLocale]=[]),he[e.parentLocale].push({name:t,config:e}),null;i=n._config}return de[t]=new F(A(i,e)),he[t]&&he[t].forEach((function(t){me(t.name,t.config)})),ge(t),de[t]}return delete de[t],null}function pe(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return se;if(!r(t)){if(e=fe(t))return e;t=[t]}return function(t){for(var e,n,i,a,r=0;r0;){if(i=fe(a.slice(0,e).join("-")))return i;if(n&&n.length>=e&&M(a,n,!0)>=e-1)break;e--}r++}return se}(t)}function ve(t){var e,n=t._a;return n&&-2===g(t).overflow&&(e=n[bt]<0||n[bt]>11?bt:n[yt]<1||n[yt]>It(n[vt],n[bt])?yt:n[xt]<0||n[xt]>24||24===n[xt]&&(0!==n[_t]||0!==n[wt]||0!==n[kt])?xt:n[_t]<0||n[_t]>59?_t:n[wt]<0||n[wt]>59?wt:n[kt]<0||n[kt]>999?kt:-1,g(t)._overflowDayOfYear&&(eyt)&&(e=yt),g(t)._overflowWeeks&&-1===e&&(e=Mt),g(t)._overflowWeekday&&-1===e&&(e=St),g(t).overflow=e),t}function be(t,e,n){return null!=t?t:null!=e?e:n}function ye(t){var e,n,i,r,o,s=[];if(!t._d){for(i=function(t){var e=new Date(a.now());return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}(t),t._w&&null==t._a[yt]&&null==t._a[bt]&&function(t){var e,n,i,a,r,o,s,l;if(null!=(e=t._w).GG||null!=e.W||null!=e.E)r=1,o=4,n=be(e.GG,t._a[vt],qt(Le(),1,4).year),i=be(e.W,1),((a=be(e.E,1))<1||a>7)&&(l=!0);else{r=t._locale._week.dow,o=t._locale._week.doy;var u=qt(Le(),r,o);n=be(e.gg,t._a[vt],u.year),i=be(e.w,u.week),null!=e.d?((a=e.d)<0||a>6)&&(l=!0):null!=e.e?(a=e.e+r,(e.e<0||e.e>6)&&(l=!0)):a=r}i<1||i>Zt(n,r,o)?g(t)._overflowWeeks=!0:null!=l?g(t)._overflowWeekday=!0:(s=Gt(n,i,a,r,o),t._a[vt]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(o=be(t._a[vt],i[vt]),(t._dayOfYear>Dt(o)||0===t._dayOfYear)&&(g(t)._overflowDayOfYear=!0),n=jt(o,0,t._dayOfYear),t._a[bt]=n.getUTCMonth(),t._a[yt]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=i[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[xt]&&0===t._a[_t]&&0===t._a[wt]&&0===t._a[kt]&&(t._nextDay=!0,t._a[xt]=0),t._d=(t._useUTC?jt:Bt).apply(null,s),r=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[xt]=24),t._w&&void 0!==t._w.d&&t._w.d!==r&&(g(t).weekdayMismatch=!0)}}var xe=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_e=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,we=/Z|[+-]\d\d(?::?\d\d)?/,ke=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Me=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Se=/^\/?Date\((\-?\d+)/i;function De(t){var e,n,i,a,r,o,s=t._i,l=xe.exec(s)||_e.exec(s);if(l){for(g(t).iso=!0,e=0,n=ke.length;e0&&g(t).unusedInput.push(o),s=s.slice(s.indexOf(n)+n.length),u+=n.length),B[r]?(n?g(t).empty=!1:g(t).unusedTokens.push(r),pt(r,n,t)):t._strict&&!n&&g(t).unusedTokens.push(r);g(t).charsLeftOver=l-u,s.length>0&&g(t).unusedInput.push(s),t._a[xt]<=12&&!0===g(t).bigHour&&t._a[xt]>0&&(g(t).bigHour=void 0),g(t).parsedDateParts=t._a.slice(0),g(t).meridiem=t._meridiem,t._a[xt]=function(t,e,n){var i;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?((i=t.isPM(n))&&e<12&&(e+=12),i||12!==e||(e=0),e):e}(t._locale,t._a[xt],t._meridiem),ye(t),ve(t)}else Oe(t);else De(t)}function Fe(t){var e=t._i,n=t._f;return t._locale=t._locale||pe(t._l),null===e||void 0===n&&""===e?p({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),_(e)?new x(ve(e)):(u(e)?t._d=e:r(n)?function(t){var e,n,i,a,r;if(0===t._f.length)return g(t).invalidFormat=!0,void(t._d=new Date(NaN));for(a=0;athis?this:t:p()}));function We(t,e){var n,i;if(1===e.length&&r(e[0])&&(e=e[0]),!e.length)return Le();for(n=e[0],i=1;i=0?new Date(t+400,e,n)-hn:new Date(t,e,n).valueOf()}function gn(t,e,n){return t<100&&t>=0?Date.UTC(t+400,e,n)-hn:Date.UTC(t,e,n)}function mn(t,e){j(0,[t,t.length],0,e)}function pn(t,e,n,i,a){var r;return null==t?qt(this,i,a).year:(e>(r=Zt(t,i,a))&&(e=r),vn.call(this,t,e,n,i,a))}function vn(t,e,n,i,a){var r=Gt(t,e,n,i,a),o=jt(r.year,0,r.dayOfYear);return this.year(o.getUTCFullYear()),this.month(o.getUTCMonth()),this.date(o.getUTCDate()),this}j(0,["gg",2],0,(function(){return this.weekYear()%100})),j(0,["GG",2],0,(function(){return this.isoWeekYear()%100})),mn("gggg","weekYear"),mn("ggggg","weekYear"),mn("GGGG","isoWeekYear"),mn("GGGGG","isoWeekYear"),L("weekYear","gg"),L("isoWeekYear","GG"),Y("weekYear",1),Y("isoWeekYear",1),dt("G",rt),dt("g",rt),dt("GG",J,Z),dt("gg",J,Z),dt("GGGG",nt,X),dt("gggg",nt,X),dt("GGGGG",it,K),dt("ggggg",it,K),mt(["gggg","ggggg","GGGG","GGGGG"],(function(t,e,n,i){e[i.substr(0,2)]=k(t)})),mt(["gg","GG"],(function(t,e,n,i){e[i]=a.parseTwoDigitYear(t)})),j("Q",0,"Qo","quarter"),L("quarter","Q"),Y("quarter",7),dt("Q",q),gt("Q",(function(t,e){e[bt]=3*(k(t)-1)})),j("D",["DD",2],"Do","date"),L("date","D"),Y("date",9),dt("D",J),dt("DD",J,Z),dt("Do",(function(t,e){return t?e._dayOfMonthOrdinalParse||e._ordinalParse:e._dayOfMonthOrdinalParseLenient})),gt(["D","DD"],yt),gt("Do",(function(t,e){e[yt]=k(t.match(J)[0])}));var bn=Ot("Date",!0);j("DDD",["DDDD",3],"DDDo","dayOfYear"),L("dayOfYear","DDD"),Y("dayOfYear",4),dt("DDD",et),dt("DDDD",$),gt(["DDD","DDDD"],(function(t,e,n){n._dayOfYear=k(t)})),j("m",["mm",2],0,"minute"),L("minute","m"),Y("minute",14),dt("m",J),dt("mm",J,Z),gt(["m","mm"],_t);var yn=Ot("Minutes",!1);j("s",["ss",2],0,"second"),L("second","s"),Y("second",15),dt("s",J),dt("ss",J,Z),gt(["s","ss"],wt);var xn,_n=Ot("Seconds",!1);for(j("S",0,0,(function(){return~~(this.millisecond()/100)})),j(0,["SS",2],0,(function(){return~~(this.millisecond()/10)})),j(0,["SSS",3],0,"millisecond"),j(0,["SSSS",4],0,(function(){return 10*this.millisecond()})),j(0,["SSSSS",5],0,(function(){return 100*this.millisecond()})),j(0,["SSSSSS",6],0,(function(){return 1e3*this.millisecond()})),j(0,["SSSSSSS",7],0,(function(){return 1e4*this.millisecond()})),j(0,["SSSSSSSS",8],0,(function(){return 1e5*this.millisecond()})),j(0,["SSSSSSSSS",9],0,(function(){return 1e6*this.millisecond()})),L("millisecond","ms"),Y("millisecond",16),dt("S",et,q),dt("SS",et,Z),dt("SSS",et,$),xn="SSSS";xn.length<=9;xn+="S")dt(xn,at);function wn(t,e){e[kt]=k(1e3*("0."+t))}for(xn="S";xn.length<=9;xn+="S")gt(xn,wn);var kn=Ot("Milliseconds",!1);j("z",0,0,"zoneAbbr"),j("zz",0,0,"zoneName");var Mn=x.prototype;function Sn(t){return t}Mn.add=en,Mn.calendar=function(t,e){var n=t||Le(),i=Ue(n,this).startOf("day"),r=a.calendarFormat(this,i)||"sameElse",o=e&&(O(e[r])?e[r].call(this,n):e[r]);return this.format(o||this.localeData().calendar(r,this,Le(n)))},Mn.clone=function(){return new x(this)},Mn.diff=function(t,e,n){var i,a,r;if(!this.isValid())return NaN;if(!(i=Ue(t,this)).isValid())return NaN;switch(a=6e4*(i.utcOffset()-this.utcOffset()),e=R(e)){case"year":r=an(this,i)/12;break;case"month":r=an(this,i);break;case"quarter":r=an(this,i)/3;break;case"second":r=(this-i)/1e3;break;case"minute":r=(this-i)/6e4;break;case"hour":r=(this-i)/36e5;break;case"day":r=(this-i-a)/864e5;break;case"week":r=(this-i-a)/6048e5;break;default:r=this-i}return n?r:w(r)},Mn.endOf=function(t){var e;if(void 0===(t=R(t))||"millisecond"===t||!this.isValid())return this;var n=this._isUTC?gn:fn;switch(t){case"year":e=n(this.year()+1,0,1)-1;break;case"quarter":e=n(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":e=n(this.year(),this.month()+1,1)-1;break;case"week":e=n(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":e=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":e=n(this.year(),this.month(),this.date()+1)-1;break;case"hour":e=this._d.valueOf(),e+=dn-cn(e+(this._isUTC?0:this.utcOffset()*un),dn)-1;break;case"minute":e=this._d.valueOf(),e+=un-cn(e,un)-1;break;case"second":e=this._d.valueOf(),e+=ln-cn(e,ln)-1}return this._d.setTime(e),a.updateOffset(this,!0),this},Mn.format=function(t){t||(t=this.isUtc()?a.defaultFormatUtc:a.defaultFormat);var e=U(this,t);return this.localeData().postformat(e)},Mn.from=function(t,e){return this.isValid()&&(_(t)&&t.isValid()||Le(t).isValid())?Xe({to:this,from:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()},Mn.fromNow=function(t){return this.from(Le(),t)},Mn.to=function(t,e){return this.isValid()&&(_(t)&&t.isValid()||Le(t).isValid())?Xe({from:this,to:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()},Mn.toNow=function(t){return this.to(Le(),t)},Mn.get=function(t){return O(this[t=R(t)])?this[t]():this},Mn.invalidAt=function(){return g(this).overflow},Mn.isAfter=function(t,e){var n=_(t)?t:Le(t);return!(!this.isValid()||!n.isValid())&&("millisecond"===(e=R(e)||"millisecond")?this.valueOf()>n.valueOf():n.valueOf()9999?U(n,e?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):O(Date.prototype.toISOString)?e?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",U(n,"Z")):U(n,e?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},Mn.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var t="moment",e="";this.isLocal()||(t=0===this.utcOffset()?"moment.utc":"moment.parseZone",e="Z");var n="["+t+'("]',i=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",a=e+'[")]';return this.format(n+i+"-MM-DD[T]HH:mm:ss.SSS"+a)},Mn.toJSON=function(){return this.isValid()?this.toISOString():null},Mn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},Mn.unix=function(){return Math.floor(this.valueOf()/1e3)},Mn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},Mn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},Mn.year=Tt,Mn.isLeapYear=function(){return Ct(this.year())},Mn.weekYear=function(t){return pn.call(this,t,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},Mn.isoWeekYear=function(t){return pn.call(this,t,this.isoWeek(),this.isoWeekday(),1,4)},Mn.quarter=Mn.quarters=function(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)},Mn.month=zt,Mn.daysInMonth=function(){return It(this.year(),this.month())},Mn.week=Mn.weeks=function(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")},Mn.isoWeek=Mn.isoWeeks=function(t){var e=qt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")},Mn.weeksInYear=function(){var t=this.localeData()._week;return Zt(this.year(),t.dow,t.doy)},Mn.isoWeeksInYear=function(){return Zt(this.year(),1,4)},Mn.date=bn,Mn.day=Mn.days=function(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=function(t,e){return"string"!=typeof t?t:isNaN(t)?"number"==typeof(t=e.weekdaysParse(t))?t:null:parseInt(t,10)}(t,this.localeData()),this.add(t-e,"d")):e},Mn.weekday=function(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")},Mn.isoWeekday=function(t){if(!this.isValid())return null!=t?this:NaN;if(null!=t){var e=function(t,e){return"string"==typeof t?e.weekdaysParse(t)%7||7:isNaN(t)?null:t}(t,this.localeData());return this.day(this.day()%7?e:e-7)}return this.day()||7},Mn.dayOfYear=function(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")},Mn.hour=Mn.hours=le,Mn.minute=Mn.minutes=yn,Mn.second=Mn.seconds=_n,Mn.millisecond=Mn.milliseconds=kn,Mn.utcOffset=function(t,e,n){var i,r=this._offset||0;if(!this.isValid())return null!=t?this:NaN;if(null!=t){if("string"==typeof t){if(null===(t=je(st,t)))return this}else Math.abs(t)<16&&!n&&(t*=60);return!this._isUTC&&e&&(i=Ge(this)),this._offset=t,this._isUTC=!0,null!=i&&this.add(i,"m"),r!==t&&(!e||this._changeInProgress?tn(this,Xe(t-r,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?r:Ge(this)},Mn.utc=function(t){return this.utcOffset(0,t)},Mn.local=function(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Ge(this),"m")),this},Mn.parseZone=function(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if("string"==typeof this._i){var t=je(ot,this._i);null!=t?this.utcOffset(t):this.utcOffset(0,!0)}return this},Mn.hasAlignedHourOffset=function(t){return!!this.isValid()&&(t=t?Le(t).utcOffset():0,(this.utcOffset()-t)%60==0)},Mn.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},Mn.isLocal=function(){return!!this.isValid()&&!this._isUTC},Mn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},Mn.isUtc=qe,Mn.isUTC=qe,Mn.zoneAbbr=function(){return this._isUTC?"UTC":""},Mn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},Mn.dates=D("dates accessor is deprecated. Use date instead.",bn),Mn.months=D("months accessor is deprecated. Use month instead",zt),Mn.years=D("years accessor is deprecated. Use year instead",Tt),Mn.zone=D("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()})),Mn.isDSTShifted=D("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(b(t,this),(t=Fe(t))._a){var e=t._isUTC?f(t._a):Le(t._a);this._isDSTShifted=this.isValid()&&M(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}));var Dn=F.prototype;function Cn(t,e,n,i){var a=pe(),r=f().set(i,e);return a[n](r,t)}function Pn(t,e,n){if(l(t)&&(e=t,t=void 0),t=t||"",null!=e)return Cn(t,e,n,"month");var i,a=[];for(i=0;i<12;i++)a[i]=Cn(t,i,n,"month");return a}function Tn(t,e,n,i){"boolean"==typeof t?(l(e)&&(n=e,e=void 0),e=e||""):(n=e=t,t=!1,l(e)&&(n=e,e=void 0),e=e||"");var a,r=pe(),o=t?r._week.dow:0;if(null!=n)return Cn(e,(n+o)%7,i,"day");var s=[];for(a=0;a<7;a++)s[a]=Cn(e,(a+o)%7,i,"day");return s}Dn.calendar=function(t,e,n){var i=this._calendar[t]||this._calendar.sameElse;return O(i)?i.call(e,n):i},Dn.longDateFormat=function(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()];return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,(function(t){return t.slice(1)})),this._longDateFormat[t])},Dn.invalidDate=function(){return this._invalidDate},Dn.ordinal=function(t){return this._ordinal.replace("%d",t)},Dn.preparse=Sn,Dn.postformat=Sn,Dn.relativeTime=function(t,e,n,i){var a=this._relativeTime[n];return O(a)?a(t,e,n,i):a.replace(/%d/i,t)},Dn.pastFuture=function(t,e){var n=this._relativeTime[t>0?"future":"past"];return O(n)?n(e):n.replace(/%s/i,e)},Dn.set=function(t){var e,n;for(n in t)O(e=t[n])?this[n]=e:this["_"+n]=e;this._config=t,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},Dn.months=function(t,e){return t?r(this._months)?this._months[t.month()]:this._months[(this._months.isFormat||Lt).test(e)?"format":"standalone"][t.month()]:r(this._months)?this._months:this._months.standalone},Dn.monthsShort=function(t,e){return t?r(this._monthsShort)?this._monthsShort[t.month()]:this._monthsShort[Lt.test(e)?"format":"standalone"][t.month()]:r(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},Dn.monthsParse=function(t,e,n){var i,a,r;if(this._monthsParseExact)return Wt.call(this,t,e,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),i=0;i<12;i++){if(a=f([2e3,i]),n&&!this._longMonthsParse[i]&&(this._longMonthsParse[i]=new RegExp("^"+this.months(a,"").replace(".","")+"$","i"),this._shortMonthsParse[i]=new RegExp("^"+this.monthsShort(a,"").replace(".","")+"$","i")),n||this._monthsParse[i]||(r="^"+this.months(a,"")+"|^"+this.monthsShort(a,""),this._monthsParse[i]=new RegExp(r.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[i].test(t))return i;if(n&&"MMM"===e&&this._shortMonthsParse[i].test(t))return i;if(!n&&this._monthsParse[i].test(t))return i}},Dn.monthsRegex=function(t){return this._monthsParseExact?(h(this,"_monthsRegex")||Ht.call(this),t?this._monthsStrictRegex:this._monthsRegex):(h(this,"_monthsRegex")||(this._monthsRegex=Vt),this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex)},Dn.monthsShortRegex=function(t){return this._monthsParseExact?(h(this,"_monthsRegex")||Ht.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):(h(this,"_monthsShortRegex")||(this._monthsShortRegex=Et),this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex)},Dn.week=function(t){return qt(t,this._week.dow,this._week.doy).week},Dn.firstDayOfYear=function(){return this._week.doy},Dn.firstDayOfWeek=function(){return this._week.dow},Dn.weekdays=function(t,e){var n=r(this._weekdays)?this._weekdays:this._weekdays[t&&!0!==t&&this._weekdays.isFormat.test(e)?"format":"standalone"];return!0===t?$t(n,this._week.dow):t?n[t.day()]:n},Dn.weekdaysMin=function(t){return!0===t?$t(this._weekdaysMin,this._week.dow):t?this._weekdaysMin[t.day()]:this._weekdaysMin},Dn.weekdaysShort=function(t){return!0===t?$t(this._weekdaysShort,this._week.dow):t?this._weekdaysShort[t.day()]:this._weekdaysShort},Dn.weekdaysParse=function(t,e,n){var i,a,r;if(this._weekdaysParseExact)return Qt.call(this,t,e,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),i=0;i<7;i++){if(a=f([2e3,1]).day(i),n&&!this._fullWeekdaysParse[i]&&(this._fullWeekdaysParse[i]=new RegExp("^"+this.weekdays(a,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[i]=new RegExp("^"+this.weekdaysShort(a,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[i]=new RegExp("^"+this.weekdaysMin(a,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[i]||(r="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,""),this._weekdaysParse[i]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[i].test(t))return i;if(n&&"ddd"===e&&this._shortWeekdaysParse[i].test(t))return i;if(n&&"dd"===e&&this._minWeekdaysParse[i].test(t))return i;if(!n&&this._weekdaysParse[i].test(t))return i}},Dn.weekdaysRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||ie.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):(h(this,"_weekdaysRegex")||(this._weekdaysRegex=te),this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex)},Dn.weekdaysShortRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||ie.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(h(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ee),this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},Dn.weekdaysMinRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||ie.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(h(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=ne),this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},Dn.isPM=function(t){return"p"===(t+"").toLowerCase().charAt(0)},Dn.meridiem=function(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"},ge("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10;return t+(1===k(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th")}}),a.lang=D("moment.lang is deprecated. Use moment.locale instead.",ge),a.langData=D("moment.langData is deprecated. Use moment.localeData instead.",pe);var On=Math.abs;function An(t,e,n,i){var a=Xe(e,n);return t._milliseconds+=i*a._milliseconds,t._days+=i*a._days,t._months+=i*a._months,t._bubble()}function Fn(t){return t<0?Math.floor(t):Math.ceil(t)}function In(t){return 4800*t/146097}function Ln(t){return 146097*t/4800}function Rn(t){return function(){return this.as(t)}}var Nn=Rn("ms"),Wn=Rn("s"),Yn=Rn("m"),zn=Rn("h"),En=Rn("d"),Vn=Rn("w"),Hn=Rn("M"),Bn=Rn("Q"),jn=Rn("y");function Un(t){return function(){return this.isValid()?this._data[t]:NaN}}var Gn=Un("milliseconds"),qn=Un("seconds"),Zn=Un("minutes"),$n=Un("hours"),Xn=Un("days"),Kn=Un("months"),Jn=Un("years"),Qn=Math.round,ti={ss:44,s:45,m:45,h:22,d:26,M:11};function ei(t,e,n,i,a){return a.relativeTime(e||1,!!n,t,i)}var ni=Math.abs;function ii(t){return(t>0)-(t<0)||+t}function ai(){if(!this.isValid())return this.localeData().invalidDate();var t,e,n=ni(this._milliseconds)/1e3,i=ni(this._days),a=ni(this._months);t=w(n/60),e=w(t/60),n%=60,t%=60;var r=w(a/12),o=a%=12,s=i,l=e,u=t,d=n?n.toFixed(3).replace(/\.?0+$/,""):"",h=this.asSeconds();if(!h)return"P0D";var c=h<0?"-":"",f=ii(this._months)!==ii(h)?"-":"",g=ii(this._days)!==ii(h)?"-":"",m=ii(this._milliseconds)!==ii(h)?"-":"";return c+"P"+(r?f+r+"Y":"")+(o?f+o+"M":"")+(s?g+s+"D":"")+(l||u||d?"T":"")+(l?m+l+"H":"")+(u?m+u+"M":"")+(d?m+d+"S":"")}var ri=ze.prototype;return ri.isValid=function(){return this._isValid},ri.abs=function(){var t=this._data;return this._milliseconds=On(this._milliseconds),this._days=On(this._days),this._months=On(this._months),t.milliseconds=On(t.milliseconds),t.seconds=On(t.seconds),t.minutes=On(t.minutes),t.hours=On(t.hours),t.months=On(t.months),t.years=On(t.years),this},ri.add=function(t,e){return An(this,t,e,1)},ri.subtract=function(t,e){return An(this,t,e,-1)},ri.as=function(t){if(!this.isValid())return NaN;var e,n,i=this._milliseconds;if("month"===(t=R(t))||"quarter"===t||"year"===t)switch(e=this._days+i/864e5,n=this._months+In(e),t){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(e=this._days+Math.round(Ln(this._months)),t){case"week":return e/7+i/6048e5;case"day":return e+i/864e5;case"hour":return 24*e+i/36e5;case"minute":return 1440*e+i/6e4;case"second":return 86400*e+i/1e3;case"millisecond":return Math.floor(864e5*e)+i;default:throw new Error("Unknown unit "+t)}},ri.asMilliseconds=Nn,ri.asSeconds=Wn,ri.asMinutes=Yn,ri.asHours=zn,ri.asDays=En,ri.asWeeks=Vn,ri.asMonths=Hn,ri.asQuarters=Bn,ri.asYears=jn,ri.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*k(this._months/12):NaN},ri._bubble=function(){var t,e,n,i,a,r=this._milliseconds,o=this._days,s=this._months,l=this._data;return r>=0&&o>=0&&s>=0||r<=0&&o<=0&&s<=0||(r+=864e5*Fn(Ln(s)+o),o=0,s=0),l.milliseconds=r%1e3,t=w(r/1e3),l.seconds=t%60,e=w(t/60),l.minutes=e%60,n=w(e/60),l.hours=n%24,o+=w(n/24),a=w(In(o)),s+=a,o-=Fn(Ln(a)),i=w(s/12),s%=12,l.days=o,l.months=s,l.years=i,this},ri.clone=function(){return Xe(this)},ri.get=function(t){return t=R(t),this.isValid()?this[t+"s"]():NaN},ri.milliseconds=Gn,ri.seconds=qn,ri.minutes=Zn,ri.hours=$n,ri.days=Xn,ri.weeks=function(){return w(this.days()/7)},ri.months=Kn,ri.years=Jn,ri.humanize=function(t){if(!this.isValid())return this.localeData().invalidDate();var e=this.localeData(),n=function(t,e,n){var i=Xe(t).abs(),a=Qn(i.as("s")),r=Qn(i.as("m")),o=Qn(i.as("h")),s=Qn(i.as("d")),l=Qn(i.as("M")),u=Qn(i.as("y")),d=a<=ti.ss&&["s",a]||a0,d[4]=n,ei.apply(null,d)}(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)},ri.toISOString=ai,ri.toString=ai,ri.toJSON=ai,ri.locale=rn,ri.localeData=sn,ri.toIsoString=D("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ai),ri.lang=on,j("X",0,0,"unix"),j("x",0,0,"valueOf"),dt("x",rt),dt("X",/[+-]?\d+(\.\d{1,3})?/),gt("X",(function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))})),gt("x",(function(t,e,n){n._d=new Date(k(t))})),a.version="2.24.0",n=Le,a.fn=Mn,a.min=function(){return We("isBefore",[].slice.call(arguments,0))},a.max=function(){return We("isAfter",[].slice.call(arguments,0))},a.now=function(){return Date.now?Date.now():+new Date},a.utc=f,a.unix=function(t){return Le(1e3*t)},a.months=function(t,e){return Pn(t,e,"months")},a.isDate=u,a.locale=ge,a.invalid=p,a.duration=Xe,a.isMoment=_,a.weekdays=function(t,e,n){return Tn(t,e,n,"weekdays")},a.parseZone=function(){return Le.apply(null,arguments).parseZone()},a.localeData=pe,a.isDuration=Ee,a.monthsShort=function(t,e){return Pn(t,e,"monthsShort")},a.weekdaysMin=function(t,e,n){return Tn(t,e,n,"weekdaysMin")},a.defineLocale=me,a.updateLocale=function(t,e){if(null!=e){var n,i,a=ue;null!=(i=fe(t))&&(a=i._config),e=A(a,e),(n=new F(e)).parentLocale=de[t],de[t]=n,ge(t)}else null!=de[t]&&(null!=de[t].parentLocale?de[t]=de[t].parentLocale:null!=de[t]&&delete de[t]);return de[t]},a.locales=function(){return C(de)},a.weekdaysShort=function(t,e,n){return Tn(t,e,n,"weekdaysShort")},a.normalizeUnits=R,a.relativeTimeRounding=function(t){return void 0===t?Qn:"function"==typeof t&&(Qn=t,!0)},a.relativeTimeThreshold=function(t,e){return void 0!==ti[t]&&(void 0===e?ti[t]:(ti[t]=e,"s"===t&&(ti.ss=e-1),!0))},a.calendarFormat=function(t,e){var n=t.diff(e,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"},a.prototype=Mn,a.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},a}()})),pi={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};on._date.override("function"==typeof mi?{_id:"moment",formats:function(){return pi},parse:function(t,e){return"string"==typeof t&&"string"==typeof e?t=mi(t,e):t instanceof mi||(t=mi(t)),t.isValid()?t.valueOf():null},format:function(t,e){return mi(t).format(e)},add:function(t,e,n){return mi(t).add(e,n).valueOf()},diff:function(t,e,n){return mi(t).diff(mi(e),n)},startOf:function(t,e,n){return t=mi(t),"isoWeek"===e?t.isoWeekday(n).valueOf():t.startOf(e).valueOf()},endOf:function(t,e){return mi(t).endOf(e).valueOf()},_create:function(t){return mi(t)}}:{}),Y._set("global",{plugins:{filler:{propagate:!0}}});var vi={dataset:function(t){var e=t.fill,n=t.chart,i=n.getDatasetMeta(e),a=i&&n.isDatasetVisible(e)&&i.dataset._children||[],r=a.length||0;return r?function(t,e){return e=n)&&i;switch(r){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return r;default:return!1}}function yi(t){return(t.el._scale||{}).getPointPositionForValue?function(t){var e,n,i,a,r,o=t.el._scale,s=o.options,l=o.chart.data.labels.length,u=t.fill,d=[];if(!l)return null;for(e=s.ticks.reverse?o.max:o.min,n=s.ticks.reverse?o.min:o.max,i=o.getPointPositionForValue(0,e),a=0;a0;--r)B.canvas.lineTo(t,n[r],n[r-1],!0);else for(o=n[0].cx,s=n[0].cy,l=Math.sqrt(Math.pow(n[0].x-o,2)+Math.pow(n[0].y-s,2)),r=a-1;r>0;--r)t.arc(o,s,l,n[r].angle,n[r-1].angle,!0)}}function Mi(t,e,n,i,a,r){var o,s,l,u,d,h,c,f,g=e.length,m=i.spanGaps,p=[],v=[],b=0,y=0;for(t.beginPath(),o=0,s=g;o=0;--n)(e=l[n].$filler)&&e.visible&&(a=(i=e.el)._view,r=i._children||[],o=e.mapper,s=a.backgroundColor||Y.global.defaultColor,o&&s&&r.length&&(B.canvas.clipArea(u,t.chartArea),Mi(u,r,o,a,s,i._loop),B.canvas.unclipArea(u)))}},Di=B.rtl.getRtlAdapter,Ci=B.noop,Pi=B.valueOrDefault;function Ti(t,e){return t.usePointStyle&&t.boxWidth>e?e:t.boxWidth}Y._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data.datasets,n=t.options.legend||{},i=n.labels&&n.labels.usePointStyle;return t._getSortedDatasetMetas().map((function(n){var a=n.controller.getStyle(i?0:void 0);return{text:e[n.index].label,fillStyle:a.backgroundColor,hidden:!t.isDatasetVisible(n.index),lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:a.borderWidth,strokeStyle:a.borderColor,pointStyle:a.pointStyle,rotation:a.rotation,datasetIndex:n.index}}),this)}}},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data.datasets;for(a.setAttribute("class",t.id+"-legend"),e=0,n=r.length;el.width)&&(h+=o+n.padding,d[d.length-(e>0?0:1)]=0),s[e]={left:0,top:0,width:i,height:o},d[d.length-1]+=i+n.padding})),l.height+=h}else{var c=n.padding,f=t.columnWidths=[],g=t.columnHeights=[],m=n.padding,p=0,v=0;B.each(t.legendItems,(function(t,e){var i=Ti(n,o)+o/2+a.measureText(t.text).width;e>0&&v+o+2*c>l.height&&(m+=p+n.padding,f.push(p),g.push(v),p=0,v=0),p=Math.max(p,i),v+=o+c,s[e]={left:0,top:0,width:i,height:o}})),m+=p,f.push(p),g.push(v),l.width+=m}t.width=l.width,t.height=l.height}else t.width=l.width=t.height=l.height=0},afterFit:Ci,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,n=e.labels,i=Y.global,a=i.defaultColor,r=i.elements.line,o=t.height,s=t.columnHeights,l=t.width,u=t.lineWidths;if(e.display){var d,h=Di(e.rtl,t.left,t.minSize.width),c=t.ctx,f=Pi(n.fontColor,i.defaultFontColor),g=B.options._parseFont(n),m=g.size;c.textAlign=h.textAlign("left"),c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=g.string;var p=Ti(n,m),v=t.legendHitBoxes,b=function(t,i){switch(e.align){case"start":return n.padding;case"end":return t-i;default:return(t-i+n.padding)/2}},y=t.isHorizontal();d=y?{x:t.left+b(l,u[0]),y:t.top+n.padding,line:0}:{x:t.left+n.padding,y:t.top+b(o,s[0]),line:0},B.rtl.overrideTextDirection(t.ctx,e.textDirection);var x=m+n.padding;B.each(t.legendItems,(function(e,i){var f=c.measureText(e.text).width,g=p+m/2+f,_=d.x,w=d.y;h.setWidth(t.minSize.width),y?i>0&&_+g+n.padding>t.left+t.minSize.width&&(w=d.y+=x,d.line++,_=d.x=t.left+b(l,u[d.line])):i>0&&w+x>t.top+t.minSize.height&&(_=d.x=_+t.columnWidths[d.line]+n.padding,d.line++,w=d.y=t.top+b(o,s[d.line]));var k=h.x(_);!function(t,e,i){if(!(isNaN(p)||p<=0)){c.save();var o=Pi(i.lineWidth,r.borderWidth);if(c.fillStyle=Pi(i.fillStyle,a),c.lineCap=Pi(i.lineCap,r.borderCapStyle),c.lineDashOffset=Pi(i.lineDashOffset,r.borderDashOffset),c.lineJoin=Pi(i.lineJoin,r.borderJoinStyle),c.lineWidth=o,c.strokeStyle=Pi(i.strokeStyle,a),c.setLineDash&&c.setLineDash(Pi(i.lineDash,r.borderDash)),n&&n.usePointStyle){var s=p*Math.SQRT2/2,l=h.xPlus(t,p/2),u=e+m/2;B.canvas.drawPoint(c,i.pointStyle,s,l,u,i.rotation)}else c.fillRect(h.leftForLtr(t,p),e,p,m),0!==o&&c.strokeRect(h.leftForLtr(t,p),e,p,m);c.restore()}}(k,w,e),v[i].left=h.leftForLtr(k,v[i].width),v[i].top=w,function(t,e,n,i){var a=m/2,r=h.xPlus(t,p+a),o=e+a;c.fillText(n.text,r,o),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(r,o),c.lineTo(h.xPlus(r,i),o),c.stroke())}(k,w,e,f),y?d.x+=g+n.padding:d.y+=x})),B.rtl.restoreTextDirection(t.ctx,e.textDirection)}},_getLegendItemAt:function(t,e){var n,i,a,r=this;if(t>=r.left&&t<=r.right&&e>=r.top&&e<=r.bottom)for(a=r.legendHitBoxes,n=0;n=(i=a[n]).left&&t<=i.left+i.width&&e>=i.top&&e<=i.top+i.height)return r.legendItems[n];return null},handleEvent:function(t){var e,n=this,i=n.options,a="mouseup"===t.type?"click":t.type;if("mousemove"===a){if(!i.onHover&&!i.onLeave)return}else{if("click"!==a)return;if(!i.onClick)return}e=n._getLegendItemAt(t.x,t.y),"click"===a?e&&i.onClick&&i.onClick.call(n,t.native,e):(i.onLeave&&e!==n._hoveredItem&&(n._hoveredItem&&i.onLeave.call(n,t.native,n._hoveredItem),n._hoveredItem=e),i.onHover&&e&&i.onHover.call(n,t.native,e))}});function Ai(t,e){var n=new Oi({ctx:t.ctx,options:e,chart:t});pe.configure(t,n,e),pe.addBox(t,n),t.legend=n}var Fi={id:"legend",_element:Oi,beforeInit:function(t){var e=t.options.legend;e&&Ai(t,e)},beforeUpdate:function(t){var e=t.options.legend,n=t.legend;e?(B.mergeIf(e,Y.global.legend),n?(pe.configure(t,n,e),n.options=e):Ai(t,e)):n&&(pe.removeBox(t,n),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}},Ii=B.noop;Y._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var Li=X.extend({initialize:function(t){B.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:Ii,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Ii,beforeSetDimensions:Ii,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Ii,beforeBuildLabels:Ii,buildLabels:Ii,afterBuildLabels:Ii,beforeFit:Ii,fit:function(){var t,e=this,n=e.options,i=e.minSize={},a=e.isHorizontal();n.display?(t=(B.isArray(n.text)?n.text.length:1)*B.options._parseFont(n).lineHeight+2*n.padding,e.width=i.width=a?e.maxWidth:t,e.height=i.height=a?t:e.maxHeight):e.width=i.width=e.height=i.height=0},afterFit:Ii,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,n=t.options;if(n.display){var i,a,r,o=B.options._parseFont(n),s=o.lineHeight,l=s/2+n.padding,u=0,d=t.top,h=t.left,c=t.bottom,f=t.right;e.fillStyle=B.valueOrDefault(n.fontColor,Y.global.defaultFontColor),e.font=o.string,t.isHorizontal()?(a=h+(f-h)/2,r=d+l,i=f-h):(a="left"===n.position?h+l:f-l,r=d+(c-d)/2,i=c-d,u=Math.PI*("left"===n.position?-.5:.5)),e.save(),e.translate(a,r),e.rotate(u),e.textAlign="center",e.textBaseline="middle";var g=n.text;if(B.isArray(g))for(var m=0,p=0;p=0;i--){var a=t[i];if(e(a))return a}},B.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},B.almostEquals=function(t,e,n){return Math.abs(t-e)=t},B.max=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.max(t,e)}),Number.NEGATIVE_INFINITY)},B.min=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.min(t,e)}),Number.POSITIVE_INFINITY)},B.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0===(t=+t)||isNaN(t)?t:t>0?1:-1},B.toRadians=function(t){return t*(Math.PI/180)},B.toDegrees=function(t){return t*(180/Math.PI)},B._decimalPlaces=function(t){if(B.isFinite(t)){for(var e=1,n=0;Math.round(t*e)/e!==t;)e*=10,n++;return n}},B.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),r=Math.atan2(i,n);return r<-.5*Math.PI&&(r+=2*Math.PI),{angle:r,distance:a}},B.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},B.aliasPixel=function(t){return t%2==0?0:.5},B._alignPixel=function(t,e,n){var i=t.currentDevicePixelRatio,a=n/2;return Math.round((e-a)*i)/i+a},B.splineCurve=function(t,e,n,i){var a=t.skip?e:t,r=e,o=n.skip?e:n,s=Math.sqrt(Math.pow(r.x-a.x,2)+Math.pow(r.y-a.y,2)),l=Math.sqrt(Math.pow(o.x-r.x,2)+Math.pow(o.y-r.y,2)),u=s/(s+l),d=l/(s+l),h=i*(u=isNaN(u)?0:u),c=i*(d=isNaN(d)?0:d);return{previous:{x:r.x-h*(o.x-a.x),y:r.y-h*(o.y-a.y)},next:{x:r.x+c*(o.x-a.x),y:r.y+c*(o.y-a.y)}}},B.EPSILON=Number.EPSILON||1e-14,B.splineCurveMonotone=function(t){var e,n,i,a,r,o,s,l,u,d=(t||[]).map((function(t){return{model:t._model,deltaK:0,mK:0}})),h=d.length;for(e=0;e0?d[e-1]:null,(a=e0?d[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},B.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},B.niceNum=function(t,e){var n=Math.floor(B.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},B.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},B.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.target||t.srcElement,o=r.getBoundingClientRect(),s=a.touches;s&&s.length>0?(n=s[0].clientX,i=s[0].clientY):(n=a.clientX,i=a.clientY);var l=parseFloat(B.getStyle(r,"padding-left")),u=parseFloat(B.getStyle(r,"padding-top")),d=parseFloat(B.getStyle(r,"padding-right")),h=parseFloat(B.getStyle(r,"padding-bottom")),c=o.right-o.left-l-d,f=o.bottom-o.top-u-h;return{x:n=Math.round((n-o.left-l)/c*r.width/e.currentDevicePixelRatio),y:i=Math.round((i-o.top-u)/f*r.height/e.currentDevicePixelRatio)}},B.getConstraintWidth=function(t){return n(t,"max-width","clientWidth")},B.getConstraintHeight=function(t){return n(t,"max-height","clientHeight")},B._calculatePadding=function(t,e,n){return(e=B.getStyle(t,e)).indexOf("%")>-1?n*parseInt(e,10)/100:parseInt(e,10)},B._getParentNode=function(t){var e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e},B.getMaximumWidth=function(t){var e=B._getParentNode(t);if(!e)return t.clientWidth;var n=e.clientWidth,i=n-B._calculatePadding(e,"padding-left",n)-B._calculatePadding(e,"padding-right",n),a=B.getConstraintWidth(t);return isNaN(a)?i:Math.min(i,a)},B.getMaximumHeight=function(t){var e=B._getParentNode(t);if(!e)return t.clientHeight;var n=e.clientHeight,i=n-B._calculatePadding(e,"padding-top",n)-B._calculatePadding(e,"padding-bottom",n),a=B.getConstraintHeight(t);return isNaN(a)?i:Math.min(i,a)},B.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},B.retinaScale=function(t,e){var n=t.currentDevicePixelRatio=e||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==n){var i=t.canvas,a=t.height,r=t.width;i.height=a*n,i.width=r*n,t.ctx.scale(n,n),i.style.height||i.style.width||(i.style.height=a+"px",i.style.width=r+"px")}},B.fontString=function(t,e,n){return e+" "+t+"px "+n},B.longestText=function(t,e,n,i){var a=(i=i||{}).data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var o,s,l,u,d,h=0,c=n.length;for(o=0;on.length){for(o=0;oi&&(i=r),i},B.numberOfLabelLines=function(t){var e=1;return B.each(t,(function(t){B.isArray(t)&&t.length>e&&(e=t.length)})),e},B.color=w?function(t){return t instanceof CanvasGradient&&(t=Y.global.defaultColor),w(t)}:function(t){return console.error("Color.js not found!"),t},B.getHoverColor=function(t){return t instanceof CanvasPattern||t instanceof CanvasGradient?t:B.color(t).saturate(.5).darken(.1).rgbString()}}(),nn._adapters=on,nn.Animation=J,nn.animationService=Q,nn.controllers=Qt,nn.DatasetController=at,nn.defaults=Y,nn.Element=X,nn.elements=kt,nn.Interaction=oe,nn.layouts=pe,nn.platform=Le,nn.plugins=Re,nn.Scale=_n,nn.scaleService=Ne,nn.Ticks=sn,nn.Tooltip=qe,nn.helpers.each(gi,(function(t,e){nn.scaleService.registerScaleType(e,t,t._defaults)})),Ni)Ni.hasOwnProperty(Ei)&&nn.plugins.register(Ni[Ei]);nn.platform.initialize();var Vi=nn;return"undefined"!=typeof window&&(window.Chart=nn),nn.Chart=nn,nn.Legend=Ni.legend._element,nn.Title=Ni.title._element,nn.pluginService=nn.plugins,nn.PluginBase=nn.Element.extend({}),nn.canvasHelpers=nn.helpers.canvas,nn.layoutService=nn.layouts,nn.LinearScaleBase=Cn,nn.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],(function(t){nn[t]=function(e,n){return new nn(e,nn.helpers.merge(n||{},{type:t.charAt(0).toLowerCase()+t.slice(1)}))}})),Vi})); diff --git a/histview2/static/common/js/Chart.min.js b/histview2/static/common/js/Chart.min.js new file mode 100644 index 0000000..659e40c --- /dev/null +++ b/histview2/static/common/js/Chart.min.js @@ -0,0 +1,13 @@ +/*! + * Chart.js v3.3.0 + * https://www.chartjs.org + * (c) 2021 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";const t="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function e(e,i,n){const o=n||(t=>Array.prototype.slice.call(t));let s=!1,a=[];return function(...n){a=o(n),s||(s=!0,t.call(window,(()=>{s=!1,e.apply(i,a)})))}}function i(t,e){let i;return function(){return e?(clearTimeout(i),i=setTimeout(t,e)):t(),e}}const n=t=>"start"===t?"left":"end"===t?"right":"center",o=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,s=(t,e,i)=>"right"===t?i:"center"===t?(e+i)/2:e;var a=new class{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,n){const o=e.listeners[n],s=e.duration;o.forEach((n=>n({chart:t,initial:e.initial,numSteps:s,currentStep:Math.min(i-e.start,s)})))}_refresh(){const e=this;e._request||(e._running=!0,e._request=t.call(window,(()=>{e._update(),e._request=null,e._running&&e._refresh()})))}_update(t=Date.now()){const e=this;let i=0;e._charts.forEach(((n,o)=>{if(!n.running||!n.items.length)return;const s=n.items;let a,r=s.length-1,l=!1;for(;r>=0;--r)a=s[r],a._active?(a._total>n.duration&&(n.duration=a._total),a.tick(t),l=!0):(s[r]=s[s.length-1],s.pop());l&&(o.draw(),e._notify(o,n,t,"progress")),s.length||(n.running=!1,e._notify(o,n,t,"complete"),n.initial=!1),i+=s.length})),e._lastDate=t,0===i&&(e._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let n=i.length-1;for(;n>=0;--n)i[n].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}; +/*! + * @kurkle/color v0.1.9 + * https://github.com/kurkle/color#readme + * (c) 2020 Jukka Kurkela + * Released under the MIT License + */const r={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},l="0123456789ABCDEF",c=t=>l[15&t],h=t=>l[(240&t)>>4]+l[15&t],d=t=>(240&t)>>4==(15&t);function u(t){var e=function(t){return d(t.r)&&d(t.g)&&d(t.b)&&d(t.a)}(t)?c:h;return t?"#"+e(t.r)+e(t.g)+e(t.b)+(t.a<255?e(t.a):""):t}function f(t){return t+.5|0}const g=(t,e,i)=>Math.max(Math.min(t,i),e);function p(t){return g(f(2.55*t),0,255)}function m(t){return g(f(255*t),0,255)}function x(t){return g(f(t/2.55)/100,0,1)}function b(t){return g(f(100*t),0,100)}const _=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const y=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function v(t,e,i){const n=e*Math.min(i,1-i),o=(e,o=(e+t/30)%12)=>i-n*Math.max(Math.min(o-3,9-o,1),-1);return[o(0),o(8),o(4)]}function w(t,e,i){const n=(n,o=(n+t/60)%6)=>i-i*e*Math.max(Math.min(o,4-o,1),0);return[n(5),n(3),n(1)]}function M(t,e,i){const n=v(t,1,.5);let o;for(e+i>1&&(o=1/(e+i),e*=o,i*=o),o=0;o<3;o++)n[o]*=1-e-i,n[o]+=e;return n}function k(t){const e=t.r/255,i=t.g/255,n=t.b/255,o=Math.max(e,i,n),s=Math.min(e,i,n),a=(o+s)/2;let r,l,c;return o!==s&&(c=o-s,l=a>.5?c/(2-o-s):c/(o+s),r=o===e?(i-n)/c+(i>16&255,s>>8&255,255&s]}return t}(),T.transparent=[0,0,0,0]);const e=T[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}function R(t,e,i){if(t){let n=k(t);n[e]=Math.max(0,Math.min(n[e]+n[e]*i,0===e?360:1)),n=P(n),t.r=n[0],t.g=n[1],t.b=n[2]}}function E(t,e){return t?Object.assign(e||{},t):t}function I(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=m(t[3]))):(e=E(t,{r:0,g:0,b:0,a:1})).a=m(e.a),e}function z(t){return"r"===t.charAt(0)?function(t){const e=_.exec(t);let i,n,o,s=255;if(e){if(e[7]!==i){const t=+e[7];s=255&(e[8]?p(t):255*t)}return i=+e[1],n=+e[3],o=+e[5],i=255&(e[2]?p(i):i),n=255&(e[4]?p(n):n),o=255&(e[6]?p(o):o),{r:i,g:n,b:o,a:s}}}(t):C(t)}class F{constructor(t){if(t instanceof F)return t;const e=typeof t;let i;var n,o,s;"object"===e?i=I(t):"string"===e&&(s=(n=t).length,"#"===n[0]&&(4===s||5===s?o={r:255&17*r[n[1]],g:255&17*r[n[2]],b:255&17*r[n[3]],a:5===s?17*r[n[4]]:255}:7!==s&&9!==s||(o={r:r[n[1]]<<4|r[n[2]],g:r[n[3]]<<4|r[n[4]],b:r[n[5]]<<4|r[n[6]],a:9===s?r[n[7]]<<4|r[n[8]]:255})),i=o||L(t)||z(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=E(this._rgb);return t&&(t.a=x(t.a)),t}set rgb(t){this._rgb=I(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${x(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):this._rgb;var t}hexString(){return this._valid?u(this._rgb):this._rgb}hslString(){return this._valid?function(t){if(!t)return;const e=k(t),i=e[0],n=b(e[1]),o=b(e[2]);return t.a<255?`hsla(${i}, ${n}%, ${o}%, ${x(t.a)})`:`hsl(${i}, ${n}%, ${o}%)`}(this._rgb):this._rgb}mix(t,e){const i=this;if(t){const n=i.rgb,o=t.rgb;let s;const a=e===s?.5:e,r=2*a-1,l=n.a-o.a,c=((r*l==-1?r:(r+l)/(1+r*l))+1)/2;s=1-c,n.r=255&c*n.r+s*o.r+.5,n.g=255&c*n.g+s*o.g+.5,n.b=255&c*n.b+s*o.b+.5,n.a=a*n.a+(1-a)*o.a,i.rgb=n}return i}clone(){return new F(this.rgb)}alpha(t){return this._rgb.a=m(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=f(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return R(this._rgb,2,t),this}darken(t){return R(this._rgb,2,-t),this}saturate(t){return R(this._rgb,1,t),this}desaturate(t){return R(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=k(t);i[0]=D(i[0]+e),i=P(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function V(t){return new F(t)}const B=t=>t instanceof CanvasGradient||t instanceof CanvasPattern;function W(t){return B(t)?t:V(t)}function H(t){return B(t)?t:V(t).saturate(.5).darken(.1).hexString()}function N(){}const j=function(){let t=0;return function(){return t++}}();function $(t){return null==t}function Y(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.substr(0,7)&&"Array]"===e.substr(-6)}function U(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}const X=t=>("number"==typeof t||t instanceof Number)&&isFinite(+t);function q(t,e){return X(t)?t:e}function K(t,e){return void 0===t?e:t}const G=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:t/e,Z=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function Q(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function J(t,e,i,n){let o,s,a;if(Y(t))if(s=t.length,n)for(o=s-1;o>=0;o--)e.call(i,t[o],o);else for(o=0;oi;)t=t[e.substr(i,n-i)],i=n+1,n=rt(e,i);return t}function ct(t){return t.charAt(0).toUpperCase()+t.slice(1)}const ht=t=>void 0!==t,dt=t=>"function"==typeof t,ut=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0},ft=Object.create(null),gt=Object.create(null);function pt(t,e){if(!e)return t;const i=e.split(".");for(let e=0,n=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>H(e.backgroundColor),this.hoverBorderColor=(t,e)=>H(e.borderColor),this.hoverColor=(t,e)=>H(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.describe(t)}set(t,e){return mt(this,t,e)}get(t){return pt(this,t)}describe(t,e){return mt(gt,t,e)}override(t,e){return mt(ft,t,e)}route(t,e,i,n){const o=pt(this,t),s=pt(this,i),a="_"+e;Object.defineProperties(o,{[a]:{value:o[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[a],e=s[n];return U(t)?Object.assign({},e,t):K(t,e)},set(t){this[a]=t}}})}}({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}});const bt=Math.PI,_t=2*bt,yt=_t+bt,vt=Number.POSITIVE_INFINITY,wt=bt/180,Mt=bt/2,kt=bt/4,St=2*bt/3,Pt=Math.log10,Dt=Math.sign;function Ct(t){const e=Math.pow(10,Math.floor(Pt(t))),i=t/e;return(i<=1?1:i<=2?2:i<=5?5:10)*e}function Ot(t){const e=[],i=Math.sqrt(t);let n;for(n=1;nt-e)).pop(),e}function At(t){return!isNaN(parseFloat(t))&&isFinite(t)}function Tt(t,e,i){return Math.abs(t-e)=t}function Rt(t,e,i){let n,o,s;for(n=0,o=t.length;nl&&cn&&(n=s),n}function Ut(t,e,i,n){let o=(n=n||{}).data=n.data||{},s=n.garbageCollect=n.garbageCollect||[];n.font!==e&&(o=n.data={},s=n.garbageCollect=[],n.font=e),t.save(),t.font=e;let a=0;const r=i.length;let l,c,h,d,u;for(l=0;li.length){for(l=0;l0&&t.stroke()}}function Gt(t,e,i){return i=i||.5,t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==s.strokeColor;let l,c;for(t.save(),s.translation&&t.translate(s.translation[0],s.translation[1]),$(s.rotation)||t.rotate(s.rotation),t.font=o.string,s.color&&(t.fillStyle=s.color),s.textAlign&&(t.textAlign=s.textAlign),s.textBaseline&&(t.textBaseline=s.textBaseline),l=0;lt[i]1;)n=s+o>>1,i(n)?s=n:o=n;return{lo:s,hi:o}}const oe=(t,e,i)=>ne(t,i,(n=>t[n][e]ne(t,i,(n=>t[n][e]>=i));function ae(t,e,i){let n=0,o=t.length;for(;nn&&t[o-1]>i;)o--;return n>0||o{const i="_onData"+ct(e),n=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const o=n.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),o}})})))}function ce(t,e){const i=t._chartjs;if(!i)return;const n=i.listeners,o=n.indexOf(e);-1!==o&&n.splice(o,1),n.length>0||(re.forEach((e=>{delete t[e]})),delete t._chartjs)}function he(t){const e=new Set;let i,n;for(i=0,n=t.length;i{o.push(t)})),o}function de(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function ue(t,e,i){let n;return"string"==typeof t?(n=parseInt(t,10),-1!==t.indexOf("%")&&(n=n/100*e.parentNode[i])):n=t,n}const fe=t=>window.getComputedStyle(t,null);function ge(t,e){return fe(t).getPropertyValue(e)}const pe=["top","right","bottom","left"];function me(t,e,i){const n={};i=i?"-"+i:"";for(let o=0;o<4;o++){const s=pe[o];n[s]=parseFloat(t[e+"-"+s+i])||0}return n.width=n.left+n.right,n.height=n.top+n.bottom,n}function xe(t,e){const{canvas:i,currentDevicePixelRatio:n}=e,o=fe(i),s="border-box"===o.boxSizing,a=me(o,"padding"),r=me(o,"border","width"),{x:l,y:c,box:h}=function(t,e){const i=t.native||t,n=i.touches,o=n&&n.length?n[0]:i,{offsetX:s,offsetY:a}=o;let r,l,c=!1;if(((t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot))(s,a,i.target))r=s,l=a;else{const t=e.getBoundingClientRect();r=o.clientX-t.left,l=o.clientY-t.top,c=!0}return{x:r,y:l,box:c}}(t,i),d=a.left+(h&&r.left),u=a.top+(h&&r.top);let{width:f,height:g}=e;return s&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/n),y:Math.round((c-u)/g*i.height/n)}}const be=t=>Math.round(10*t)/10;function _e(t,e,i,n){const o=fe(t),s=me(o,"margin"),a=ue(o.maxWidth,t,"clientWidth")||vt,r=ue(o.maxHeight,t,"clientHeight")||vt,l=function(t,e,i){let n,o;if(void 0===e||void 0===i){const s=de(t);if(s){const t=s.getBoundingClientRect(),a=fe(s),r=me(a,"border","width"),l=me(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,n=ue(a.maxWidth,s,"clientWidth"),o=ue(a.maxHeight,s,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:n||vt,maxHeight:o||vt}}(t,e,i);let{width:c,height:h}=l;if("content-box"===o.boxSizing){const t=me(o,"border","width"),e=me(o,"padding");c-=e.width+t.width,h-=e.height+t.height}return c=Math.max(0,c-s.width),h=Math.max(0,n?Math.floor(c/n):h-s.height),c=be(Math.min(c,a,l.maxWidth)),h=be(Math.min(h,r,l.maxHeight)),c&&!h&&(h=be(c/2)),{width:c,height:h}}function ye(t,e,i){const n=e||1,o=Math.floor(t.height*n),s=Math.floor(t.width*n);t.height=o/n,t.width=s/n;const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==n||a.height!==o||a.width!==s)&&(t.currentDevicePixelRatio=n,a.height=o,a.width=s,t.ctx.setTransform(n,0,0,n,0,0),!0)}const ve=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};window.addEventListener("test",null,e),window.removeEventListener("test",null,e)}catch(t){}return t}();function we(t,e){const i=ge(t,e),n=i&&i.match(/^(\d+)(\.\d+)?px$/);return n?+n[1]:void 0}function Me(t,e){return"native"in t?{x:t.x,y:t.y}:xe(t,e)}function ke(t,e,i,n){const{controller:o,data:s,_sorted:a}=t,r=o._cachedMeta.iScale;if(r&&e===r.axis&&a&&s.length){const t=r._reversePixels?se:oe;if(!n)return t(s,e,i);if(o._sharedOptions){const n=s[0],o="function"==typeof n.getRange&&n.getRange(e);if(o){const n=t(s,e,i-o),a=t(s,e,i+o);return{lo:n.lo,hi:a.hi}}}}return{lo:0,hi:s.length-1}}function Se(t,e,i,n,o){const s=t.getSortedVisibleDatasetMetas(),a=i[e];for(let t=0,i=s.length;t{t[r](o[a],n)&&s.push({element:t,datasetIndex:e,index:i}),t.inRange(o.x,o.y,n)&&(l=!0)})),i.intersect&&!l?[]:s}var Oe={modes:{index(t,e,i,n){const o=Me(e,t),s=i.axis||"x",a=i.intersect?Pe(t,o,s,n):De(t,o,s,!1,n),r=[];return a.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=a[0].index,i=t.data[e];i&&!i.skip&&r.push({element:i,datasetIndex:t.index,index:e})})),r):[]},dataset(t,e,i,n){const o=Me(e,t),s=i.axis||"xy";let a=i.intersect?Pe(t,o,s,n):De(t,o,s,!1,n);if(a.length>0){const e=a[0].datasetIndex,i=t.getDatasetMeta(e).data;a=[];for(let t=0;tPe(t,Me(e,t),i.axis||"xy",n),nearest:(t,e,i,n)=>De(t,Me(e,t),i.axis||"xy",i.intersect,n),x:(t,e,i,n)=>(i.axis="x",Ce(t,e,i,n)),y:(t,e,i,n)=>(i.axis="y",Ce(t,e,i,n))}};const Ae=new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/),Te=new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/);function Le(t,e){const i=(""+t).match(Ae);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}function Re(t,e){const i={},n=U(e),o=n?Object.keys(e):e,s=U(t)?n?i=>K(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of o)i[t]=+s(t)||0;return i}function Ee(t){return Re(t,{top:"y",right:"x",bottom:"y",left:"x"})}function Ie(t){return Re(t,["topLeft","topRight","bottomLeft","bottomRight"])}function ze(t){const e=Ee(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Fe(t,e){t=t||{},e=e||xt.font;let i=K(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let n=K(t.style,e.style);n&&!(""+n).match(Te)&&(console.warn('Invalid font style specified: "'+n+'"'),n="");const o={family:K(t.family,e.family),lineHeight:Le(K(t.lineHeight,e.lineHeight),i),size:i,style:n,weight:K(t.weight,e.weight),string:""};return o.string=$t(o),o}function Ve(t,e,i,n){let o,s,a,r=!0;for(o=0,s=t.length;ot.pos===e))}function Ne(t,e){return t.filter((t=>-1===We.indexOf(t.pos)&&t.box.axis===e))}function je(t,e){return t.sort(((t,i)=>{const n=e?i:t,o=e?t:i;return n.weight===o.weight?n.index-o.index:n.weight-o.weight}))}function $e(t,e,i,n){return Math.max(t[i],e[i])+Math.max(t[n],e[n])}function Ye(t,e){t.top=Math.max(t.top,e.top),t.left=Math.max(t.left,e.left),t.bottom=Math.max(t.bottom,e.bottom),t.right=Math.max(t.right,e.right)}function Ue(t,e,i){const n=i.box,o=t.maxPadding;U(i.pos)||(i.size&&(t[i.pos]-=i.size),i.size=i.horizontal?n.height:n.width,t[i.pos]+=i.size),n.getPadding&&Ye(o,n.getPadding());const s=Math.max(0,e.outerWidth-$e(o,t,"left","right")),a=Math.max(0,e.outerHeight-$e(o,t,"top","bottom")),r=s!==t.w,l=a!==t.h;return t.w=s,t.h=a,i.horizontal?{same:r,other:l}:{same:l,other:r}}function Xe(t,e){const i=e.maxPadding;function n(t){const n={left:0,top:0,right:0,bottom:0};return t.forEach((t=>{n[t]=Math.max(e[t],i[t])})),n}return n(t?["left","right"]:["top","bottom"])}function qe(t,e,i){const n=[];let o,s,a,r,l,c;for(o=0,s=t.length,l=0;ot.box.fullSize)),!0),n=je(He(e,"left"),!0),o=je(He(e,"right")),s=je(He(e,"top"),!0),a=je(He(e,"bottom")),r=Ne(e,"x"),l=Ne(e,"y");return{fullSize:i,leftAndTop:n.concat(s),rightAndBottom:o.concat(l).concat(a).concat(r),chartArea:He(e,"chartArea"),vertical:n.concat(o).concat(l),horizontal:s.concat(a).concat(r)}}(t.boxes),l=r.vertical,c=r.horizontal;J(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const h=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:o,availableWidth:s,availableHeight:a,vBoxMaxWidth:s/2/h,hBoxMaxHeight:a/2}),u=Object.assign({},o);Ye(u,ze(n));const f=Object.assign({maxPadding:u,w:s,h:a,x:o.left,y:o.top},o);!function(t,e){let i,n,o;for(i=0,n=t.length;i{const i=e.box;Object.assign(i,t.chartArea),i.update(f.w,f.h)}))}};class Ze{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,n){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,n?Math.floor(e/n):i)}}isAttached(t){return!0}}class Qe extends Ze{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}}const Je={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},ti=t=>null===t||""===t;const ei=!!ve&&{passive:!0};function ii(t,e,i){t.canvas.removeEventListener(e,i,ei)}function ni(t,e,i){const n=t.canvas,o=n&&de(n)||n,s=new MutationObserver((t=>{const e=de(o);t.forEach((t=>{for(let n=0;n{t.forEach((t=>{for(let e=0;e{i.currentDevicePixelRatio!==t&&e()})))}function li(t,i,n){const o=t.canvas,s=o&&de(o);if(!s)return;const a=e(((t,e)=>{const i=s.clientWidth;n(t,e),i{const e=t[0],i=e.contentRect.width,n=e.contentRect.height;0===i&&0===n||a(i,n)}));return r.observe(s),function(t,e){si.size||window.addEventListener("resize",ri),si.set(t,e)}(t,a),r}function ci(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){si.delete(t),si.size||window.removeEventListener("resize",ri)}(t)}function hi(t,i,n){const o=t.canvas,s=e((e=>{null!==t.ctx&&n(function(t,e){const i=Je[t.type]||t.type,{x:n,y:o}=xe(t,e);return{type:i,chart:e,native:t,x:void 0!==n?n:null,y:void 0!==o?o:null}}(e,t))}),t,(t=>{const e=t[0];return[e,e.offsetX,e.offsetY]}));return function(t,e,i){t.addEventListener(e,i,ei)}(o,i,s),s}class di extends Ze{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,n=t.getAttribute("height"),o=t.getAttribute("width");if(t.$chartjs={initial:{height:n,width:o,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",ti(o)){const e=we(t,"width");void 0!==e&&(t.width=e)}if(ti(n))if(""===t.style.height)t.height=t.width/(e||2);else{const e=we(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e.$chartjs)return!1;const i=e.$chartjs.initial;["height","width"].forEach((t=>{const n=i[t];$(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e.$chartjs,!0}addEventListener(t,e,i){this.removeEventListener(t,e);const n=t.$proxies||(t.$proxies={}),o={attach:ni,detach:oi,resize:li}[e]||hi;n[e]=o(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),n=i[e];if(!n)return;({attach:ci,detach:ci,resize:ci}[e]||ii)(t,e,n),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,n){return _e(t,e,i,n)}isAttached(t){const e=de(t);return!(!e||!de(e))}}var ui=Object.freeze({__proto__:null,BasePlatform:Ze,BasicPlatform:Qe,DomPlatform:di});const fi=t=>0===t||1===t,gi=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*_t/i),pi=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*_t/i)+1,mi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*Mt),easeOutSine:t=>Math.sin(t*Mt),easeInOutSine:t=>-.5*(Math.cos(bt*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>fi(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>fi(t)?t:gi(t,.075,.3),easeOutElastic:t=>fi(t)?t:pi(t,.075,.3),easeInOutElastic(t){const e=.1125;return fi(t)?t:t<.5?.5*gi(2*t,e,.45):.5+.5*pi(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-mi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*mi.easeInBounce(2*t):.5*mi.easeOutBounce(2*t-1)+.5},xi="transparent",bi={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const n=W(t||xi),o=n.valid&&W(e||xi);return o&&o.valid?o.mix(n,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class _i{constructor(t,e,i,n){const o=e[i];n=Ve([t.to,n,o,t.from]);const s=Ve([t.from,o,n]);this._active=!0,this._fn=t.fn||bi[t.type||typeof s],this._easing=mi[t.easing]||mi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=s,this._to=n,this._promises=void 0}active(){return this._active}update(t,e,i){const n=this;if(n._active){n._notify(!1);const o=n._target[n._prop],s=i-n._start,a=n._duration-s;n._start=i,n._duration=Math.floor(Math.max(a,t.duration)),n._total+=s,n._loop=!!t.loop,n._to=Ve([t.to,e,o,t.from]),n._from=Ve([t.from,o,e])}}cancel(){const t=this;t._active&&(t.tick(Date.now()),t._active=!1,t._notify(!1))}tick(t){const e=this,i=t-e._start,n=e._duration,o=e._prop,s=e._from,a=e._loop,r=e._to;let l;if(e._active=s!==r&&(a||i1?2-l:l,l=e._easing(Math.min(1,Math.max(0,l))),e._target[o]=e._fn(s,r,l))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),xt.set("animations",{colors:{type:"color",properties:["color","borderColor","backgroundColor"]},numbers:{type:"number",properties:["x","y","borderWidth","radius","tension"]}}),xt.describe("animations",{_fallback:"animation"}),xt.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}});class vi{constructor(t,e){this._chart=t,this._properties=new Map,this.configure(e)}configure(t){if(!U(t))return;const e=this._properties;Object.getOwnPropertyNames(t).forEach((i=>{const n=t[i];if(!U(n))return;const o={};for(const t of yi)o[t]=n[t];(Y(n.properties)&&n.properties||[i]).forEach((t=>{t!==i&&e.has(t)||e.set(t,o)}))}))}_animateOptions(t,e){const i=e.options,n=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!n)return[];const o=this._createAnimations(n,i);return i.$shared&&function(t,e){const i=[],n=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),o}_createAnimations(t,e){const i=this._properties,n=[],o=t.$animations||(t.$animations={}),s=Object.keys(e),a=Date.now();let r;for(r=s.length-1;r>=0;--r){const l=s[r];if("$"===l.charAt(0))continue;if("options"===l){n.push(...this._animateOptions(t,e));continue}const c=e[l];let h=o[l];const d=i.get(l);if(h){if(d&&h.active()){h.update(d,c,a);continue}h.cancel()}d&&d.duration?(o[l]=h=new _i(d,t,l,c),n.push(h)):t[l]=c}return n}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(a.add(this._chart,i),!0):void 0}}function wi(t,e){const i=t&&t.options||{},n=i.reverse,o=void 0===i.min?e:0,s=void 0===i.max?e:0;return{start:n?s:o,end:n?o:s}}function Mi(t,e){const i=[],n=t._getSortedDatasetMetas(e);let o,s;for(o=0,s=n.length;o0||!i&&e<0)return n.index}return null}function Ci(t,e){const{chart:i,_cachedMeta:n}=t,o=i._stacks||(i._stacks={}),{iScale:s,vScale:a,index:r}=n,l=s.axis,c=a.axis,h=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(s,a,n),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Ai(t,e){e=e||t._parsed;for(const i of e){const e=i._stacks;if(!e||void 0===e[t.vScale.id]||void 0===e[t.vScale.id][t.index])return;delete e[t.vScale.id][t.index]}}const Ti=t=>"reset"===t||"none"===t,Li=(t,e)=>e?t:Object.assign({},t);class Ri{constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.$context=void 0,this._syncList=[],this.initialize()}initialize(){const t=this,e=t._cachedMeta;t.configure(),t.linkScales(),e._stacked=Si(e.vScale,e),t.addElements()}updateIndex(t){this.index!==t&&Ai(this._cachedMeta),this.index=t}linkScales(){const t=this,e=t.chart,i=t._cachedMeta,n=t.getDataset(),o=(t,e,i,n)=>"x"===t?e:"r"===t?n:i,s=i.xAxisID=K(n.xAxisID,Oi(e,"x")),a=i.yAxisID=K(n.yAxisID,Oi(e,"y")),r=i.rAxisID=K(n.rAxisID,Oi(e,"r")),l=i.indexAxis,c=i.iAxisID=o(l,s,a,r),h=i.vAxisID=o(l,a,s,r);i.xScale=t.getScaleForId(s),i.yScale=t.getScaleForId(a),i.rScale=t.getScaleForId(r),i.iScale=t.getScaleForId(c),i.vScale=t.getScaleForId(h)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&ce(this._data,this),t._stacked&&Ai(t)}_dataCheck(){const t=this,e=t.getDataset(),i=e.data||(e.data=[]),n=t._data;if(U(i))t._data=function(t){const e=Object.keys(t),i=new Array(e.length);let n,o,s;for(n=0,o=e.length;n0&&n._parsed[t-1];if(!1===i._parsing)n._parsed=o,n._sorted=!0,h=o;else{h=Y(o[t])?i.parseArrayData(n,o,t,e):U(o[t])?i.parseObjectData(n,o,t,e):i.parsePrimitiveData(n,o,t,e);const s=()=>null===c[r]||u&&c[r]p||d=0;--u)if(!m()){i.updateRangeFromParsed(c,t,g,l);break}return c}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let n,o,s;for(n=0,o=e.length;n=0&&tn.getContext(i,o)),d);return g.$shared&&(g.$shared=l,s[a]=Object.freeze(Li(g,l))),g}_resolveAnimations(t,e,i){const n=this,o=n.chart,s=n._cachedDataOpts,a=`animation-${e}`,r=s[a];if(r)return r;let l;if(!1!==o.options.animation){const o=n.chart.config,s=o.datasetAnimationScopeKeys(n._type,e),a=o.getOptionScopes(n.getDataset(),s);l=o.createResolver(a,n.getContext(t,i,e))}const c=new vi(o,l&&l.animations);return l&&l._cacheable&&(s[a]=Object.freeze(c)),c}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Ti(t)||this.chart._animationsDisabled}updateElement(t,e,i,n){Ti(n)?Object.assign(t,i):this._resolveAnimations(e,n).update(t,i)}updateSharedOptions(t,e,i){t&&!Ti(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,n){t.active=n;const o=this.getStyle(e,n);this._resolveAnimations(e,i,n).update(t,{options:!n&&this.getSharedOptions(o)||o})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this,i=e._data,n=e._cachedMeta.data;for(const[t,i,n]of e._syncList)e[t](i,n);e._syncList=[];const o=n.length,s=i.length,a=Math.min(s,o);s>o?e._insertElements(o,s-o,t):s{for(t.length+=e,r=t.length-1;r>=a;r--)t[r]=t[r-e]};for(l(s),r=t;r{o[t]=n[t]&&n[t].active()?n[t]._to:i[t]})),o}}Ei.defaults={},Ei.defaultRoutes=void 0;const Ii=new Map;function zi(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let n=Ii.get(i);return n||(n=new Intl.NumberFormat(t,e),Ii.set(i,n)),n}(e,i).format(t)}const Fi={values:t=>Y(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const n=this.chart.options.locale;let o,s=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(o="scientific"),s=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=Pt(Math.abs(s)),r=Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:o,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),zi(t,n,l)},logarithmic(t,e,i){if(0===t)return"0";const n=t/Math.pow(10,Math.floor(Pt(t)));return 1===n||2===n||5===n?Fi.numeric.call(this,t,e,i):""}};var Vi={formatters:Fi};function Bi(t,e){const i=t.options.ticks,n=i.maxTicksLimit||function(t){const e=t.options.offset,i=t._tickSize(),n=t._length/i+(e?0:1),o=t._maxLength/i;return Math.floor(Math.min(n,o))}(t),o=i.major.enabled?function(t){const e=[];let i,n;for(i=0,n=t.length;in)return function(t,e,i,n){let o,s=0,a=i[0];for(n=Math.ceil(n),o=0;oo)return e}return Math.max(o,1)}(o,e,n);if(s>0){let t,i;const n=s>1?Math.round((r-a)/(s-1)):null;for(Wi(e,l,c,$(n)?0:a-n,a),t=0,i=s-1;te.lineWidth,tickColor:(t,e)=>e.color,offset:!1,borderDash:[],borderDashOffset:0,borderWidth:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:Vi.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),xt.route("scale.ticks","color","","color"),xt.route("scale.grid","color","","borderColor"),xt.route("scale.grid","borderColor","","borderColor"),xt.route("scale.title","color","","color"),xt.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t}),xt.describe("scales",{_fallback:"scale"});const Hi=(t,e,i)=>"top"===e||"left"===e?t[e]+i:t[e]-i;function Ni(t,e){const i=[],n=t.length/e,o=t.length;let s=0;for(;sa+r)))return c}function $i(t){return t.drawTicks?t.tickLength:0}function Yi(t,e){if(!t.display)return 0;const i=Fe(t.font,e),n=ze(t.padding);return(Y(t.text)?t.text.length:1)*i.lineHeight+n.height}function Ui(t,e,i){let o=n(t);return(i&&"right"!==e||!i&&"right"===e)&&(o=(t=>"left"===t?"right":"right"===t?"left":t)(o)),o}class Xi extends Ei{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){const e=this;e.options=t.setContext(e.getContext()),e.axis=t.axis,e._userMin=e.parse(t.min),e._userMax=e.parse(t.max),e._suggestedMin=e.parse(t.suggestedMin),e._suggestedMax=e.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:n}=this;return t=q(t,Number.POSITIVE_INFINITY),e=q(e,Number.NEGATIVE_INFINITY),i=q(i,Number.POSITIVE_INFINITY),n=q(n,Number.NEGATIVE_INFINITY),{min:q(t,i),max:q(e,n),minDefined:X(t),maxDefined:X(e)}}getMinMax(t){const e=this;let i,{min:n,max:o,minDefined:s,maxDefined:a}=e.getUserBounds();if(s&&a)return{min:n,max:o};const r=e.getMatchingVisibleMetas();for(let l=0,c=r.length;l=s||n<=1||!t.isHorizontal())return void(t.labelRotation=o);const h=t._getLabelSizes(),d=h.widest.width,u=h.highest.height,f=Nt(t.chart.width-d,0,t.maxWidth);a=e.offset?t.maxWidth/n:f/(n-1),d+6>a&&(a=f/(n-(e.offset?.5:1)),r=t.maxHeight-$i(e.grid)-i.padding-Yi(e.title,t.chart.options.font),l=Math.sqrt(d*d+u*u),c=It(Math.min(Math.asin(Math.min((h.highest.height+6)/a,1)),Math.asin(Math.min(r/l,1))-Math.asin(u/l))),c=Math.max(o,Math.min(s,c))),t.labelRotation=c}afterCalculateLabelRotation(){Q(this.options.afterCalculateLabelRotation,[this])}beforeFit(){Q(this.options.beforeFit,[this])}fit(){const t=this,e={width:0,height:0},{chart:i,options:{ticks:n,title:o,grid:s}}=t,a=t._isVisible(),r=t.isHorizontal();if(a){const a=Yi(o,i.options.font);if(r?(e.width=t.maxWidth,e.height=$i(s)+a):(e.height=t.maxHeight,e.width=$i(s)+a),n.display&&t.ticks.length){const{first:i,last:o,widest:s,highest:a}=t._getLabelSizes(),l=2*n.padding,c=Et(t.labelRotation),h=Math.cos(c),d=Math.sin(c);if(r){const i=n.mirror?0:d*s.width+h*a.height;e.height=Math.min(t.maxHeight,e.height+i+l)}else{const i=n.mirror?0:h*s.width+d*a.height;e.width=Math.min(t.maxWidth,e.width+i+l)}t._calculatePadding(i,o,d,h)}}t._handleMargins(),r?(t.width=t._length=i.width-t._margins.left-t._margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=i.height-t._margins.top-t._margins.bottom)}_calculatePadding(t,e,i,n){const o=this,{ticks:{align:s,padding:a},position:r}=o.options,l=0!==o.labelRotation,c="top"!==r&&"x"===o.axis;if(o.isHorizontal()){const r=o.getPixelForTick(0)-o.left,h=o.right-o.getPixelForTick(o.ticks.length-1);let d=0,u=0;l?c?(d=n*t.width,u=i*e.height):(d=i*t.height,u=n*e.width):"start"===s?u=e.width:"end"===s?d=t.width:(d=t.width/2,u=e.width/2),o.paddingLeft=Math.max((d-r+a)*o.width/(o.width-r),0),o.paddingRight=Math.max((u-h+a)*o.width/(o.width-h),0)}else{let i=e.height/2,n=t.height/2;"start"===s?(i=0,n=t.height):"end"===s&&(i=e.height,n=0),o.paddingTop=i+a,o.paddingBottom=n+a}}_handleMargins(){const t=this;t._margins&&(t._margins.left=Math.max(t.paddingLeft,t._margins.left),t._margins.top=Math.max(t.paddingTop,t._margins.top),t._margins.right=Math.max(t.paddingRight,t._margins.right),t._margins.bottom=Math.max(t.paddingBottom,t._margins.bottom))}afterFit(){Q(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){const e=this;e.beforeTickToLabelConversion(),e.generateTickLabels(t),e.afterTickToLabelConversion()}_getLabelSizes(){const t=this;let e=t._labelSizes;if(!e){const i=t.options.ticks.sampleSize;let n=t.ticks;i{const i=t.gc,n=i.length/2;let o;if(n>e){for(o=0;o({width:o[t]||0,height:s[t]||0});return{first:v(0),last:v(e-1),widest:v(_),highest:v(y),widths:o,heights:s}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){const e=this;e._reversePixels&&(t=1-t);const i=e._startPixel+t*e._length;return jt(e._alignToPixels?Xt(e.chart,i,0):i)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this,i=e.ticks||[];if(t>=0&&tr*o?r/n:l/o:l*o0}_computeGridLineItems(t){const e=this,i=e.axis,n=e.chart,o=e.options,{grid:s,position:a}=o,r=s.offset,l=e.isHorizontal(),c=e.ticks.length+(r?1:0),h=$i(s),d=[],u=s.setContext(e.getContext()),f=u.drawBorder?u.borderWidth:0,g=f/2,p=function(t){return Xt(n,t,f)};let m,x,b,_,y,v,w,M,k,S,P,D;if("top"===a)m=p(e.bottom),v=e.bottom-h,M=m-g,S=p(t.top)+g,D=t.bottom;else if("bottom"===a)m=p(e.top),S=t.top,D=p(t.bottom)-g,v=m+g,M=e.top+h;else if("left"===a)m=p(e.right),y=e.right-h,w=m-g,k=p(t.left)+g,P=t.right;else if("right"===a)m=p(e.left),k=t.left,P=p(t.right)-g,y=m+g,w=e.left+h;else if("x"===i){if("center"===a)m=p((t.top+t.bottom)/2+.5);else if(U(a)){const t=Object.keys(a)[0],i=a[t];m=p(e.chart.scales[t].getPixelForValue(i))}S=t.top,D=t.bottom,v=m+g,M=v+h}else if("y"===i){if("center"===a)m=p((t.left+t.right)/2);else if(U(a)){const t=Object.keys(a)[0],i=a[t];m=p(e.chart.scales[t].getPixelForValue(i))}y=m-g,w=y-h,k=t.left,P=t.right}for(x=0;xe.value===t));if(n>=0){return i.setContext(e.getContext(n)).lineWidth}return 0}drawGrid(t){const e=this,i=e.options.grid,n=e.ctx,o=e._gridLineItems||(e._gridLineItems=e._computeGridLineItems(t));let s,a;const r=(t,e,i)=>{i.width&&i.color&&(n.save(),n.lineWidth=i.width,n.strokeStyle=i.color,n.setLineDash(i.borderDash||[]),n.lineDashOffset=i.borderDashOffset,n.beginPath(),n.moveTo(t.x,t.y),n.lineTo(e.x,e.y),n.stroke(),n.restore())};if(i.display)for(s=0,a=o.length;st[0])){ht(n)||(n=an("_fallback",t));const s={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:i,_fallback:n,_getTarget:o,override:o=>qi([o,...t],e,i,n)};return new Proxy(s,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,n)=>Ji(i,n,(()=>function(t,e,i,n){let o;for(const s of e)if(o=an(Zi(s,t),i),ht(o))return Qi(t,o)?on(i,n,t,o):o}(n,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>rn(t).includes(e),ownKeys:t=>rn(t),set:(t,e,i)=>((t._storage||(t._storage=o()))[e]=i,delete t[e],delete t._keys,!0)})}function Ki(t,e,i,n){const o={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Gi(t,n),setContext:e=>Ki(t,e,i,n),override:o=>Ki(t.override(o),e,i,n)};return new Proxy(o,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>Ji(t,e,(()=>function(t,e,i){const{_proxy:n,_context:o,_subProxy:s,_descriptors:a}=t;let r=n[e];dt(r)&&a.isScriptable(e)&&(r=function(t,e,i,n){const{_proxy:o,_context:s,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+[...r].join("->")+"->"+t);r.add(t),e=e(s,a||n),r.delete(t),U(e)&&(e=on(o._scopes,o,t,e));return e}(e,r,t,i));Y(r)&&r.length&&(r=function(t,e,i,n){const{_proxy:o,_context:s,_subProxy:a,_descriptors:r}=i;if(ht(s.index)&&n(t))e=e[s.index%e.length];else if(U(e[0])){const i=e,n=o._scopes.filter((t=>t!==i));e=[];for(const l of i){const i=on(n,o,t,l);e.push(Ki(i,s,a&&a[t],r))}}return e}(e,r,t,a.isIndexable));Qi(e,r)&&(r=Ki(r,o,s&&s[e],a));return r}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,n)=>(t[i]=n,delete e[i],!0)})}function Gi(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:n=e.indexable,_allKeys:o=e.allKeys}=t;return{allKeys:o,scriptable:i,indexable:n,isScriptable:dt(i)?i:()=>i,isIndexable:dt(n)?n:()=>n}}const Zi=(t,e)=>t?t+ct(e):e,Qi=(t,e)=>U(e)&&"adapters"!==t;function Ji(t,e,i){let n=t[e];return ht(n)||(n=i(),ht(n)&&(t[e]=n)),n}function tn(t,e,i){return dt(t)?t(e,i):t}const en=(t,e)=>!0===t?e:"string"==typeof t?lt(e,t):void 0;function nn(t,e,i,n){for(const o of e){const e=en(i,o);if(e){t.add(e);const o=tn(e._fallback,i,e);if(ht(o)&&o!==i&&o!==n)return o}else if(!1===e&&ht(n)&&i!==n)return null}return!1}function on(t,e,i,n){const o=e._rootScopes,s=tn(e._fallback,i,n),a=[...t,...o],r=new Set;r.add(n);let l=sn(r,a,i,s||i);return null!==l&&((!ht(s)||s===i||(l=sn(r,a,s,l),null!==l))&&qi([...r],[""],o,s,(()=>function(t,e,i){const n=t._getTarget();e in n||(n[e]={});const o=n[e];if(Y(o)&&U(i))return i;return o}(e,i,n))))}function sn(t,e,i,n){for(;i;)i=nn(t,e,i,n);return i}function an(t,e){for(const i of e){if(!i)continue;const e=i[t];if(ht(e))return e}}function rn(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return[...e]}(t._scopes)),e}const ln=Number.EPSILON||1e-14,cn=(t,e)=>e"x"===t?"y":"x";function dn(t,e,i,n){const o=t.skip?e:t,s=e,a=i.skip?e:i,r=Vt(s,o),l=Vt(a,s);let c=r/(r+l),h=l/(r+l);c=isNaN(c)?0:c,h=isNaN(h)?0:h;const d=n*c,u=n*h;return{previous:{x:s.x-d*(a.x-o.x),y:s.y-d*(a.y-o.y)},next:{x:s.x+u*(a.x-o.x),y:s.y+u*(a.y-o.y)}}}function un(t,e="x"){const i=hn(e),n=t.length,o=Array(n).fill(0),s=Array(n);let a,r,l,c=cn(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)un(t,o);else{let i=n?t[t.length-1]:t[0];for(s=0,a=t.length;s0?e.y:t.y}}function xn(t,e,i,n){const o={x:t.cp2x,y:t.cp2y},s={x:e.cp1x,y:e.cp1y},a=pn(t,o,i),r=pn(o,s,i),l=pn(s,e,i),c=pn(a,r,i),h=pn(r,l,i);return pn(c,h,i)}function bn(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function _n(t,e){let i,n;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,n=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=n)}function yn(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function vn(t){return"angle"===t?{between:Ht,compare:Bt,normalize:Wt}:{between:(t,e,i)=>t>=Math.min(e,i)&&t<=Math.max(i,e),compare:(t,e)=>t-e,normalize:t=>t}}function wn({start:t,end:e,count:i,loop:n,style:o}){return{start:t%i,end:e%i,loop:n&&(e-t+1)%i==0,style:o}}function Mn(t,e,i){if(!i)return[t];const{property:n,start:o,end:s}=i,a=e.length,{compare:r,between:l,normalize:c}=vn(n),{start:h,end:d,loop:u,style:f}=function(t,e,i){const{property:n,start:o,end:s}=i,{between:a,normalize:r}=vn(n),l=e.length;let c,h,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,c=0,h=l;cb||l(o,x,p)&&0!==r(o,x),v=()=>!b||0===r(s,p)||l(s,x,p);for(let t=h,i=h;t<=d;++t)m=e[t%a],m.skip||(p=c(m[n]),p!==x&&(b=l(p,o,s),null===_&&y()&&(_=0===r(p,o)?t:i),null!==_&&v()&&(g.push(wn({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,x=p));return null!==_&&g.push(wn({start:_,end:d,loop:u,count:a,style:f})),g}function kn(t,e){const i=[],n=t.segments;for(let o=0;oo&&t[s%e].skip;)s--;return s%=e,{start:o,end:s}}(i,o,s,n);if(!0===n)return Pn([{start:a,end:r,loop:s}],i,e);return Pn(function(t,e,i,n){const o=t.length,s=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%o];i.skip||i.stop?l.skip||(n=!1,s.push({start:e%o,end:(a-1)%o,loop:n}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&s.push({start:e%o,end:r%o,loop:n}),s}(i,a,r{const n=i.split("."),o=n.pop(),s=[t].concat(n).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");xt.route(s,o,l,r)}))}(e,t.defaultRoutes);t.descriptors&&xt.describe(e,t.descriptors)}(t,a,n),e.override&&xt.override(t.id,t.overrides)),a}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,n=this.scope;i in e&&delete e[i],n&&i in xt[n]&&(delete xt[n][i],this.override&&delete ft[i])}}var Tn=new class{constructor(){this.controllers=new An(Ri,"datasets",!0),this.elements=new An(Ei,"elements"),this.plugins=new An(Object,"plugins"),this.scales=new An(Xi,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){const n=this;[...e].forEach((e=>{const o=i||n._getRegistryForType(e);i||o.isForType(e)||o===n.plugins&&e.id?n._exec(t,o,e):J(e,(e=>{const o=i||n._getRegistryForType(e);n._exec(t,o,e)}))}))}_exec(t,e,i){const n=ct(t);Q(i["before"+n],[],i),e[t](i),Q(i["after"+n],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(n(e,i),t,"stop"),this._notify(n(i,e),t,"start")}}function Rn(t,e){return e||!1!==t?!0===t?{}:t:null}function En(t,e,i,n){const o=t.pluginScopeKeys(e),s=t.getOptionScopes(i,o);return t.createResolver(s,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function In(t,e){const i=xt.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function zn(t,e){return"x"===t||"y"===t?t:e.axis||("top"===(i=e.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.charAt(0).toLowerCase();var i}function Fn(t){const e=t.options||(t.options={});e.plugins=K(e.plugins,{}),e.scales=function(t,e){const i=ft[t.type]||{scales:{}},n=e.scales||{},o=In(t.type,e),s=Object.create(null),a=Object.create(null);return Object.keys(n).forEach((t=>{const e=n[t],r=zn(t,e),l=function(t,e){return t===e?"_index_":"_value_"}(r,o),c=i.scales||{};s[r]=s[r]||t,a[t]=st(Object.create(null),[{axis:r},e,c[r],c[l]])})),t.data.datasets.forEach((i=>{const o=i.type||t.type,r=i.indexAxis||In(o,e),l=(ft[o]||{}).scales||{};Object.keys(l).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,r),o=i[e+"AxisID"]||s[e]||e;a[o]=a[o]||Object.create(null),st(a[o],[{axis:e},n[o],l[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];st(e,[xt.scales[e.type],xt.scale])})),a}(t,e)}function Vn(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const Bn=new Map,Wn=new Set;function Hn(t,e){let i=Bn.get(t);return i||(i=e(),Bn.set(t,i),Wn.add(i)),i}const Nn=(t,e,i)=>{const n=lt(e,i);void 0!==n&&t.add(n)};class jn{constructor(t){this._config=function(t){return(t=t||{}).data=Vn(t.data),Fn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=Vn(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),Fn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return Hn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return Hn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return Hn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return Hn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let n=i.get(t);return n&&!e||(n=new Map,i.set(t,n)),n}getOptionScopes(t,e,i){const{options:n,type:o}=this,s=this._cachedScopes(t,i),a=s.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>Nn(r,t,e)))),e.forEach((t=>Nn(r,n,t))),e.forEach((t=>Nn(r,ft[o]||{},t))),e.forEach((t=>Nn(r,xt,t))),e.forEach((t=>Nn(r,gt,t)))}));const l=[...r];return Wn.has(e)&&s.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,ft[e]||{},xt.datasets[e]||{},{type:e},xt,gt]}resolveNamedOptions(t,e,i,n=[""]){const o={$shared:!0},{resolver:s,subPrefixes:a}=$n(this._resolverCache,t,n);let r=s;if(function(t,e){const{isScriptable:i,isIndexable:n}=Gi(t);for(const o of e)if(i(o)&&dt(t[o])||n(o)&&Y(t[o]))return!0;return!1}(s,e)){o.$shared=!1;r=Ki(s,i=dt(i)?i():i,this.createResolver(t,i,a))}for(const t of e)o[t]=r[t];return o}createResolver(t,e,i=[""],n){const{resolver:o}=$n(this._resolverCache,t,i);return U(e)?Ki(o,e,void 0,n):o}}function $n(t,e,i){let n=t.get(e);n||(n=new Map,t.set(e,n));const o=i.join();let s=n.get(o);if(!s){s={resolver:qi(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},n.set(o,s)}return s}const Yn=["top","bottom","left","right","chartArea"];function Un(t,e){return"top"===t||"bottom"===t||-1===Yn.indexOf(t)&&"x"===e}function Xn(t,e){return function(i,n){return i[t]===n[t]?i[e]-n[e]:i[t]-n[t]}}function qn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),Q(i&&i.onComplete,[t],e)}function Kn(t){const e=t.chart,i=e.options.animation;Q(i&&i.onProgress,[t],e)}function Gn(){return"undefined"!=typeof window&&"undefined"!=typeof document}function Zn(t){return Gn()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Qn={},Jn=t=>{const e=Zn(t);return Object.values(Qn).filter((t=>t.canvas===e)).pop()};class to{constructor(t,e){const n=this;this.config=e=new jn(e);const o=Zn(t),s=Jn(o);if(s)throw new Error("Canvas is already in use. Chart with ID '"+s.id+"' must be destroyed before the canvas can be reused.");const r=e.createResolver(e.chartOptionScopes(),n.getContext());this.platform=n._initializePlatform(o,e);const l=n.platform.acquireContext(o,r.aspectRatio),c=l&&l.canvas,h=c&&c.height,d=c&&c.width;this.id=j(),this.ctx=l,this.canvas=c,this.width=d,this.height=h,this._options=r,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this.scale=void 0,this._plugins=new Ln,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=i((()=>this.update("resize")),r.resizeDelay||0),Qn[n.id]=n,l&&c?(a.listen(n,"complete",qn),a.listen(n,"progress",Kn),n._initialize(),n.attached&&n.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return $(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}_initialize(){const t=this;return t.notifyPlugins("beforeInit"),t.options.responsive?t.resize():ye(t,t.options.devicePixelRatio),t.bindEvents(),t.notifyPlugins("afterInit"),t}_initializePlatform(t,e){return e.platform?new e.platform:!Gn()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?new Qe:new di}clear(){return qt(this.canvas,this.ctx),this}stop(){return a.stop(this),this}resize(t,e){a.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this,n=i.options,o=i.canvas,s=n.maintainAspectRatio&&i.aspectRatio,a=i.platform.getMaximumSize(o,t,e,s),r=n.devicePixelRatio||i.platform.getDevicePixelRatio();i.width=a.width,i.height=a.height,i._aspectRatio=i.aspectRatio,ye(i,r,!0)&&(i.notifyPlugins("resize",{size:a}),Q(n.onResize,[i,a],i),i.attached&&i._doResize()&&i.render())}ensureScalesHaveIDs(){J(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this,e=t.options,i=e.scales,n=t.scales,o=Object.keys(n).reduce(((t,e)=>(t[e]=!1,t)),{});let s=[];i&&(s=s.concat(Object.keys(i).map((t=>{const e=i[t],n=zn(t,e),o="r"===n,s="x"===n;return{options:e,dposition:o?"chartArea":s?"bottom":"left",dtype:o?"radialLinear":s?"category":"linear"}})))),J(s,(i=>{const s=i.options,a=s.id,r=zn(a,s),l=K(s.type,i.dtype);void 0!==s.position&&Un(s.position,r)===Un(i.dposition)||(s.position=i.dposition),o[a]=!0;let c=null;if(a in n&&n[a].type===l)c=n[a];else{c=new(Tn.getScale(l))({id:a,type:l,ctx:t.ctx,chart:t}),n[c.id]=c}c.init(s,e)})),J(o,((t,e)=>{t||delete n[e]})),J(n,(e=>{Ge.configure(t,e,e.options),Ge.addBox(t,e)}))}_updateMetasetIndex(t,e){const i=this._metasets,n=t.index;n!==e&&(i[n]=i[e],i[e]=t,t.index=e)}_updateMetasets(){const t=this,e=t._metasets,i=t.data.datasets.length,n=e.length;if(n>i){for(let e=i;ei.length&&delete t._stacks,e.forEach(((e,n)=>{0===i.filter((t=>t===e._dataset)).length&&t._destroyDatasetMeta(n)}))}buildOrUpdateControllers(){const t=this,e=[],i=t.data.datasets;let n,o;for(t._removeUnreferencedMetasets(),n=0,o=i.length;n{t.getDatasetMeta(i).controller.reset()}),t)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this,i=e.config;i.update(),e._options=i.createResolver(i.chartOptionScopes(),e.getContext()),J(e.scales,(t=>{Ge.removeBox(e,t)}));const n=e._animationsDisabled=!e.options.animation;e.ensureScalesHaveIDs(),e.buildOrUpdateScales();const o=new Set(Object.keys(e._listeners)),s=new Set(e.options.events);if(ut(o,s)&&!!this._responsiveListeners===e.options.responsive||(e.unbindEvents(),e.bindEvents()),e._plugins.invalidate(),!1===e.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const a=e.buildOrUpdateControllers();e.notifyPlugins("beforeElementsUpdate");let r=0;for(let t=0,i=e.data.datasets.length;t{t.reset()})),e._updateDatasets(t),e.notifyPlugins("afterUpdate",{mode:t}),e._layers.sort(Xn("z","_idx")),e._lastEvent&&e._eventHandler(e._lastEvent,!0),e.render()}_updateLayout(t){const e=this;if(!1===e.notifyPlugins("beforeLayout",{cancelable:!0}))return;Ge.update(e,e.width,e.height,t);const i=e.chartArea,n=i.width<=0||i.height<=0;e._layers=[],J(e.boxes,(t=>{n&&"chartArea"===t.position||(t.configure&&t.configure(),e._layers.push(...t._layers()))}),e),e._layers.forEach(((t,e)=>{t._idx=e})),e.notifyPlugins("afterLayout")}_updateDatasets(t){const e=this,i="function"==typeof t;if(!1!==e.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let n=0,o=e.data.datasets.length;n=0;--i)t._drawDataset(e[i]);t.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this,i=e.ctx,n=t._clip,o=e.chartArea,s={meta:t,index:t.index,cancelable:!0};!1!==e.notifyPlugins("beforeDatasetDraw",s)&&(Zt(i,{left:!1===n.left?0:o.left-n.left,right:!1===n.right?e.width:o.right+n.right,top:!1===n.top?0:o.top-n.top,bottom:!1===n.bottom?e.height:o.bottom+n.bottom}),t.controller.draw(),Qt(i),s.cancelable=!1,e.notifyPlugins("afterDatasetDraw",s))}getElementsAtEventForMode(t,e,i,n){const o=Oe.modes[e];return"function"==typeof o?o(this,t,i,n):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let n=i.filter((t=>t&&t._dataset===e)).pop();return n||(n=i[t]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1}),n}getContext(){return this.$context||(this.$context={chart:this,type:"chart"})}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateDatasetVisibility(t,e){const i=this,n=e?"show":"hide",o=i.getDatasetMeta(t),s=o.controller._resolveAnimations(void 0,n);i.setDatasetVisibility(t,e),s.update(o,{visible:e}),i.update((e=>e.datasetIndex===t?n:void 0))}hide(t){this._updateDatasetVisibility(t,!1)}show(t){this._updateDatasetVisibility(t,!0)}_destroyDatasetMeta(t){const e=this,i=e._metasets&&e._metasets[t];i&&i.controller&&(i.controller._destroy(),delete e._metasets[t])}destroy(){const t=this,{canvas:e,ctx:i}=t;let n,o;for(t.stop(),a.remove(t),n=0,o=t.data.datasets.length;n((n,o)=>{i.addEventListener(t,n,o),e[n]=o})(o,n)))}bindResponsiveEvents(){const t=this;t._responsiveListeners||(t._responsiveListeners={});const e=t._responsiveListeners,i=t.platform,n=(n,o)=>{i.addEventListener(t,n,o),e[n]=o},o=(n,o)=>{e[n]&&(i.removeEventListener(t,n,o),delete e[n])},s=(e,i)=>{t.canvas&&t.resize(e,i)};let a;const r=()=>{o("attach",r),t.attached=!0,t.resize(),n("resize",s),n("detach",a)};a=()=>{t.attached=!1,o("resize",s),n("attach",r)},i.isAttached(t.canvas)?r():a()}unbindEvents(){const t=this;J(t._listeners,((e,i)=>{t.platform.removeEventListener(t,i,e)})),t._listeners={},J(t._responsiveListeners,((e,i)=>{t.platform.removeEventListener(t,i,e)})),t._responsiveListeners=void 0}updateHoverStyle(t,e,i){const n=i?"set":"remove";let o,s,a,r;for("dataset"===e&&(o=this.getDatasetMeta(t[0].datasetIndex),o.controller["_"+n+"DatasetHoverStyle"]()),a=0,r=t.length;a{const n=e.getDatasetMeta(t);if(!n)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:n.data[i],index:i}}));!tt(n,i)&&(e._active=n,e._updateHoverStyles(n,i))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}_updateHoverStyles(t,e,i){const n=this,o=n.options.hover,s=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),a=s(e,t),r=i?t:s(t,e);a.length&&n.updateHoverStyle(a,o.mode,!1),r.length&&o.mode&&n.updateHoverStyle(r,o.mode,!0)}_eventHandler(t,e){const i=this,n={event:t,replay:e,cancelable:!0},o=e=>(e.options.events||this.options.events).includes(t.type);if(!1===i.notifyPlugins("beforeEvent",n,o))return;const s=i._handleEvent(t,e);return n.cancelable=!1,i.notifyPlugins("afterEvent",n,o),(s||n.changed)&&i.render(),i}_handleEvent(t,e){const i=this,{_active:n=[],options:o}=i,s=o.hover,a=e;let r=[],l=!1,c=null;return"mouseout"!==t.type&&(r=i.getElementsAtEventForMode(t,s.mode,s,a),c="click"===t.type?i._lastEvent:t),i._lastEvent=null,Gt(t,i.chartArea,i._minPadding)&&(Q(o.onHover,[t,r,i],i),"mouseup"!==t.type&&"click"!==t.type&&"contextmenu"!==t.type||Q(o.onClick,[t,r,i],i)),l=!tt(r,n),(l||e)&&(i._active=r,i._updateHoverStyles(r,n,e)),i._lastEvent=c,l}}const eo=()=>J(to.instances,(t=>t._plugins.invalidate())),io=!0;function no(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}Object.defineProperties(to,{defaults:{enumerable:io,value:xt},instances:{enumerable:io,value:Qn},overrides:{enumerable:io,value:ft},registry:{enumerable:io,value:Tn},version:{enumerable:io,value:"3.3.0"},getChart:{enumerable:io,value:Jn},register:{enumerable:io,value:(...t)=>{Tn.add(...t),eo()}},unregister:{enumerable:io,value:(...t)=>{Tn.remove(...t),eo()}}});class oo{constructor(t){this.options=t||{}}formats(){return no()}parse(t,e){return no()}format(t,e){return no()}add(t,e,i){return no()}diff(t,e,i){return no()}startOf(t,e,i){return no()}endOf(t,e){return no()}}oo.override=function(t){Object.assign(oo.prototype,t)};var so={_date:oo};function ao(t){const e=function(t){if(!t._cache.$bar){const e=t.getMatchingVisibleMetas("bar");let i=[];for(let n=0,o=e.length;nt-e)))}return t._cache.$bar}(t);let i,n,o,s,a=t._length;const r=()=>{32767!==o&&-32768!==o&&(ht(s)&&(a=Math.min(a,Math.abs(o-s)||a)),s=o)};for(i=0,n=e.length;iMath.abs(r)&&(l=r,c=a),e[i.axis]=c,e._custom={barStart:l,barEnd:c,start:o,end:s,min:a,max:r}}(t,e,i,n):e[i.axis]=i.parse(t,n),e}function lo(t,e,i,n){const o=t.iScale,s=t.vScale,a=o.getLabels(),r=o===s,l=[];let c,h,d,u;for(c=i,h=i+n;c0?(p+=t,h-=t):h<0&&(p-=t,h+=t)}return{size:h,base:p,head:c,center:c+h/2}}_calculateBarIndexPixels(t,e){const i=this,n=e.scale,o=i.options,s=o.skipNull,a=K(o.maxBarThickness,1/0);let r,l;if(e.grouped){const n=s?i._getStackCount(t):e.stackCount,c="flex"===o.barThickness?function(t,e,i,n){const o=e.pixels,s=o[t];let a=t>0?o[t-1]:null,r=t=0;--n)i=Math.max(i,t[n].size()/2,e[n]._custom);return i>0&&i}getLabelAndValue(t){const e=this._cachedMeta,{xScale:i,yScale:n}=e,o=this.getParsed(t),s=i.getLabelForValue(o.x),a=n.getLabelForValue(o.y),r=o._custom;return{label:e.label,value:"("+s+", "+a+(r?", "+r:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,n){const o=this,s="reset"===n,{iScale:a,vScale:r}=o._cachedMeta,l=o.resolveDataElementOptions(e,n),c=o.getSharedOptions(l),h=o.includeOptions(n,c),d=a.axis,u=r.axis;for(let l=e;l""}}}};class fo extends Ri{constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,n=this._cachedMeta;let o,s;for(o=t,s=t+e;oHt(t,r,l,!0)?1:Math.max(e,e*i,n,n*i),g=(t,e,n)=>Ht(t,r,l,!0)?-1:Math.min(e,e*i,n,n*i),p=f(0,c,d),m=f(Mt,h,u),x=g(bt,c,d),b=g(bt+Mt,h,u);n=(p-x)/2,o=(m-b)/2,s=-(p+x)/2,a=-(m+b)/2}return{ratioX:n,ratioY:o,offsetX:s,offsetY:a}}(d,h,l),m=(n.width-a)/u,x=(n.height-a)/f,b=Math.max(Math.min(m,x)/2,0),_=Z(e.options.radius,b),y=(_-Math.max(_*l,0))/e._getVisibleDatasetWeightTotal();e.offsetX=g*_,e.offsetY=p*_,o.total=e.calculateTotal(),e.outerRadius=_-y*e._getRingWeightOffset(e.index),e.innerRadius=Math.max(e.outerRadius-y*c,0),e.updateElements(s,0,s.length,t)}_circumference(t,e){const i=this,n=i.options,o=i._cachedMeta,s=i._getCircumference();return e&&n.animation.animateRotate||!this.chart.getDataVisibility(t)||null===o._parsed[t]?0:i.calculateCircumference(o._parsed[t]*s/_t)}updateElements(t,e,i,n){const o=this,s="reset"===n,a=o.chart,r=a.chartArea,l=a.options.animation,c=(r.left+r.right)/2,h=(r.top+r.bottom)/2,d=s&&l.animateScale,u=d?0:o.innerRadius,f=d?0:o.outerRadius,g=o.resolveDataElementOptions(e,n),p=o.getSharedOptions(g),m=o.includeOptions(n,p);let x,b=o._getRotation();for(x=0;x0&&!isNaN(t)?_t*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,n=i.data.labels||[],o=zi(e._parsed[t],i.options.locale);return{label:n[t]||"",value:o}}getMaxBorderWidth(t){const e=this;let i=0;const n=e.chart;let o,s,a,r,l;if(!t)for(o=0,s=n.data.datasets.length;o{const n=t.getDatasetMeta(0).controller.getStyle(i);return{text:e,fillStyle:n.backgroundColor,strokeStyle:n.borderColor,lineWidth:n.borderWidth,hidden:!t.getDataVisibility(i),index:i}})):[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}},tooltip:{callbacks:{title:()=>"",label(t){let e=t.label;const i=": "+t.formattedValue;return Y(e)?(e=e.slice(),e[0]+=i):e+=i,e}}}}};class go extends Ri{initialize(){this.enableOptionSharing=!0,super.initialize()}update(t){const e=this,i=e._cachedMeta,{dataset:n,data:o=[],_dataset:s}=i,a=e.chart._animationsDisabled;let{start:r,count:l}=function(t,e,i){const n=e.length;let o=0,s=n;if(t._sorted){const{iScale:a,_parsed:r}=t,l=a.axis,{min:c,max:h,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(o=Nt(Math.min(oe(r,a.axis,c).lo,i?n:oe(e,l,a.getPixelForValue(c)).lo),0,n-1)),s=u?Nt(Math.max(oe(r,a.axis,h).hi+1,i?0:oe(e,l,a.getPixelForValue(h)).hi+1),o,n)-o:n-o}return{start:o,count:s}}(i,o,a);e._drawStart=r,e._drawCount=l,function(t){const{xScale:e,yScale:i,_scaleRanges:n}=t,o={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!n)return t._scaleRanges=o,!0;const s=n.xmin!==e.min||n.xmax!==e.max||n.ymin!==i.min||n.ymax!==i.max;return Object.assign(n,o),s}(i)&&(r=0,l=o.length),n._decimated=!!s._decimated,n.points=o;const c=e.resolveDatasetElementOptions(t);e.options.showLine||(c.borderWidth=0),c.segment=e.options.segment,e.updateElement(n,void 0,{animated:!a,options:c},t),e.updateElements(o,r,l,t)}updateElements(t,e,i,n){const o=this,s="reset"===n,{iScale:a,vScale:r,_stacked:l}=o._cachedMeta,c=o.resolveDataElementOptions(e,n),h=o.getSharedOptions(c),d=o.includeOptions(n,h),u=a.axis,f=r.axis,g=o.options.spanGaps,p=At(g)?g:Number.POSITIVE_INFINITY,m=o.chart._animationsDisabled||s||"none"===n;let x=e>0&&o.getParsed(e-1);for(let c=e;c0&&i[u]-x[u]>p,g.parsed=i,d&&(g.options=h||o.resolveDataElementOptions(c,n)),m||o.updateElement(e,c,g,n),x=i}o.updateSharedOptions(h,n,c)}getMaxOverflow(){const t=this,e=t._cachedMeta,i=e.dataset,n=i.options&&i.options.borderWidth||0,o=e.data||[];if(!o.length)return n;const s=o[0].size(t.resolveDataElementOptions(0)),a=o[o.length-1].size(t.resolveDataElementOptions(o.length-1));return Math.max(n,s,a)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}}go.id="line",go.defaults={datasetElementType:"line",dataElementType:"point",showLine:!0,spanGaps:!1},go.overrides={scales:{_index_:{type:"category"},_value_:{type:"linear"}}};class po extends Ri{constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,n=i.data.labels||[],o=zi(e._parsed[t].r,i.options.locale);return{label:n[t]||"",value:o}}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}_updateRadius(){const t=this,e=t.chart,i=e.chartArea,n=e.options,o=Math.min(i.right-i.left,i.bottom-i.top),s=Math.max(o/2,0),a=(s-Math.max(n.cutoutPercentage?s/100*n.cutoutPercentage:1,0))/e.getVisibleDatasetCount();t.outerRadius=s-a*t.index,t.innerRadius=t.outerRadius-a}updateElements(t,e,i,n){const o=this,s="reset"===n,a=o.chart,r=o.getDataset(),l=a.options.animation,c=o._cachedMeta.rScale,h=c.xCenter,d=c.yCenter,u=c.getIndexAngle(0)-.5*bt;let f,g=u;const p=360/o.countVisibleElements();for(f=0;f{!isNaN(t.data[n])&&this.chart.getDataVisibility(n)&&i++})),i}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?Et(this.resolveDataElementOptions(t,e).angle||i):0}}po.id="polarArea",po.defaults={dataElementType:"arc",animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:"number",properties:["x","y","startAngle","endAngle","innerRadius","outerRadius"]}},indexAxis:"r",startAngle:0},po.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(((e,i)=>{const n=t.getDatasetMeta(0).controller.getStyle(i);return{text:e,fillStyle:n.backgroundColor,strokeStyle:n.borderColor,lineWidth:n.borderWidth,hidden:!t.getDataVisibility(i),index:i}})):[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}},tooltip:{callbacks:{title:()=>"",label:t=>t.chart.data.labels[t.dataIndex]+": "+t.formattedValue}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};class mo extends fo{}mo.id="pie",mo.defaults={cutout:0,rotation:0,circumference:360,radius:"100%"};class xo extends Ri{getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}update(t){const e=this,i=e._cachedMeta,n=i.dataset,o=i.data||[],s=i.iScale.getLabels();if(n.points=o,"resize"!==t){const i=e.resolveDatasetElementOptions(t);e.options.showLine||(i.borderWidth=0);const a={_loop:!0,_fullLoop:s.length===o.length,options:i};e.updateElement(n,void 0,a,t)}e.updateElements(o,0,o.length,t)}updateElements(t,e,i,n){const o=this,s=o.getDataset(),a=o._cachedMeta.rScale,r="reset"===n;for(let l=e;l"",label:t=>"("+t.label+", "+t.formattedValue+")"}}},scales:{x:{type:"linear"},y:{type:"linear"}}};var _o=Object.freeze({__proto__:null,BarController:ho,BubbleController:uo,DoughnutController:fo,LineController:go,PolarAreaController:po,PieController:mo,RadarController:xo,ScatterController:bo});function yo(t,e){const{startAngle:i,endAngle:n,pixelMargin:o,x:s,y:a,outerRadius:r,innerRadius:l}=e;let c=o/r;t.beginPath(),t.arc(s,a,r,i-c,n+c),l>o?(c=o/l,t.arc(s,a,l,n+c,i-c,!0)):t.arc(s,a,o,n+Mt,i-Mt),t.closePath(),t.clip()}function vo(t,e,i,n){const o=Re(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const s=(i-e)/2,a=Math.min(s,n*e/2),r=t=>{const e=(i-Math.min(s,t))*n/2;return Nt(t,0,Math.min(s,e))};return{outerStart:r(o.outerStart),outerEnd:r(o.outerEnd),innerStart:Nt(o.innerStart,0,a),innerEnd:Nt(o.innerEnd,0,a)}}function wo(t,e,i,n){return{x:i+t*Math.cos(e),y:n+t*Math.sin(e)}}function Mo(t,e,i){const{x:n,y:o,startAngle:s,endAngle:a,pixelMargin:r,innerRadius:l}=e,c=Math.max(e.outerRadius+i-r,0),h=l>0?l+i+r:0,d=a-s,u=(d-Math.max(.001,d*c-i/bt)/c)/2,f=s+u,g=a-u,{outerStart:p,outerEnd:m,innerStart:x,innerEnd:b}=vo(e,h,c,g-f),_=c-p,y=c-m,v=f+p/_,w=g-m/y,M=h+x,k=h+b,S=f+x/M,P=g-b/k;if(t.beginPath(),t.arc(n,o,c,v,w),m>0){const e=wo(y,w,n,o);t.arc(e.x,e.y,m,w,g+Mt)}const D=wo(k,g,n,o);if(t.lineTo(D.x,D.y),b>0){const e=wo(k,P,n,o);t.arc(e.x,e.y,b,g+Mt,P+Math.PI)}if(t.arc(n,o,h,g-b/h,f+x/h,!0),x>0){const e=wo(M,S,n,o);t.arc(e.x,e.y,x,S+Math.PI,f-Mt)}const C=wo(_,f,n,o);if(t.lineTo(C.x,C.y),p>0){const e=wo(_,v,n,o);t.arc(e.x,e.y,p,f-Mt,v)}t.closePath()}function ko(t,e,i){const{options:n}=e,o="inner"===n.borderAlign;n.borderWidth&&(o?(t.lineWidth=2*n.borderWidth,t.lineJoin="round"):(t.lineWidth=n.borderWidth,t.lineJoin="bevel"),e.fullCircles&&function(t,e,i){const{x:n,y:o,startAngle:s,endAngle:a,pixelMargin:r}=e,l=Math.max(e.outerRadius-r,0),c=e.innerRadius+r;let h;for(i&&(e.endAngle=e.startAngle+_t,yo(t,e),e.endAngle=a,e.endAngle===e.startAngle&&(e.endAngle+=_t,e.fullCircles--)),t.beginPath(),t.arc(n,o,c,s+_t,s,!0),h=0;h=_t||Ht(o,a,r))&&(s>=l&&s<=c)}getCenterPoint(t){const{x:e,y:i,startAngle:n,endAngle:o,innerRadius:s,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),r=(n+o)/2,l=(s+a)/2;return{x:e+Math.cos(r)*l,y:i+Math.sin(r)*l}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const e=this,i=e.options,n=(i.offset||0)/2;if(e.pixelMargin="inner"===i.borderAlign?.33:0,e.fullCircles=Math.floor(e.circumference/_t),0===e.circumference||e.innerRadius<0||e.outerRadius<0)return;t.save();let o=0;if(n){o=n/2;const i=(e.startAngle+e.endAngle)/2;t.translate(Math.cos(i)*o,Math.sin(i)*o),e.circumference>=bt&&(o=n)}t.fillStyle=i.backgroundColor,t.strokeStyle=i.borderColor,function(t,e,i){if(e.fullCircles){e.endAngle=e.startAngle+_t,Mo(t,e,i);for(let i=0;ir&&s>r;return{count:n,start:l,loop:e.loop,ilen:c(a+(c?r-t:t))%s,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=o[b(0)],t.moveTo(d.x,d.y)),h=0;h<=r;++h){if(d=o[b(h)],d.skip)continue;const e=d.x,i=d.y,n=0|e;n===u?(ig&&(g=i),m=(x*m+e)/++x):(_(),t.lineTo(e,i),u=n,x=0,f=g=i),p=i}_()}function To(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?Ao:Oo}So.id="arc",So.defaults={borderAlign:"center",borderColor:"#fff",borderRadius:0,borderWidth:2,offset:0,angle:void 0},So.defaultRoutes={backgroundColor:"backgroundColor"};const Lo="function"==typeof Path2D;function Ro(t,e,i,n){Lo&&1===e.segments.length?function(t,e,i,n){let o=e._path;o||(o=e._path=new Path2D,e.path(o,i,n)&&o.closePath()),Po(t,e.options),t.stroke(o)}(t,e,i,n):function(t,e,i,n){const{segments:o,options:s}=e,a=To(e);for(const r of o)Po(t,s,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+n-1})&&t.closePath(),t.stroke()}(t,e,i,n)}class Eo extends Ei{constructor(t){super(),this.animated=!0,this.options=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this,n=i.options;if((n.tension||"monotone"===n.cubicInterpolationMode)&&!n.stepped&&!i._pointsUpdated){const o=n.spanGaps?i._loop:i._fullLoop;gn(i._points,n,t,o,e),i._pointsUpdated=!0}}set points(t){const e=this;e._points=t,delete e._segments,delete e._path,e._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=Sn(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this,n=i.options,o=t[e],s=i.points,a=kn(i,{property:e,start:o,end:o});if(!a.length)return;const r=[],l=function(t){return t.stepped?mn:t.tension||"monotone"===t.cubicInterpolationMode?xn:pn}(n);let c,h;for(c=0,h=a.length;c"borderDash"!==t&&"fill"!==t};class zo extends Ei{constructor(t){super(),this.options=void 0,this.parsed=void 0,this.skip=void 0,this.stop=void 0,t&&Object.assign(this,t)}inRange(t,e,i){const n=this.options,{x:o,y:s}=this.getProps(["x","y"],i);return Math.pow(t-o,2)+Math.pow(e-s,2)t.x):Bo(e,"bottom","top",t.base=a.left&&e<=a.right)&&(s||i>=a.top&&i<=a.bottom)}function $o(t,e){t.rect(e.x,e.y,e.w,e.h)}zo.id="point",zo.defaults={borderWidth:1,hitRadius:1,hoverBorderWidth:1,hoverRadius:4,pointStyle:"circle",radius:3,rotation:0},zo.defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};class Yo extends Ei{constructor(t){super(),this.options=void 0,this.horizontal=void 0,this.base=void 0,this.width=void 0,this.height=void 0,t&&Object.assign(this,t)}draw(t){const e=this.options,{inner:i,outer:n}=No(this),o=(s=n.radius).topLeft||s.topRight||s.bottomLeft||s.bottomRight?ie:$o;var s;t.save(),n.w===i.w&&n.h===i.h||(t.beginPath(),o(t,n),t.clip(),o(t,i),t.fillStyle=e.borderColor,t.fill("evenodd")),t.beginPath(),o(t,i),t.fillStyle=e.backgroundColor,t.fill(),t.restore()}inRange(t,e,i){return jo(this,t,e,i)}inXRange(t,e){return jo(this,t,null,e)}inYRange(t,e){return jo(this,null,t,e)}getCenterPoint(t){const{x:e,y:i,base:n,horizontal:o}=this.getProps(["x","y","base","horizontal"],t);return{x:o?(e+n)/2:e,y:o?i:(i+n)/2}}getRange(t){return"x"===t?this.width/2:this.height/2}}Yo.id="bar",Yo.defaults={borderSkipped:"start",borderWidth:0,borderRadius:0,enableBorderRadius:!0,pointStyle:void 0},Yo.defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};var Uo=Object.freeze({__proto__:null,ArcElement:So,LineElement:Eo,PointElement:zo,BarElement:Yo});function Xo(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{value:e})}}function qo(t){t.data.datasets.forEach((t=>{Xo(t)}))}var Ko={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void qo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:s,indexAxis:a}=e,r=t.getDatasetMeta(o),l=s||e.data;if("y"===Ve([a,t.options.indexAxis]))return;if("line"!==r.type)return;const c=t.scales[r.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let h,{start:d,count:u}=function(t,e){const i=e.length;let n,o=0;const{iScale:s}=t,{min:a,max:r,minDefined:l,maxDefined:c}=s.getUserBounds();return l&&(o=Nt(oe(e,s.axis,a).lo,0,i-1)),n=c?Nt(oe(e,s.axis,r).hi+1,o,i)-o:i-o,{start:o,count:n}}(r,l);if(u<=4*n)Xo(e);else{switch($(s)&&(e._data=l,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":h=function(t,e,i,n,o){const s=o.samples||n;if(s>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(s-2);let l=0;const c=e+i-1;let h,d,u,f,g,p=e;for(a[l++]=t[p],h=0;hu&&(u=f,d=t[n],g=n);a[l++]=d,p=g}return a[l++]=t[c],a}(l,d,u,n,i);break;case"min-max":h=function(t,e,i,n){let o,s,a,r,l,c,h,d,u,f,g=0,p=0;const m=[],x=e+i-1,b=t[e].x,_=t[x].x-b;for(o=e;of&&(f=r,h=o),g=(p*g+s.x)/++p;else{const i=o-1;if(!$(c)&&!$(h)){const e=Math.min(c,h),n=Math.max(c,h);e!==d&&e!==i&&m.push({...t[e],x:g}),n!==d&&n!==i&&m.push({...t[n],x:g})}o>0&&i!==d&&m.push(t[i]),m.push(s),l=e,p=0,u=f=r,c=h=d=o}}return m}(l,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=h}}))},destroy(t){qo(t)}};function Go(t,e,i){const n=function(t){const e=t.options,i=e.fill;let n=K(i&&i.target,i);return void 0===n&&(n=!!e.backgroundColor),!1!==n&&null!==n&&(!0===n?"origin":n)}(t);if(U(n))return!isNaN(n.value)&&n;let o=parseFloat(n);return X(o)&&Math.floor(o)===o?("-"!==n[0]&&"+"!==n[0]||(o=e+o),!(o===e||o<0||o>=i)&&o):["origin","start","end","stack"].indexOf(n)>=0&&n}class Zo{constructor(t){this.x=t.x,this.y=t.y,this.radius=t.radius}pathSegment(t,e,i){const{x:n,y:o,radius:s}=this;return e=e||{start:0,end:_t},t.arc(n,o,s,e.end,e.start,!0),!i.bounds}interpolate(t){const{x:e,y:i,radius:n}=this,o=t.angle;return{x:e+Math.cos(o)*n,y:i+Math.sin(o)*n,angle:o}}}function Qo(t){return(t.scale||{}).getPointPositionForValue?function(t){const{scale:e,fill:i}=t,n=e.options,o=e.getLabels().length,s=[],a=n.reverse?e.max:e.min,r=n.reverse?e.min:e.max;let l,c,h;if(h="start"===i?a:"end"===i?r:U(i)?i.value:e.getBaseValue(),n.grid.circular)return c=e.getPointPositionForValue(0,a),new Zo({x:c.x,y:c.y,radius:e.getDistanceFromCenterForValue(h)});for(l=0;l"line"===t.type&&!t.hidden;function es(t,e,i){const n=[];for(let o=0;o=n&&o<=c){r=o===n,l=o===c;break}}return{first:r,last:l,point:n}}function ns(t,e){let i=[],n=!1;return Y(t)?(n=!0,i=t):i=function(t,e){const{x:i=null,y:n=null}=t||{},o=e.points,s=[];return e.segments.forEach((t=>{const e=o[t.start],a=o[t.end];null!==n?(s.push({x:e.x,y:n}),s.push({x:a.x,y:n})):null!==i&&(s.push({x:i,y:e.y}),s.push({x:i,y:a.y}))})),s}(t,e),i.length?new Eo({points:i,options:{tension:0},_loop:n,_fullLoop:n}):null}function os(t,e,i){let n=t[e].fill;const o=[e];let s;if(!i)return n;for(;!1!==n&&-1===o.indexOf(n);){if(!X(n))return n;if(s=t[n],!s)return!1;if(s.visible)return n;o.push(n),n=s.fill}return!1}function ss(t,e,i){t.beginPath(),e.path(t),t.lineTo(e.last().x,i),t.lineTo(e.first().x,i),t.closePath(),t.clip()}function as(t,e,i,n){if(n)return;let o=e[t],s=i[t];return"angle"===t&&(o=Wt(o),s=Wt(s)),{property:t,start:o,end:s}}function rs(t,e,i,n){return t&&e?n(t[i],e[i]):t?t[i]:e?e[i]:0}function ls(t,e,i){const{top:n,bottom:o}=e.chart.chartArea,{property:s,start:a,end:r}=i||{};"x"===s&&(t.beginPath(),t.rect(a,n,r-a,o-n),t.clip())}function cs(t,e,i,n){const o=e.interpolate(i,n);o&&t.lineTo(o.x,o.y)}function hs(t,e){const{line:i,target:n,property:o,color:s,scale:a}=e,r=function(t,e,i){const n=t.segments,o=t.points,s=e.points,a=[];for(const t of n){const n=as(i,o[t.start],o[t.end],t.loop);if(!e.segments){a.push({source:t,target:n,start:o[t.start],end:o[t.end]});continue}const r=kn(e,n);for(const e of r){const r=as(i,s[e.start],s[e.end],e.loop),l=Mn(t,o,r);for(const t of l)a.push({source:t,target:e,start:{[i]:rs(n,r,"start",Math.max)},end:{[i]:rs(n,r,"end",Math.min)}})}}return a}(i,n,o);for(const{source:e,target:l,start:c,end:h}of r){const{style:{backgroundColor:r=s}={}}=e;t.save(),t.fillStyle=r,ls(t,a,as(o,c,h)),t.beginPath();const d=!!i.pathSegment(t,e);d?t.closePath():cs(t,n,h,o);const u=!!n.pathSegment(t,l,{move:d,reverse:!0}),f=d&&u;f||cs(t,n,c,o),t.closePath(),t.fill(f?"evenodd":"nonzero"),t.restore()}}function ds(t,e,i){const n=function(t){const{chart:e,fill:i,line:n}=t;if(X(i))return function(t,e){const i=t.getDatasetMeta(e);return i&&t.isDatasetVisible(e)?i.dataset:null}(e,i);if("stack"===i)return Jo(t);const o=Qo(t);return o instanceof Zo?o:ns(o,n)}(e),{line:o,scale:s,axis:a}=e,r=o.options,l=r.fill,c=r.backgroundColor,{above:h=c,below:d=c}=l||{};n&&o.points.length&&(Zt(t,i),function(t,e){const{line:i,target:n,above:o,below:s,area:a,scale:r}=e,l=i._loop?"angle":e.axis;t.save(),"x"===l&&s!==o&&(ss(t,n,a.top),hs(t,{line:i,target:n,color:o,scale:r,property:l}),t.restore(),t.save(),ss(t,n,a.bottom)),hs(t,{line:i,target:n,color:s,scale:r,property:l}),t.restore()}(t,{line:o,target:n,above:h,below:d,area:i,scale:s,axis:a}),Qt(t))}var us={id:"filler",afterDatasetsUpdate(t,e,i){const n=(t.data.datasets||[]).length,o=[];let s,a,r,l;for(a=0;a=0;--e){const i=o[e].$filler;i&&(i.line.updateControlPoints(s,i.axis),n&&ds(t.ctx,i,s))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const n=t.getSortedVisibleDatasetMetas();for(let e=n.length-1;e>=0;--e){const i=n[e].$filler;i&&ds(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const n=e.meta.$filler;n&&!1!==n.fill&&"beforeDatasetDraw"===i.drawTime&&ds(t.ctx,n,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const fs=(t,e)=>{let{boxHeight:i=e,boxWidth:n=e}=t;return t.usePointStyle&&(i=Math.min(i,e),n=Math.min(n,e)),{boxWidth:n,boxHeight:i,itemHeight:Math.max(e,i)}};class gs extends Ei{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){const n=this;n.maxWidth=t,n.maxHeight=e,n._margins=i,n.setDimensions(),n.buildLabels(),n.fit()}setDimensions(){const t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height)}buildLabels(){const t=this,e=t.options.labels||{};let i=Q(e.generateLabels,[t.chart],t)||[];e.filter&&(i=i.filter((i=>e.filter(i,t.chart.data)))),e.sort&&(i=i.sort(((i,n)=>e.sort(i,n,t.chart.data)))),t.options.reverse&&i.reverse(),t.legendItems=i}fit(){const t=this,{options:e,ctx:i}=t;if(!e.display)return void(t.width=t.height=0);const n=e.labels,o=Fe(n.font),s=o.size,a=t._computeTitleHeight(),{boxWidth:r,itemHeight:l}=fs(n,s);let c,h;i.font=o.string,t.isHorizontal()?(c=t.maxWidth,h=t._fitRows(a,s,r,l)+10):(h=t.maxHeight,c=t._fitCols(a,s,r,l)+10),t.width=Math.min(c,e.maxWidth||t.maxWidth),t.height=Math.min(h,e.maxHeight||t.maxHeight)}_fitRows(t,e,i,n){const o=this,{ctx:s,maxWidth:a,options:{labels:{padding:r}}}=o,l=o.legendHitBoxes=[],c=o.lineWidths=[0],h=n+r;let d=t;s.textAlign="left",s.textBaseline="middle";let u=-1,f=-h;return o.legendItems.forEach(((t,o)=>{const g=i+e/2+s.measureText(t.text).width;(0===o||c[c.length-1]+g+2*r>a)&&(d+=h,c[c.length-(o>0?0:1)]=0,f+=h,u++),l[o]={left:0,top:f,row:u,width:g,height:n},c[c.length-1]+=g+r})),d}_fitCols(t,e,i,n){const o=this,{ctx:s,maxHeight:a,options:{labels:{padding:r}}}=o,l=o.legendHitBoxes=[],c=o.columnSizes=[],h=a-t;let d=r,u=0,f=0,g=0,p=0,m=0;return o.legendItems.forEach(((t,o)=>{const a=i+e/2+s.measureText(t.text).width;o>0&&f+e+2*r>h&&(d+=u+r,c.push({width:u,height:f}),g+=u+r,m++,p=0,u=f=0),u=Math.max(u,a),f+=e+r,l[o]={left:g,top:p,col:m,width:a,height:n},p+=n+r})),d+=u,c.push({width:u,height:f}),d}adjustHitBoxes(){const t=this;if(!t.options.display)return;const e=t._computeTitleHeight(),{legendHitBoxes:i,options:{align:n,labels:{padding:s}}}=t;if(this.isHorizontal()){let a=0,r=o(n,t.left+s,t.right-t.lineWidths[a]);for(const l of i)a!==l.row&&(a=l.row,r=o(n,t.left+s,t.right-t.lineWidths[a])),l.top+=t.top+e+s,l.left=r,r+=l.width+s}else{let a=0,r=o(n,t.top+e+s,t.bottom-t.columnSizes[a].height);for(const l of i)l.col!==a&&(a=l.col,r=o(n,t.top+e+s,t.bottom-t.columnSizes[a].height)),l.top=r,l.left+=t.left+s,r+=l.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){const t=this;if(t.options.display){const e=t.ctx;Zt(e,t),t._draw(),Qt(e)}}_draw(){const t=this,{options:e,columnSizes:i,lineWidths:n,ctx:a}=t,{align:r,labels:l}=e,c=xt.color,h=bn(e.rtl,t.left,t.width),d=Fe(l.font),{color:u,padding:f}=l,g=d.size,p=g/2;let m;t.drawTitle(),a.textAlign=h.textAlign("left"),a.textBaseline="middle",a.lineWidth=.5,a.font=d.string;const{boxWidth:x,boxHeight:b,itemHeight:_}=fs(l,g),y=t.isHorizontal(),v=this._computeTitleHeight();m=y?{x:o(r,t.left+f,t.right-n[0]),y:t.top+f+v,line:0}:{x:t.left+f,y:o(r,t.top+v+f,t.bottom-i[0].height),line:0},_n(t.ctx,e.textDirection);const w=_+f;t.legendItems.forEach(((e,M)=>{a.strokeStyle=e.fontColor||u,a.fillStyle=e.fontColor||u;const k=a.measureText(e.text).width,S=h.textAlign(e.textAlign||(e.textAlign=l.textAlign)),P=x+g/2+k;let D=m.x,C=m.y;h.setWidth(t.width),y?M>0&&D+P+f>t.right&&(C=m.y+=w,m.line++,D=m.x=o(r,t.left+f,t.right-n[m.line])):M>0&&C+w>t.bottom&&(D=m.x=D+i[m.line].width+f,m.line++,C=m.y=o(r,t.top+v+f,t.bottom-i[m.line].height));!function(t,e,i){if(isNaN(x)||x<=0||isNaN(b)||b<0)return;a.save();const n=K(i.lineWidth,1);if(a.fillStyle=K(i.fillStyle,c),a.lineCap=K(i.lineCap,"butt"),a.lineDashOffset=K(i.lineDashOffset,0),a.lineJoin=K(i.lineJoin,"miter"),a.lineWidth=n,a.strokeStyle=K(i.strokeStyle,c),a.setLineDash(K(i.lineDash,[])),l.usePointStyle){const o={radius:x*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},s=h.xPlus(t,x/2);Kt(a,o,s,e+p)}else{const o=e+Math.max((g-b)/2,0),s=h.leftForLtr(t,x),r=Ie(i.borderRadius);a.beginPath(),Object.values(r).some((t=>0!==t))?ie(a,{x:s,y:o,w:x,h:b,radius:r}):a.rect(s,o,x,b),a.fill(),0!==n&&a.stroke()}a.restore()}(h.x(D),C,e),D=s(S,D+x+p,t.right),function(t,e,i){ee(a,i.text,t,e+_/2,d,{strikethrough:i.hidden,textAlign:i.textAlign})}(h.x(D),C,e),y?m.x+=P+f:m.y+=w})),yn(t.ctx,e.textDirection)}drawTitle(){const t=this,e=t.options,i=e.title,s=Fe(i.font),a=ze(i.padding);if(!i.display)return;const r=bn(e.rtl,t.left,t.width),l=t.ctx,c=i.position,h=s.size/2,d=a.top+h;let u,f=t.left,g=t.width;if(this.isHorizontal())g=Math.max(...t.lineWidths),u=t.top+d,f=o(e.align,f,t.right-g);else{const i=t.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);u=d+o(e.align,t.top,t.bottom-i-e.labels.padding-t._computeTitleHeight())}const p=o(c,f,f+g);l.textAlign=r.textAlign(n(c)),l.textBaseline="middle",l.strokeStyle=i.color,l.fillStyle=i.color,l.font=s.string,ee(l,i.text,p,u,s)}_computeTitleHeight(){const t=this.options.title,e=Fe(t.font),i=ze(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){const i=this;let n,o,s;if(t>=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom)for(s=i.legendHitBoxes,n=0;n=o.left&&t<=o.left+o.width&&e>=o.top&&e<=o.top+o.height)return i.legendItems[n];return null}handleEvent(t){const e=this,i=e.options;if(!function(t,e){if("mousemove"===t&&(e.onHover||e.onLeave))return!0;if(e.onClick&&("click"===t||"mouseup"===t))return!0;return!1}(t.type,i))return;const n=e._getLegendItemAt(t.x,t.y);if("mousemove"===t.type){const a=e._hoveredItem,r=(s=n,null!==(o=a)&&null!==s&&o.datasetIndex===s.datasetIndex&&o.index===s.index);a&&!r&&Q(i.onLeave,[t,a,e],e),e._hoveredItem=n,n&&!r&&Q(i.onHover,[t,n,e],e)}else n&&Q(i.onClick,[t,n,e],e);var o,s}}var ps={id:"legend",_element:gs,start(t,e,i){const n=t.legend=new gs({ctx:t.ctx,options:i,chart:t});Ge.configure(t,n,i),Ge.addBox(t,n)},stop(t){Ge.removeBox(t,t.legend),delete t.legend},beforeUpdate(t,e,i){const n=t.legend;Ge.configure(t,n,i),n.options=i},afterUpdate(t){const e=t.legend;e.buildLabels(),e.adjustHitBoxes()},afterEvent(t,e){e.replay||t.legend.handleEvent(e.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(t,e,i){const n=e.datasetIndex,o=i.chart;o.isDatasetVisible(n)?(o.hide(n),e.hidden=!0):(o.show(n),e.hidden=!1)},onHover:null,onLeave:null,labels:{color:t=>t.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:n,textAlign:o,color:s}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const a=t.controller.getStyle(i?0:void 0),r=ze(a.borderWidth);return{text:e[t.index].label,fillStyle:a.backgroundColor,fontColor:s,hidden:!t.visible,lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:(r.width+r.height)/4,strokeStyle:a.borderColor,pointStyle:n||a.pointStyle,rotation:a.rotation,textAlign:o||a.textAlign,borderRadius:0,datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class ms extends Ei{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this,n=i.options;if(i.left=0,i.top=0,!n.display)return void(i.width=i.height=i.right=i.bottom=0);i.width=i.right=t,i.height=i.bottom=e;const o=Y(n.text)?n.text.length:1;i._padding=ze(n.padding);const s=o*Fe(n.font).lineHeight+i._padding.height;i.isHorizontal()?i.height=s:i.width=s}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:n,right:s,options:a}=this,r=a.align;let l,c,h,d=0;return this.isHorizontal()?(c=o(r,i,s),h=e+t,l=s-i):("left"===a.position?(c=i+t,h=o(r,n,e),d=-.5*bt):(c=s-t,h=o(r,e,n),d=.5*bt),l=n-e),{titleX:c,titleY:h,maxWidth:l,rotation:d}}draw(){const t=this,e=t.ctx,i=t.options;if(!i.display)return;const o=Fe(i.font),s=o.lineHeight/2+t._padding.top,{titleX:a,titleY:r,maxWidth:l,rotation:c}=t._drawArgs(s);ee(e,i.text,0,0,o,{color:i.color,maxWidth:l,rotation:c,textAlign:n(i.align),textBaseline:"middle",translation:[a,r]})}}var xs={id:"title",_element:ms,start(t,e,i){!function(t,e){const i=new ms({ctx:t.ctx,options:e,chart:t});Ge.configure(t,i,e),Ge.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;Ge.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const n=t.titleBlock;Ge.configure(t,n,i),n.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const bs={average(t){if(!t.length)return!1;let e,i,n=0,o=0,s=0;for(e=0,i=t.length;e-1?t.split("\n"):t}function vs(t,e){const{element:i,datasetIndex:n,index:o}=e,s=t.getDatasetMeta(n).controller,{label:a,value:r}=s.getLabelAndValue(o);return{chart:t,label:a,parsed:s.getParsed(o),raw:t.data.datasets[n].data[o],formattedValue:r,dataset:s.getDataset(),dataIndex:o,datasetIndex:n,element:i}}function ws(t,e){const i=t._chart.ctx,{body:n,footer:o,title:s}=t,{boxWidth:a,boxHeight:r}=e,l=Fe(e.bodyFont),c=Fe(e.titleFont),h=Fe(e.footerFont),d=s.length,u=o.length,f=n.length,g=ze(e.padding);let p=g.height,m=0,x=n.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(x+=t.beforeBody.length+t.afterBody.length,d&&(p+=d*c.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),x){p+=f*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(x-f)*l.lineHeight+(x-1)*e.bodySpacing}u&&(p+=e.footerMarginTop+u*h.lineHeight+(u-1)*e.footerSpacing);let b=0;const _=function(t){m=Math.max(m,i.measureText(t).width+b)};return i.save(),i.font=c.string,J(t.title,_),i.font=l.string,J(t.beforeBody.concat(t.afterBody),_),b=e.displayColors?a+2:0,J(n,(t=>{J(t.before,_),J(t.lines,_),J(t.after,_)})),b=0,i.font=h.string,J(t.footer,_),i.restore(),m+=g.width,{width:m,height:p}}function Ms(t,e,i,n){const{x:o,width:s}=i,{width:a,chartArea:{left:r,right:l}}=t;let c="center";return"center"===n?c=o<=(r+l)/2?"left":"right":o<=s/2?c="left":o>=a-s/2&&(c="right"),function(t,e,i,n){const{x:o,width:s}=n,a=i.caretSize+i.caretPadding;return"left"===t&&o+s+a>e.width||"right"===t&&o-s-a<0||void 0}(c,t,e,i)&&(c="center"),c}function ks(t,e,i){const n=e.yAlign||function(t,e){const{y:i,height:n}=e;return it.height-n/2?"bottom":"center"}(t,i);return{xAlign:e.xAlign||Ms(t,e,i,n),yAlign:n}}function Ss(t,e,i,n){const{caretSize:o,caretPadding:s,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,c=o+s,h=a+s;let d=function(t,e){let{x:i,width:n}=t;return"right"===e?i-=n:"center"===e&&(i-=n/2),i}(e,r);const u=function(t,e,i){let{y:n,height:o}=t;return"top"===e?n+=i:n-="bottom"===e?o+i:o/2,n}(e,l,c);return"center"===l?"left"===r?d+=c:"right"===r&&(d-=c):"left"===r?d-=h:"right"===r&&(d+=h),{x:Nt(d,0,n.width-e.width),y:Nt(u,0,n.height-e.height)}}function Ps(t,e,i){const n=ze(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-n.right:t.x+n.left}function Ds(t){return _s([],ys(t))}function Cs(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}class Os extends Ei{constructor(t){super(),this.opacity=0,this._active=[],this._chart=t._chart,this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const t=this,e=t._cachedAnimations;if(e)return e;const i=t._chart,n=t.options.setContext(t.getContext()),o=n.enabled&&i.options.animation&&n.animations,s=new vi(t._chart,o);return o._cacheable&&(t._cachedAnimations=Object.freeze(s)),s}getContext(){const t=this;return t.$context||(t.$context=(e=t._chart.getContext(),i=t,n=t._tooltipItems,Object.assign(Object.create(e),{tooltip:i,tooltipItems:n,type:"tooltip"})));var e,i,n}getTitle(t,e){const i=this,{callbacks:n}=e,o=n.beforeTitle.apply(i,[t]),s=n.title.apply(i,[t]),a=n.afterTitle.apply(i,[t]);let r=[];return r=_s(r,ys(o)),r=_s(r,ys(s)),r=_s(r,ys(a)),r}getBeforeBody(t,e){return Ds(e.callbacks.beforeBody.apply(this,[t]))}getBody(t,e){const i=this,{callbacks:n}=e,o=[];return J(t,(t=>{const e={before:[],lines:[],after:[]},s=Cs(n,t);_s(e.before,ys(s.beforeLabel.call(i,t))),_s(e.lines,s.label.call(i,t)),_s(e.after,ys(s.afterLabel.call(i,t))),o.push(e)})),o}getAfterBody(t,e){return Ds(e.callbacks.afterBody.apply(this,[t]))}getFooter(t,e){const i=this,{callbacks:n}=e,o=n.beforeFooter.apply(i,[t]),s=n.footer.apply(i,[t]),a=n.afterFooter.apply(i,[t]);let r=[];return r=_s(r,ys(o)),r=_s(r,ys(s)),r=_s(r,ys(a)),r}_createItems(t){const e=this,i=e._active,n=e._chart.data,o=[],s=[],a=[];let r,l,c=[];for(r=0,l=i.length;rt.filter(e,i,o,n)))),t.itemSort&&(c=c.sort(((e,i)=>t.itemSort(e,i,n)))),J(c,(i=>{const n=Cs(t.callbacks,i);o.push(n.labelColor.call(e,i)),s.push(n.labelPointStyle.call(e,i)),a.push(n.labelTextColor.call(e,i))})),e.labelColors=o,e.labelPointStyles=s,e.labelTextColors=a,e.dataPoints=c,c}update(t,e){const i=this,n=i.options.setContext(i.getContext()),o=i._active;let s,a=[];if(o.length){const t=bs[n.position].call(i,o,i._eventPosition);a=i._createItems(n),i.title=i.getTitle(a,n),i.beforeBody=i.getBeforeBody(a,n),i.body=i.getBody(a,n),i.afterBody=i.getAfterBody(a,n),i.footer=i.getFooter(a,n);const e=i._size=ws(i,n),r=Object.assign({},t,e),l=ks(i._chart,n,r),c=Ss(n,r,l,i._chart);i.xAlign=l.xAlign,i.yAlign=l.yAlign,s={opacity:1,x:c.x,y:c.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==i.opacity&&(s={opacity:0});i._tooltipItems=a,i.$context=void 0,s&&i._resolveAnimations().update(i,s),t&&n.external&&n.external.call(i,{chart:i._chart,tooltip:i,replay:e})}drawCaret(t,e,i,n){const o=this.getCaretPosition(t,i,n);e.lineTo(o.x1,o.y1),e.lineTo(o.x2,o.y2),e.lineTo(o.x3,o.y3)}getCaretPosition(t,e,i){const{xAlign:n,yAlign:o}=this,{cornerRadius:s,caretSize:a}=i,{x:r,y:l}=t,{width:c,height:h}=e;let d,u,f,g,p,m;return"center"===o?(p=l+h/2,"left"===n?(d=r,u=d-a,g=p+a,m=p-a):(d=r+c,u=d+a,g=p-a,m=p+a),f=d):(u="left"===n?r+s+a:"right"===n?r+c-s-a:this.caretX,"top"===o?(g=l,p=g-a,d=u-a,f=u+a):(g=l+h,p=g+a,d=u+a,f=u-a),m=g),{x1:d,x2:u,x3:f,y1:g,y2:p,y3:m}}drawTitle(t,e,i){const n=this,o=n.title,s=o.length;let a,r,l;if(s){const c=bn(i.rtl,n.x,n.width);for(t.x=Ps(n,i.titleAlign,i),e.textAlign=c.textAlign(i.titleAlign),e.textBaseline="middle",a=Fe(i.titleFont),r=i.titleSpacing,e.fillStyle=i.titleColor,e.font=a.string,l=0;l0!==t))?(t.beginPath(),t.fillStyle=o.multiKeyBackground,ie(t,{x:e,y:g,w:c,h:l,radius:s}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),ie(t,{x:i,y:g+1,w:c-2,h:l-2,radius:s}),t.fill()):(t.fillStyle=o.multiKeyBackground,t.fillRect(e,g,c,l),t.strokeRect(e,g,c,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,c-2,l-2))}t.fillStyle=s.labelTextColors[i]}drawBody(t,e,i){const n=this,{body:o}=n,{bodySpacing:s,bodyAlign:a,displayColors:r,boxHeight:l,boxWidth:c}=i,h=Fe(i.bodyFont);let d=h.lineHeight,u=0;const f=bn(i.rtl,n.x,n.width),g=function(i){e.fillText(i,f.x(t.x+u),t.y+d/2),t.y+=d+s},p=f.textAlign(a);let m,x,b,_,y,v,w;for(e.textAlign=a,e.textBaseline="middle",e.font=h.string,t.x=Ps(n,p,i),e.fillStyle=i.bodyColor,J(n.beforeBody,g),u=r&&"right"!==p?"center"===a?c/2+1:c+2:0,_=0,v=o.length;_0&&e.stroke()}_updateAnimationTarget(t){const e=this,i=e._chart,n=e.$animations,o=n&&n.x,s=n&&n.y;if(o||s){const n=bs[t.position].call(e,e._active,e._eventPosition);if(!n)return;const a=e._size=ws(e,t),r=Object.assign({},n,e._size),l=ks(i,t,r),c=Ss(t,r,l,i);o._to===c.x&&s._to===c.y||(e.xAlign=l.xAlign,e.yAlign=l.yAlign,e.width=a.width,e.height=a.height,e.caretX=n.x,e.caretY=n.y,e._resolveAnimations().update(e,c))}}draw(t){const e=this,i=e.options.setContext(e.getContext());let n=e.opacity;if(!n)return;e._updateAnimationTarget(i);const o={width:e.width,height:e.height},s={x:e.x,y:e.y};n=Math.abs(n)<.001?0:n;const a=ze(i.padding),r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;i.enabled&&r&&(t.save(),t.globalAlpha=n,e.drawBackground(s,t,o,i),_n(t,i.textDirection),s.y+=a.top,e.drawTitle(s,t,i),e.drawBody(s,t,i),e.drawFooter(s,t,i),yn(t,i.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this,n=i._active,o=t.map((({datasetIndex:t,index:e})=>{const n=i._chart.getDatasetMeta(t);if(!n)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:n.data[e],index:e}})),s=!tt(n,o),a=i._positionChanged(o,e);(s||a)&&(i._active=o,i._eventPosition=e,i.update(!0))}handleEvent(t,e){const i=this,n=i.options,o=i._active||[];let s=!1,a=[];"mouseout"!==t.type&&(a=i._chart.getElementsAtEventForMode(t,n.mode,n,e),n.reverse&&a.reverse());const r=i._positionChanged(a,t);return s=e||!tt(a,o)||r,s&&(i._active=a,(n.enabled||n.external)&&(i._eventPosition={x:t.x,y:t.y},i.update(!0,e))),s}_positionChanged(t,e){const{caretX:i,caretY:n,options:o}=this,s=bs[o.position].call(this,t,e);return!1!==s&&(i!==s.x||n!==s.y)}}Os.positioners=bs;var As={id:"tooltip",_element:Os,positioners:bs,afterInit(t,e,i){i&&(t.tooltip=new Os({_chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip,i={tooltip:e};!1!==t.notifyPlugins("beforeTooltipDraw",i)&&(e&&e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i))},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:{beforeTitle:N,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,n=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(n>0&&e.dataIndex"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]},Ts=Object.freeze({__proto__:null,Decimation:Ko,Filler:us,Legend:ps,Title:xs,Tooltip:As});function Ls(t,e,i){const n=t.indexOf(e);if(-1===n)return((t,e,i)=>"string"==typeof e?t.push(e)-1:isNaN(e)?null:i)(t,e,i);return n!==t.lastIndexOf(e)?i:n}class Rs extends Xi{constructor(t){super(t),this._startValue=void 0,this._valueRange=0}parse(t,e){if($(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:Nt(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:Ls(i,t,K(e,t)),i.length-1)}determineDataLimits(){const t=this,{minDefined:e,maxDefined:i}=t.getUserBounds();let{min:n,max:o}=t.getMinMax(!0);"ticks"===t.options.bounds&&(e||(n=0),i||(o=t.getLabels().length-1)),t.min=n,t.max=o}buildTicks(){const t=this,e=t.min,i=t.max,n=t.options.offset,o=[];let s=t.getLabels();s=0===e&&i===s.length-1?s:s.slice(e,i+1),t._valueRange=Math.max(s.length-(n?0:1),1),t._startValue=t.min-(n?.5:0);for(let t=e;t<=i;t++)o.push({value:t});return o}getLabelForValue(t){const e=this.getLabels();return t>=0&&te.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){const e=this;return Math.round(e._startValue+e.getDecimalForPixel(t)*e._valueRange)}getBasePixel(){return this.bottom}}function Es(t,e,{horizontal:i,minRotation:n}){const o=Et(n),s=(i?Math.sin(o):Math.cos(o))||.001,a=.75*e*(""+t).length;return Math.min(e/s,a)}Rs.id="category",Rs.defaults={ticks:{callback:Rs.prototype.getLabelForValue}};class Is extends Xi{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(t,e){return $(t)||("number"==typeof t||t instanceof Number)&&!isFinite(+t)?null:+t}handleTickRangeOptions(){const t=this,{beginAtZero:e}=t.options,{minDefined:i,maxDefined:n}=t.getUserBounds();let{min:o,max:s}=t;const a=t=>o=i?o:t,r=t=>s=n?s:t;if(e){const t=Dt(o),e=Dt(s);t<0&&e<0?r(0):t>0&&e>0&&a(0)}o===s&&(r(s+1),e||a(o-1)),t.min=o,t.max=s}getTickLimit(){const t=this,e=t.options.ticks;let i,{maxTicksLimit:n,stepSize:o}=e;return o?i=Math.ceil(t.max/o)-Math.floor(t.min/o)+1:(i=t.computeTickLimit(),n=n||11),n&&(i=Math.min(n,i)),i}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this,e=t.options,i=e.ticks;let n=t.getTickLimit();n=Math.max(2,n);const o=function(t,e){const i=[],{step:n,min:o,max:s,precision:a,count:r,maxTicks:l,maxDigits:c,includeBounds:h}=t,d=n||1,u=l-1,{min:f,max:g}=e,p=!$(o),m=!$(s),x=!$(r),b=(g-f)/(c+1);let _,y,v,w,M=Ct((g-f)/u/d)*d;if(M<1e-14&&!p&&!m)return[{value:f},{value:g}];w=Math.ceil(g/M)-Math.floor(f/M),w>u&&(M=Ct(w*M/u/d)*d),$(a)||(_=Math.pow(10,a),M=Math.ceil(M*_)/_),y=Math.floor(f/M)*M,v=Math.ceil(g/M)*M,p&&m&&n&&Lt((s-o)/n,M/1e3)?(w=Math.min((s-o)/M,l),M=(s-o)/w,y=o,v=s):x?(y=p?o:y,v=m?s:v,w=r-1,M=(v-y)/w):(w=(v-y)/M,w=Tt(w,Math.round(w),M/1e3)?Math.round(w):Math.ceil(w));const k=Math.max(zt(M),zt(y));_=Math.pow(10,$(a)?k:a),y=Math.round(y*_)/_,v=Math.round(v*_)/_;let S=0;for(p&&(h&&y!==o?(i.push({value:o}),y0?i:null;this._zero=!0}determineDataLimits(){const t=this,{min:e,max:i}=t.getMinMax(!0);t.min=X(e)?Math.max(0,e):null,t.max=X(i)?Math.max(0,i):null,t.options.beginAtZero&&(t._zero=!0),t.handleTickRangeOptions()}handleTickRangeOptions(){const t=this,{minDefined:e,maxDefined:i}=t.getUserBounds();let n=t.min,o=t.max;const s=t=>n=e?n:t,a=t=>o=i?o:t,r=(t,e)=>Math.pow(10,Math.floor(Pt(t))+e);n===o&&(n<=0?(s(1),a(10)):(s(r(n,-1)),a(r(o,1)))),n<=0&&s(r(o,-1)),o<=0&&a(r(n,1)),t._zero&&t.min!==t._suggestedMin&&n===r(t.min,0)&&s(r(n,-1)),t.min=n,t.max=o}buildTicks(){const t=this,e=t.options,i=function(t,e){const i=Math.floor(Pt(e.max)),n=Math.ceil(e.max/Math.pow(10,i)),o=[];let s=q(t.min,Math.pow(10,Math.floor(Pt(e.min)))),a=Math.floor(Pt(s)),r=Math.floor(s/Math.pow(10,a)),l=a<0?Math.pow(10,Math.abs(a)):1;do{o.push({value:s,major:Fs(s)}),++r,10===r&&(r=1,++a,l=a>=0?1:l),s=Math.round(r*Math.pow(10,a)*l)/l}while(ao?{start:e-i,end:e}:{start:e,end:e+i}}function Hs(t){return 0===t||180===t?"center":t<180?"left":"right"}function Ns(t,e,i){90===t||270===t?i.y-=e.h/2:(t>270||t<90)&&(i.y-=e.h)}function js(t,e,i,n){const{ctx:o}=t;if(i)o.arc(t.xCenter,t.yCenter,e,0,_t);else{let i=t.getPointPosition(0,e);o.moveTo(i.x,i.y);for(let s=1;s{const n=Q(e.options.pointLabels.callback,[t,i],e);return n||0===n?n:""}))}fit(){const t=this,e=t.options;e.display&&e.pointLabels.display?function(t){const e={l:0,r:t.width,t:0,b:t.height-t.paddingTop},i={};let n,o,s;const a=[],r=[],l=t.getLabels().length;for(n=0;ne.r&&(e.r=p.end,i.r=f),m.starte.b&&(e.b=m.end,i.b=f)}var c,h,d;t._setReductions(t.drawingArea,e,i),t._pointLabelItems=[];const u=t.options,f=Bs(u),g=t.getDistanceFromCenterForValue(u.ticks.reverse?t.min:t.max);for(n=0;n=0;o--){const e=n.setContext(t.getContext(o)),s=Fe(e.font),{x:a,y:r,textAlign:l,left:c,top:h,right:d,bottom:u}=t._pointLabelItems[o],{backdropColor:f}=e;if(!$(f)){const t=ze(e.backdropPadding);i.fillStyle=f,i.fillRect(c-t.left,h-t.top,d-c+t.width,u-h+t.height)}ee(i,t._pointLabels[o],a,r+s.lineHeight/2,s,{color:e.color,textAlign:l,textBaseline:"middle"})}}(t,s),o.display&&t.ticks.forEach(((e,i)=>{if(0!==i){r=t.getDistanceFromCenterForValue(e.value);const n=o.setContext(t.getContext(i-1));!function(t,e,i,n){const o=t.ctx,s=e.circular,{color:a,lineWidth:r}=e;!s&&!n||!a||!r||i<0||(o.save(),o.strokeStyle=a,o.lineWidth=r,o.setLineDash(e.borderDash),o.lineDashOffset=e.borderDashOffset,o.beginPath(),js(t,i,s,n),o.closePath(),o.stroke(),o.restore())}(t,n,r,s)}})),n.display){for(e.save(),a=t.getLabels().length-1;a>=0;a--){const o=n.setContext(t.getContext(a)),{color:s,lineWidth:c}=o;c&&s&&(e.lineWidth=c,e.strokeStyle=s,e.setLineDash(o.borderDash),e.lineDashOffset=o.borderDashOffset,r=t.getDistanceFromCenterForValue(i.ticks.reverse?t.min:t.max),l=t.getPointPosition(a,r),e.beginPath(),e.moveTo(t.xCenter,t.yCenter),e.lineTo(l.x,l.y),e.stroke())}e.restore()}}drawBorder(){}drawLabels(){const t=this,e=t.ctx,i=t.options,n=i.ticks;if(!n.display)return;const o=t.getIndexAngle(0);let s,a;e.save(),e.translate(t.xCenter,t.yCenter),e.rotate(o),e.textAlign="center",e.textBaseline="middle",t.ticks.forEach(((o,r)=>{if(0===r&&!i.reverse)return;const l=n.setContext(t.getContext(r)),c=Fe(l.font);if(s=t.getDistanceFromCenterForValue(t.ticks[r].value),l.showLabelBackdrop){a=e.measureText(o.label).width,e.fillStyle=l.backdropColor;const t=ze(l.backdropPadding);e.fillRect(-a/2-t.left,-s-c.size/2-t.top,a+t.width,c.size+t.height)}ee(e,o.label,0,-s,c,{color:l.color})})),e.restore()}drawTitle(){}}Ys.id="radialLinear",Ys.defaults={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:Vi.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback:t=>t,padding:5}},Ys.defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"},Ys.descriptors={angleLines:{_fallback:"grid"}};const Us={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Xs=Object.keys(Us);function qs(t,e){return t-e}function Ks(t,e){if($(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:s}=t._parseOpts;let a=e;return"function"==typeof n&&(a=n(a)),X(a)||(a="string"==typeof n?i.parse(a,n):i.parse(a)),null===a?null:(o&&(a="week"!==o||!At(s)&&!0!==s?i.startOf(a,o):i.startOf(a,"isoWeek",s)),+a)}function Gs(t,e,i,n){const o=Xs.length;for(let s=Xs.indexOf(t);s=e?i[n]:i[o]]=!0}}else t[e]=!0}function Qs(t,e,i){const n=[],o={},s=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,n,o,i):n}class Js extends Xi{constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e){const i=t.time||(t.time={}),n=this._adapter=new so._date(t.adapters.date);st(i.displayFormats,n.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Ks(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this,e=t.options,i=t._adapter,n=e.time.unit||"day";let{min:o,max:s,minDefined:a,maxDefined:r}=t.getUserBounds();function l(t){a||isNaN(t.min)||(o=Math.min(o,t.min)),r||isNaN(t.max)||(s=Math.max(s,t.max))}a&&r||(l(t._getLabelBounds()),"ticks"===e.bounds&&"labels"===e.ticks.source||l(t.getMinMax(!1))),o=X(o)&&!isNaN(o)?o:+i.startOf(Date.now(),n),s=X(s)&&!isNaN(s)?s:+i.endOf(Date.now(),n)+1,t.min=Math.min(o,s-1),t.max=Math.max(o+1,s)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this,e=t.options,i=e.time,n=e.ticks,o="labels"===n.source?t.getLabelTimestamps():t._generate();"ticks"===e.bounds&&o.length&&(t.min=t._userMin||o[0],t.max=t._userMax||o[o.length-1]);const s=t.min,a=ae(o,s,t.max);return t._unit=i.unit||(n.autoSkip?Gs(i.minUnit,t.min,t.max,t._getLabelCapacity(s)):function(t,e,i,n,o){for(let s=Xs.length-1;s>=Xs.indexOf(i);s--){const i=Xs[s];if(Us[i].common&&t._adapter.diff(o,n,i)>=e-1)return i}return Xs[i?Xs.indexOf(i):0]}(t,a.length,i.minUnit,t.min,t.max)),t._majorUnit=n.major.enabled&&"year"!==t._unit?function(t){for(let e=Xs.indexOf(t)+1,i=Xs.length;e1e5*r)throw new Error(i+" and "+n+" are too far apart with stepSize of "+r+" "+a);const g="data"===o.ticks.source&&t.getDataTimestamps();for(d=f,u=0;dt-e)).map((t=>+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}_tickFormatFunction(t,e,i,n){const o=this,s=o.options,a=s.time.displayFormats,r=o._unit,l=o._majorUnit,c=r&&a[r],h=l&&a[l],d=i[e],u=l&&h&&d&&d.major,f=o._adapter.format(t,n||(u?h:c)),g=s.ticks.callback;return g?Q(g,[f,e,i],o):f}generateTickLabels(t){let e,i,n;for(e=0,i=t.length;e0?r:1}getDataTimestamps(){const t=this;let e,i,n=t._cache.data||[];if(n.length)return n;const o=t.getMatchingVisibleMetas();if(t._normalized&&o.length)return t._cache.data=o[0].controller.getAllParsedValues(t);for(e=0,i=o.length;ee&&a0&&!$(e)?e/i._maxIndex:i.getDecimalForValue(t);return i.getPixelForDecimal((n.start+o)*n.factor)}getDecimalForValue(t){return ta(this._table,t)/this._maxIndex}getValueForPixel(t){const e=this,i=e._offsets,n=e.getDecimalForPixel(t)/i.factor-i.end;return ta(e._table,n*this._maxIndex,!0)}}ea.id="timeseries",ea.defaults=Js.defaults;var ia=Object.freeze({__proto__:null,CategoryScale:Rs,LinearScale:zs,LogarithmicScale:Vs,RadialLinearScale:Ys,TimeScale:Js,TimeSeriesScale:ea});return to.register(_o,ia,Uo,Ts),to.helpers={...On},to._adapters=so,to.Animation=_i,to.Animations=vi,to.animator=a,to.controllers=Tn.controllers.items,to.DatasetController=Ri,to.Element=Ei,to.elements=Uo,to.Interaction=Oe,to.layouts=Ge,to.platforms=ui,to.Scale=Xi,to.Ticks=Vi,Object.assign(to,_o,ia,Uo,Ts,ui),to.Chart=to,"undefined"!=typeof window&&(window.Chart=to),to})); diff --git a/histview2/static/common/js/all.min.js b/histview2/static/common/js/all.min.js new file mode 100644 index 0000000..0c0aa8b --- /dev/null +++ b/histview2/static/common/js/all.min.js @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +!function(){"use strict";var c={},l={};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document)}catch(c){}var h=(c.navigator||{}).userAgent,a=void 0===h?"":h,z=c,v=l,m=(z.document,!!v.documentElement&&!!v.head&&"function"==typeof v.addEventListener&&v.createElement,~a.indexOf("MSIE")||a.indexOf("Trident/"),"___FONT_AWESOME___"),e=function(){try{return!0}catch(c){return!1}}();var s=z||{};s[m]||(s[m]={}),s[m].styles||(s[m].styles={}),s[m].hooks||(s[m].hooks={}),s[m].shims||(s[m].shims=[]);var t=s[m];function M(c,a){var l=(2>>0;h--;)l[h]=c[h];return l}function Ac(c){return c.classList?bc(c.classList):(c.getAttribute("class")||"").split(" ").filter(function(c){return c})}function gc(c,l){var h,a=l.split("-"),z=a[0],v=a.slice(1).join("-");return z!==c||""===v||(h=v,~T.indexOf(h))?null:v}function Sc(c){return"".concat(c).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function yc(h){return Object.keys(h||{}).reduce(function(c,l){return c+"".concat(l,": ").concat(h[l],";")},"")}function wc(c){return c.size!==Lc.size||c.x!==Lc.x||c.y!==Lc.y||c.rotate!==Lc.rotate||c.flipX||c.flipY}function Zc(c){var l=c.transform,h=c.containerWidth,a=c.iconWidth,z={transform:"translate(".concat(h/2," 256)")},v="translate(".concat(32*l.x,", ").concat(32*l.y,") "),m="scale(".concat(l.size/16*(l.flipX?-1:1),", ").concat(l.size/16*(l.flipY?-1:1),") "),e="rotate(".concat(l.rotate," 0 0)");return{outer:z,inner:{transform:"".concat(v," ").concat(m," ").concat(e)},path:{transform:"translate(".concat(a/2*-1," -256)")}}}var kc={x:0,y:0,width:"100%",height:"100%"};function xc(c){var l=!(1").concat(m.map(Jc).join(""),"")}var $c=function(){};function cl(c){return"string"==typeof(c.getAttribute?c.getAttribute(cc):null)}var ll={replace:function(c){var l=c[0],h=c[1].map(function(c){return Jc(c)}).join("\n");if(l.parentNode&&l.outerHTML)l.outerHTML=h+(lc.keepOriginalSource&&"svg"!==l.tagName.toLowerCase()?"\x3c!-- ".concat(l.outerHTML," Font Awesome fontawesome.com --\x3e"):"");else if(l.parentNode){var a=document.createElement("span");l.parentNode.replaceChild(a,l),a.outerHTML=h}},nest:function(c){var l=c[0],h=c[1];if(~Ac(l).indexOf(lc.replacementClass))return ll.replace(c);var a=new RegExp("".concat(lc.familyPrefix,"-.*"));delete h[0].attributes.style,delete h[0].attributes.id;var z=h[0].attributes.class.split(" ").reduce(function(c,l){return l===lc.replacementClass||l.match(a)?c.toSvg.push(l):c.toNode.push(l),c},{toNode:[],toSvg:[]});h[0].attributes.class=z.toSvg.join(" ");var v=h.map(function(c){return Jc(c)}).join("\n");l.setAttribute("class",z.toNode.join(" ")),l.setAttribute(cc,""),l.innerHTML=v}};function hl(c){c()}function al(h,c){var a="function"==typeof c?c:$c;if(0===h.length)a();else{var l=hl;lc.mutateApproach===y&&(l=o.requestAnimationFrame||hl),l(function(){var c=!0===lc.autoReplaceSvg?ll.replace:ll[lc.autoReplaceSvg]||ll.replace,l=_c.begin("mutate");h.map(c),l(),a()})}}var zl=!1;function vl(){zl=!1}var ml=null;function el(c){if(t&&lc.observeMutations){var z=c.treeCallback,v=c.nodeCallback,m=c.pseudoElementsCallback,l=c.observeMutationsRoot,h=void 0===l?C:l;ml=new t(function(c){zl||bc(c).forEach(function(c){if("childList"===c.type&&0 { + if (serverSentEventCon === undefined || serverSentEventCon === null) { + serverSentEventCon = new EventSource(serverSentEventUrl); + } + + return serverSentEventCon; +}; + +const closeOldConnection = (eventSource) => { + if (eventSource !== undefined && eventSource !== null) { + eventSource.close(); + eventSource = null; + } +}; + +// shutdown app code - SSE +const shutdownAppPolling = () => { + const source = openServerSentEvent(); + source.addEventListener(serverSentEventType.shutDown, (event) => { + const msg = JSON.parse(event.data); + if (msg) { + // show toastr to notify user + showToastrMsg($(baseEles.i18nJobsStopped).text(), $(baseEles.i18nWarningTitle).text()); + + // call API to shutdown app + setTimeout(() => { + fetch('/histview2/api/setting/shutdown', { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }) + .then(response => response.clone().json()) + .then(() => { + // closeOldConnection(source); + }) + .catch(() => { + // closeOldConnection(source); + }); + }, 2000); // wait 2 seconds to let app update 100% in background job page + } + }, false); +}; + +// import job data type error notification +const notifyStatusSSE = () => { + const source = openServerSentEvent(); + + // data type error + source.addEventListener(serverSentEventType.dataTypeErr, (event) => { + const data = JSON.parse(event.data); + if (data) { + let msg = $(baseEles.i18nJobStatusMsg).text(); + msg = msg.replace('__param__', data); + + // show toastr to notify user + showToastrMsg(msg, $(baseEles.i18nWarningTitle).text()); + } + }, false); + + // import empty file + source.addEventListener(serverSentEventType.emptyFile, (event) => { + const data = JSON.parse(event.data); + if (data) { + let msg = $(baseEles.i18nImportEmptyFileMsg).text(); + msg = msg.replace('__param__', `
          ${data.join('
          ')}`); + + // show toast to notify user + showToastrMsg(msg, $(baseEles.i18nWarningTitle).text()); + } + }, false); + + // fetch pca data + source.addEventListener(serverSentEventType.pcaSensor, (event) => { + const data = JSON.parse(event.data); + try { + appendSensors(data); + } catch (e) { + // + } + }, false); + + // fetch pca data + source.addEventListener(serverSentEventType.showGraph, (event) => { + const data = JSON.parse(event.data); + try { + if (data > loadingProgressBackend) { + loadingUpdate(data); + loadingProgressBackend = data; + } + } catch (e) { + // + } + }, false); + + // show warning/error message about disk usage + source.addEventListener(serverSentEventType.diskUsage, (event) => { + const data = JSON.parse(event.data); + checkDiskCapacity(data); + }, false); +}; + +const checkDiskCapacity = (data) => { + const edgeServerType = 'EdgeServer'; + + const isMarqueeMessageDisplay = () => { + const serverTypeList = [edgeServerType]; + for (let i = 0; i < serverTypeList.length; i++) { + const marqueMsgElm = $(`#marquee-msg-${serverTypeList[i].toLowerCase()}`); + if (marqueMsgElm.text() !== '') return true; + } + + return false; + }; + + const showMarqueeMessage = (data) => { + let msg = ''; + let level = ''; + let title = ''; + let show_flag = 'visible'; + + if (data.disk_status.toLowerCase() === 'Warning'.toLowerCase()) { + title += `${data.server_info}`; + level = MESSAGE_LEVEL.WARN; + msg = $(baseEles.i18nWarningFullDiskMsg).text(); + msg = msg.replace('__LIMIT_PERCENT__', data.warning_limit_percent); + } else if (data.disk_status.toLowerCase() === 'Full'.toLowerCase()) { + title += `${data.server_info}`; + level = MESSAGE_LEVEL.ERROR; + msg = $(baseEles.i18nErrorFullDiskMsg).text(); + msg = msg.replace('__LIMIT_PERCENT__', data.error_limit_percent); + } else { // In case of normal, hide marquee message + show_flag = 'hidden'; + } + + const marqueMsgElm = $(`#marquee-msg-${data.server_type.toLowerCase()}`); + const sidebarElm = marqueMsgElm.parents('.sidebar-marquee'); + const marqueeElm = marqueMsgElm.parents('.marquee'); + + sidebarElm.css('visibility', show_flag); // See serverSentEventType.jobRun + marqueeElm.attr('class', 'marquee'); // Remove other class except one + marqueeElm.addClass(level.toLowerCase()); // Add color class base on level + marqueMsgElm.text(msg.replace('__SERVER_INFO__', data.server_info)); + }; + + if (data) { + showMarqueeMessage(data); + } else { + for (const [key, value] of Object.entries(disk_capacity)) { + if (value) showMarqueeMessage(value); + } + } + + // Expend left sidebar when marquee message display + if (isMarqueeMessageDisplay() && !isSidebarOpen()) sidebarCollapse(); +}; + +const addAttributeToElement = (parent = null) => { + // single select2 + setSelect2Selection(parent); + + // normalization + convertTextH2Z(parent); +}; + +// eslint-disable-next-line no-unused-vars +const collapseConfig = () => { + // let config page collapse + const toggleIcon = (e) => { + $(e).addClass(''); + $(e.target) + .prev('.panel-heading') + .parent() + .find('.more-less') + .toggleClass('fa-window-minimize fa-window-maximize'); + }; + // unbind collapse which has been rendered before + $('.panel-group').unbind('hidden.bs.collapse'); + $('.panel-group').unbind('shown.bs.collapse'); + + // bind collapse and change icon + $('.panel-group').on('hidden.bs.collapse', toggleIcon); + $('.panel-group').on('shown.bs.collapse', toggleIcon); +}; + +const toggleToMinIcon = (collapseId) => { + const ele = $(`#${collapseId}`).parents('.card') + .find('.collapse-box') + .find('.more-less'); + ele.removeClass('fa-window-maximize'); + ele.addClass('fa-window-minimize'); +}; + +const toggleToMaxIcon = (collapseId) => { + const ele = $(`#${collapseId}`).parents('.card') + .find('.collapse-box') + .find('.more-less'); + ele.removeClass('fa-window-minimize'); + ele.addClass('fa-window-maximize'); +}; + +const hideContextMenu = () => { + const menuName = '[name=contextMenu]'; + $(menuName).css({ display: 'none' }); +}; + +const handleMouseUp = (e) => { // later, not just mouse down, + mouseout of menu + hideContextMenu(); +}; + +const getMinMaxCard = (collapseId, clickEle = null, name = false) => { + let targetCards; + if (name) { + const eleName = collapseId; + targetCards = $(clickEle).parents('.card').find(`[name=${eleName}]`); + } else { + targetCards = $(`#${collapseId}`); + } + return targetCards; +}; + +const maximizeCard = (collapseId, clickEle = null, name = false) => { + const targetCards = getMinMaxCard(collapseId, clickEle, name); + targetCards.addClass('show'); + targetCards.each((_, e) => { + toggleToMinIcon(e.id); + }); +}; + +const minimizeCard = (collapseId, clickEle = null, name = false) => { + const targetCards = getMinMaxCard(collapseId, clickEle, name); + targetCards.removeClass('show'); + targetCards.each((_, e) => { + toggleToMaxIcon(e.id); + }); +}; + +const baseRightClickHandler = (e) => { + e.preventDefault(); + e.stopPropagation(); + + // show context menu when right click + // const menu = $(procElements.contextMenuId); + const menu = $(e.target).parents('.card').find('[name=contextMenu]'); + const menuHeight = menu.height(); + const windowHeight = $(window).height(); + const left = e.clientX; + let top = e.clientY; + if (windowHeight - top < menuHeight) { + top -= menuHeight; + } + menu.css({ + left: `${left}px`, + top: `${top}px`, + display: 'block', + }); + + return false; +}; + +const showHideShutDownButton = () => { + const hostName = window.location.hostname; + if (!['localhost', '127.0.0.1'].includes(hostName)) { + $(baseEles.shutdownApp).css('display', 'none'); + } +}; + +const sidebarCollapseHandle = () => { + let toutEnter = null; + let toutClick = null; + + const resetTimeoutEvent = () => { + clearTimeout(toutEnter); + clearTimeout(toutClick); + }; + + $(sidebarEles.sidebarCollapseId).on('click', () => { + resetTimeoutEvent(); + toutClick = setTimeout(() => { + clearTimeout(toutEnter); + sidebarCollapse(); + }, 10); + }); + + // open sidebar after 2 secs + $(sidebarEles.sid).on('mouseenter', () => { + resetTimeoutEvent(); + toutEnter = setTimeout(() => { + clearTimeout(toutClick); + $(sidebarEles.dropdownToggle).unbind('mouseenter') + .on('mouseenter', (e) => { + if ($(e.currentTarget).attr('aria-expanded') === 'false') { + $(e.currentTarget).click(); + } + }); + if (!isSidebarOpen()) sidebarCollapse(); + }, 2000); + }); + // close sidebar after 2 secs + $(sidebarEles.sid).on('mouseleave', () => { + resetTimeoutEvent(); + menuCollapse(); + toutEnter = setTimeout(() => { + clearTimeout(toutClick); + if (isSidebarOpen()) closeSidebar(); + }, 2000); + }); + + $(sidebarEles.dropdownToggle).on('click', function () { + const ele = $(this); + if ($(sidebarEles.sid).hasClass('active')) { + sidebarCollapse(ele); + } + }); + $(sidebarEles.dropdownToggle).on('mouseleave', () => { + // menuCollapse(); should be removed. no need + // TODO when submenu is already expanded, mouse enter should not trigger toggle .... + }); +}; + +const autoCheckboxInListItem = () => { + $('.list-group-item').on('click', () => { + console.log('click checkbox'); + }); +}; + +// mark as call page from tile interface, do not apply user setting +const useTileInterface = () => { + const set = () => { + localStorage.setItem('isLoadingFromTitleInterface', true); + return true; + }; + const get = () => { + const isUseTitleInterface = localStorage.getItem('isLoadingFromTitleInterface'); + return !!isUseTitleInterface; + }; + const reset = () => { + localStorage.removeItem('isLoadingFromTitleInterface'); + return null; + }; + return { set, get, reset }; +}; + + +const isLoadingFromTitleInterface = useTileInterface().get(); + +$(() => { + // hide userBookmarkBar + $('#userBookmarkBar').hide(); + + overrideUiSortable(); + + updateI18nCommon(); + + // init fatal error polling + notifyStatusSSE(); + + checkDiskCapacity(); + + SetAppEnv(); + + // click shutdown event + $('body').click((e) => { + if ($(e.target).closest(baseEles.shutdownApp).length) { + $(baseEles.shutdownAppModal).modal('show'); + } + }); + + $(baseEles.btnConfirmShutdownApp).click(() => { + // init shutdown polling + if (isShutdownListening === false) { + shutdownAppPolling(); + isShutdownListening = true; + } + + setTimeout(() => { + // wait for SSE connection was established + + // call API to shutdown + fetch('/histview2/api/setting/stop_job', { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }) + .then(response => response.clone().json()) + .then(() => { + }) + .catch(() => { + }); + }, 1000); + }); + + // single select2 + addAttributeToElement(); + + const loadingSetting = localStorage.getItem('loadingSetting') || null; + if (isLoadingFromTitleInterface) { + // reset global flag after use tile interface + useTileInterface().reset(); + setTimeout(() => { + // save original setting info + originalUserSettingInfo = saveOriginalSetting(); + }, 100); + } else { + // load user input on page load + setTimeout(() => { + // save original setting info + originalUserSettingInfo = saveOriginalSetting(); + if (loadingSetting) { + const loadingSettingObj = JSON.parse(loadingSetting); + const isRedirect = loadingSettingObj.redirect; + if (isRedirect) { + useUserSetting(loadingSettingObj.settingID); + } + } else { + useUserSettingOnLoad(); + } + + // const allTabs = getAllTabs(); + // allTabs.each((_, tab) => setTriggerInvalidFilter(tab)); + }, 100); + } + + + // onChange event for datetime group + setTimeout(() => { + onChangeForDateTimeGroup(); + }, 1000); + + showHideShutDownButton(); + + autoCheckboxInListItem(); + + sidebarCollapseHandle(); + + // trigger auto click show graph + setTimeout(() => { + if (loadingSetting) { + const showGraphBtn = $('button.show-graph'); + // if (showGraphBtn.data('with-tab')) { + // const activeTab = showGraphBtn.closest('.tab-pane.active'); + // if (activeTab) { + // showGraphBtn = $(activeTab).find('.show-graph'); + // } + // } + // console.log(showGraphBtn); + // + // debug mode + const loadingSettingObj = JSON.parse(loadingSetting); + if (loadingSettingObj.isExportMode) { + const input = document.createElement('input'); + input.setAttribute('type', 'hidden'); + input.setAttribute('name', 'isExportMode'); + input.setAttribute('value', loadingSettingObj.settingID); + // append to form element that you want . + $(showGraphBtn).after(input); + } + + if (loadingSettingObj.isImportMode) { + const input = document.createElement('input'); + input.setAttribute('type', 'hidden'); + input.setAttribute('name', 'isImportMode'); + input.setAttribute('value', loadingSettingObj.isImportMode); + // append to form element that you want . + $(showGraphBtn).after(input); + } + + $(showGraphBtn).click(); + clearLoadingSetting(); + } + }, 4000); + + $('[name=pasteCard]').click((e) => { + const divTarget = e.currentTarget.closest('.card-body'); + // console.log(divTarget); + + const sharedSetting = JSON.parse(localStorage.getItem(SHARED_USER_SETTING)); + useUserSetting(null, sharedSetting, divTarget); + + setTimeout(() => { + setTooltip($(e.currentTarget), $(baseEles.i18nPasted).text()); + }, 2000); + }); + + $('[name=pastePage]').click((e) => { + const sharedSetting = JSON.parse(localStorage.getItem(SHARED_USER_SETTING)); + useUserSetting(null, sharedSetting); + + setTimeout(() => { + setTooltip($(e.currentTarget), $(baseEles.i18nPasted).text()); + }, 2000); + }); + + $('[name=copyPage]').click(function () { + let formId = getShowFormId(); + const loadFunc = saveLoadUserInput(`#${formId}`, '', '', SHARED_USER_SETTING); + loadFunc(false); + setTooltip($(this), $(baseEles.i18nCopied).text()); + }); + + // Search table user setting + onSearchTableContent('searchInTopTable', 'tblUserSetting'); +}); + +const initTargetPeriod = () => { + removeDateTimeInList(); + addNewDatTimeRange(); + showDateTimeRangeValue(); + + setTimeout(() => { + $('input[type=text]').trigger('change'); + }, 1000); +}; + +const onSearchTableContent = (inputID, tbID) => { + $(`#${inputID}`).on('input', (e) => { + const { value } = e.currentTarget; + searchTableContent(tbID, value, false); + }); + + $(`#${inputID}`).on('change', (e) => { + const { value } = e.currentTarget; + searchTableContent(tbID, value, true); + }); +}; + +const searchTableContent = (tbID, value, isFilter = true) => { + const regex = new RegExp(value.toLowerCase(), 'i'); + $(`#${tbID} tbody tr`).filter(function () { + let text = ''; + $(this).find('td').each((i, td) => { + const selects = $(td).find('select'); + if (selects.length > 0) { + text += selects.find('option:selected').text(); + } + const input = $(td).find('input'); + if (input.length > 0) { + text += input.val(); + } + + const textArea = $(td).find('textarea'); + if (textArea.length > 0) { + text += textArea.text(); + } + + if (textArea.length === 0 && input.length === 0 && selects.length === 0) { + text += $(td).text(); + } + }); + + if (isFilter) { + $(this).toggle(regex.test(text.toLowerCase())); + } else { + $(this).show(); + if (!regex.test(text)) { + $(this).addClass('gray'); + } else { + $(this).removeClass('gray'); + } + } + }); +}; + + +const handleChangeInterval = (e, to) => { + let currentShower = null; + if (e.checked) { + if (to) { + currentShower = $(`#for-${to}`); + } else { + currentShower = $(`#for-${e.value}`); + } + if (e.value === 'from' || e.value === 'to') { + currentShower = $('#for-fromTo'); + } + + if (currentShower) { + currentShower.show(); + currentShower.find('input').trigger('change'); + currentShower.siblings().hide(); + } + } +}; + +const handleChangeDivideOption = (e) => { + const tabs = []; + e.options.forEach(el => { + tabs.push(el.value); + }); + const currentShower = $(`#for-${e.value}`); + currentShower.removeAttr('style'); + toggleDisableAllInputOfNoneDisplayEl(currentShower, false); + tabs.forEach(tab => { + if (tab !== e.value) { + $(`#for-${tab}`).css({ display: 'none', visibility: 'hidden' }); + toggleDisableAllInputOfNoneDisplayEl($(`#for-${tab}`)); + } + }); + + showDateTimeRangeValue(); +}; + +const toggleDisableAllInputOfNoneDisplayEl = (el, active = true) => { + el.find('input').prop('disabled', active); + el.find('select').prop('disabled', active); +}; + +const removeDateTimeInList = () => { + $('.remove-date').unbind('click'); + $('.remove-date').on('click', (e) => { + $(e.currentTarget).parent().remove(); + // update time range + showDateTimeRangeValue(); + }); +}; + +const addNewDatTimeRange = () => { + $('#termBtnAddDateTime').on('click', () => { + const randomIndex = new Date().getTime(); + const dtId = `datetimeRangePicker${randomIndex}`; + + const newDateHtml = ` +
          + ${dateTimeRangePickerHTML('DATETIME_RANGE_PICKER', dtId, randomIndex, 'False', 'data-gen-btn=termBtnAddDateTime')} + +
          + `; + + $('#datetimeList').append(newDateHtml); + removeDateTimeInList(); + initializeDateTimeRangePicker(dtId); + }); +}; + +const removeUnusedDate = () => { + // remove extra date + const dateGroups = $('.datetimerange-group').find('[name=DATETIME_RANGE_PICKER]'); + dateGroups.each((i, el) => { + const val = el.value; + if (!val) { + $(el).closest('.datetimerange-group').find('.remove-date').trigger('click'); + } + }); +}; + +const getDateTimeRangeValue = () => { + const currentTab = $('select[name=compareType]').val(); + let result = ''; + + if (currentTab === 'var' || currentTab === 'category' || currentTab === 'dataNumberTerm') { + result = calDateTimeRangeForVar(currentTab); + if (result.trim() === DATETIME_PICKER_SEPARATOR.trim()) { + result = `${DEFAULT_START_DATETIME}${DATETIME_PICKER_SEPARATOR}${DEFAULT_END_DATETIME}`; + } + } else if (currentTab === 'cyclicTerm') { + result = calDateTimeRangeForCyclic(currentTab); + } else { + result = calDateTimeRangeForDirectTerm(currentTab); + } + + $('#datetimeRangeShowValue').text(result); +}; + +const showDateTimeRangeValue = () => { + getDateTimeRangeValue(); + $('.to-update-time-range').on('change', (e) => { + getDateTimeRangeValue(); + }); +}; + +const calDateTimeRangeForVar = (currentTab) => { + const currentTargetDiv = $(`#for-${currentTab}`); + const traceOption = currentTargetDiv.find('[name*=varTraceTime]:checked').val(); + const startDate = currentTargetDiv.find('[name=START_DATE]').val(); + const endDate = currentTargetDiv.find('[name=END_DATE]').val(); + const startTime = currentTargetDiv.find('[name=START_TIME]').val(); + const endTime = currentTargetDiv.find('[name=END_TIME]').val(); + const recentTimeInterval = currentTargetDiv.find('[name=recentTimeInterval]').val() || 24; + const timeUnit = currentTargetDiv.find('[name=timeUnit]').val() || 60; + + if (traceOption === TRACE_TIME_CONST.RECENT) { + const timeDiffMinute = Number(recentTimeInterval) * Number(timeUnit); + const newStartDate = moment().add(-timeDiffMinute, 'minute').format(DATE_FORMAT); + const newStartTime = moment().add(-timeDiffMinute, 'minute').format(TIME_FORMAT); + const newEndDate = moment().format(DATE_FORMAT); + const newEndTime = moment().format(TIME_FORMAT); + return `${newStartDate} ${newStartTime}${DATETIME_PICKER_SEPARATOR}${newEndDate} ${newEndTime}`; + } + return `${startDate} ${startTime}${DATETIME_PICKER_SEPARATOR}${endDate} ${endTime}`; +}; + +const calDateTimeRangeForCyclic = (currentTab) => { + const currentTargetDiv = $(`#for-${currentTab}`); + + const traceTimeOption = currentTargetDiv.find('[name=cyclicTermTraceTime1]:checked').val(); + const divisionNum = currentTargetDiv.find('[name=cyclicTermDivNum]').val(); + const intervalNum = currentTargetDiv.find('[name=cyclicTermInterval]').val(); + const windowsLengthNum = currentTargetDiv.find('[name=cyclicTermWindowLength]').val(); + + + const targetDate = traceTimeOption === TRACE_TIME_CONST.RECENT + ? moment().format('YYYY-MM-DD') : currentTargetDiv.find('[name=START_DATE]').val(); + const targetTime = traceTimeOption === TRACE_TIME_CONST.RECENT + ? moment().format('HH:mm') : currentTargetDiv.find('[name=START_TIME]').val(); + + + const [startTimeRange, endTimeRange] = traceTimeOption === TRACE_TIME_CONST.FROM ? getEndTimeRange( + targetDate, targetTime, divisionNum, intervalNum, windowsLengthNum, + ) : getStartTimeRange( + traceTimeOption, targetDate, targetTime, divisionNum, intervalNum, windowsLengthNum, + ); + + return `${startTimeRange[0]} ${startTimeRange[1]}${DATETIME_PICKER_SEPARATOR}${endTimeRange[0]} ${endTimeRange[1]}`; +}; + +const getEndTimeRange = (targetDate, targetTime, divisionNum, intervalNum, windowsLengthNum) => { + const MILSEC = 60 * 60 * 1000; + const startDateTimeMil = moment(`${targetDate} ${targetTime}`).valueOf(); + const endDateTimeMil = startDateTimeMil + ((divisionNum - 1) * intervalNum * MILSEC) + (windowsLengthNum * MILSEC); + + return [ + [ + targetDate, + targetTime, + ], + [ + moment(endDateTimeMil).format(DATE_FORMAT), + moment(endDateTimeMil).format(TIME_FORMAT), + ], + ]; +}; +const getStartTimeRange = (traceTimeOpt, targetDate, targetTime, divisionNum, intervalNum, windowsLengthNum) => { + const MILSEC = 60 * 60 * 1000; + const endDateTimeMil = moment(`${targetDate} ${targetTime}`).valueOf(); + const startDateTimeMil = endDateTimeMil - ((divisionNum - 1) * intervalNum * MILSEC) - (windowsLengthNum * MILSEC); + + // default as RECENT type + let endTimeRange = [ + moment().format(DATE_FORMAT), + moment().format(TIME_FORMAT), + ]; + if (traceTimeOpt === TRACE_TIME_CONST.TO) { + endTimeRange = [ + targetDate, + targetTime, + ]; + } + return [ + [ + moment(startDateTimeMil).format(DATE_FORMAT), + moment(startDateTimeMil).format(TIME_FORMAT), + ], + endTimeRange, + ]; +}; + +const calDateTimeRangeForDirectTerm = (currentTab) => { + const currentTargetDiv = $(`#for-${currentTab}`); + const starts = []; + const ends = []; + currentTargetDiv.find('[name=DATETIME_RANGE_PICKER]').each((i, dt) => { + const [start, end] = dt.value.split(DATETIME_PICKER_SEPARATOR); + if (start) { + starts.push(new Date(start)); + } + if (end) { + ends.push(new Date(end)); + } + }); + + const [minOfStart] = findMinMax(starts); + const [, maxOfEnd] = findMinMax(ends); + const minDate = minOfStart ? moment(Math.min(...starts)).format(DATETIME_PICKER_FORMAT) : ''; + const maxDate = maxOfEnd ? moment(Math.max(...ends)).format(DATETIME_PICKER_FORMAT) : ''; + + return `${minDate}${DATETIME_PICKER_SEPARATOR}${maxDate}`; +}; + +const SetAppEnv = () => { + const env = localStorage.getItem('env'); + if (env) return; + $.get('/histview2/api/setting/get_env', (resp) => { + localStorage.setItem('env', resp.env); + }); +}; + +const setRequestTimeOut = (timeout = 60000) => { + const env = localStorage.getItem('env'); + return env === 'prod' ? timeout : 60000000; +}; diff --git a/histview2/static/common/js/bootstrap-select.min.js b/histview2/static/common/js/bootstrap-select.min.js new file mode 100644 index 0000000..e1965dd --- /dev/null +++ b/histview2/static/common/js/bootstrap-select.min.js @@ -0,0 +1,9 @@ +/*! + * Bootstrap-select v1.12.4 (http://silviomoreto.github.io/bootstrap-select) + * + * Copyright 2013-2017 bootstrap-select + * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) + */ +!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof module&&module.exports?module.exports=b(require("jquery")):b(a.jQuery)}(this,function(a){!function(a){"use strict";function b(b){var c=[{re:/[\xC0-\xC6]/g,ch:"A"},{re:/[\xE0-\xE6]/g,ch:"a"},{re:/[\xC8-\xCB]/g,ch:"E"},{re:/[\xE8-\xEB]/g,ch:"e"},{re:/[\xCC-\xCF]/g,ch:"I"},{re:/[\xEC-\xEF]/g,ch:"i"},{re:/[\xD2-\xD6]/g,ch:"O"},{re:/[\xF2-\xF6]/g,ch:"o"},{re:/[\xD9-\xDC]/g,ch:"U"},{re:/[\xF9-\xFC]/g,ch:"u"},{re:/[\xC7-\xE7]/g,ch:"c"},{re:/[\xD1]/g,ch:"N"},{re:/[\xF1]/g,ch:"n"}];return a.each(c,function(){b=b?b.replace(this.re,this.ch):""}),b}function c(b){var c=arguments,d=b;[].shift.apply(c);var e,f=this.each(function(){var b=a(this);if(b.is("select")){var f=b.data("selectpicker"),g="object"==typeof d&&d;if(f){if(g)for(var h in g)g.hasOwnProperty(h)&&(f.options[h]=g[h])}else{var i=a.extend({},l.DEFAULTS,a.fn.selectpicker.defaults||{},b.data(),g);i.template=a.extend({},l.DEFAULTS.template,a.fn.selectpicker.defaults?a.fn.selectpicker.defaults.template:{},b.data().template,g.template),b.data("selectpicker",f=new l(this,i))}"string"==typeof d&&(e=f[d]instanceof Function?f[d].apply(f,c):f.options[d])}});return"undefined"!=typeof e?e:f}String.prototype.includes||!function(){var a={}.toString,b=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),c="".indexOf,d=function(b){if(null==this)throw new TypeError;var d=String(this);if(b&&"[object RegExp]"==a.call(b))throw new TypeError;var e=d.length,f=String(b),g=f.length,h=arguments.length>1?arguments[1]:void 0,i=h?Number(h):0;i!=i&&(i=0);var j=Math.min(Math.max(i,0),e);return!(g+j>e)&&c.call(d,f,i)!=-1};b?b(String.prototype,"includes",{value:d,configurable:!0,writable:!0}):String.prototype.includes=d}(),String.prototype.startsWith||!function(){var a=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),b={}.toString,c=function(a){if(null==this)throw new TypeError;var c=String(this);if(a&&"[object RegExp]"==b.call(a))throw new TypeError;var d=c.length,e=String(a),f=e.length,g=arguments.length>1?arguments[1]:void 0,h=g?Number(g):0;h!=h&&(h=0);var i=Math.min(Math.max(h,0),d);if(f+i>d)return!1;for(var j=-1;++j":">",'"':""","'":"'","`":"`"},h={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},i=function(a){var b=function(b){return a[b]},c="(?:"+Object.keys(a).join("|")+")",d=RegExp(c),e=RegExp(c,"g");return function(a){return a=null==a?"":""+a,d.test(a)?a.replace(e,b):a}},j=i(g),k=i(h),l=function(b,c){d.useDefault||(a.valHooks.select.set=d._set,d.useDefault=!0),this.$element=a(b),this.$newElement=null,this.$button=null,this.$menu=null,this.$lis=null,this.options=c,null===this.options.title&&(this.options.title=this.$element.attr("title"));var e=this.options.windowPadding;"number"==typeof e&&(this.options.windowPadding=[e,e,e,e]),this.val=l.prototype.val,this.render=l.prototype.render,this.refresh=l.prototype.refresh,this.setStyle=l.prototype.setStyle,this.selectAll=l.prototype.selectAll,this.deselectAll=l.prototype.deselectAll,this.destroy=l.prototype.destroy,this.remove=l.prototype.remove,this.show=l.prototype.show,this.hide=l.prototype.hide,this.init()};l.VERSION="1.12.4",l.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(a,b){return 1==a?"{0} item selected":"{0} items selected"},maxOptionsText:function(a,b){return[1==a?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==b?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)"]},selectAllText:"Select All",deselectAllText:"Deselect All",doneButton:!1,doneButtonText:"Close",multipleSeparator:", ",styleBase:"btn",style:"btn-default",size:"auto",title:null,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,liveSearchPlaceholder:null,liveSearchNormalize:!1,liveSearchStyle:"contains",actionsBox:!1,iconBase:"glyphicon",tickIcon:"glyphicon-ok",showTick:!1,template:{caret:''},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0},l.prototype={constructor:l,init:function(){var b=this,c=this.$element.attr("id");this.$element.addClass("bs-select-hidden"),this.liObj={},this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$newElement=this.createView(),this.$element.after(this.$newElement).appendTo(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(".dropdown-menu"),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.$element.removeClass("bs-select-hidden"),this.options.dropdownAlignRight===!0&&this.$menu.addClass("dropdown-menu-right"),"undefined"!=typeof c&&(this.$button.attr("data-id",c),a('label[for="'+c+'"]').click(function(a){a.preventDefault(),b.$button.focus()})),this.checkDisabled(),this.clickListener(),this.options.liveSearch&&this.liveSearchListener(),this.render(),this.setStyle(),this.setWidth(),this.options.container&&this.selectPosition(),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(a){b.$menuInner.attr("aria-expanded",!1),b.$element.trigger("hide.bs.select",a)},"hidden.bs.dropdown":function(a){b.$element.trigger("hidden.bs.select",a)},"show.bs.dropdown":function(a){b.$menuInner.attr("aria-expanded",!0),b.$element.trigger("show.bs.select",a)},"shown.bs.dropdown":function(a){b.$element.trigger("shown.bs.select",a)}}),b.$element[0].hasAttribute("required")&&this.$element.on("invalid",function(){b.$button.addClass("bs-invalid"),b.$element.on({"focus.bs.select":function(){b.$button.focus(),b.$element.off("focus.bs.select")},"shown.bs.select":function(){b.$element.val(b.$element.val()).off("shown.bs.select")},"rendered.bs.select":function(){this.validity.valid&&b.$button.removeClass("bs-invalid"),b.$element.off("rendered.bs.select")}}),b.$button.on("blur.bs.select",function(){b.$element.focus().blur(),b.$button.off("blur.bs.select")})}),setTimeout(function(){b.$element.trigger("loaded.bs.select")})},createDropdown:function(){var b=this.multiple||this.options.showTick?" show-tick":"",c=this.$element.parent().hasClass("input-group")?" input-group-btn":"",d=this.autofocus?" autofocus":"",e=this.options.header?'
          '+this.options.header+"
          ":"",f=this.options.liveSearch?'':"",g=this.multiple&&this.options.actionsBox?'
          ":"",h=this.multiple&&this.options.doneButton?'
          ":"",i='
          ";return a(i)},createView:function(){var a=this.createDropdown(),b=this.createLi();return a.find("ul")[0].innerHTML=b,a},reloadLi:function(){var a=this.createLi();this.$menuInner[0].innerHTML=a},createLi:function(){var c=this,d=[],e=0,f=document.createElement("option"),g=-1,h=function(a,b,c,d){return""+a+""},i=function(d,e,f,g){return''+d+''};if(this.options.title&&!this.multiple&&(g--,!this.$element.find(".bs-title-option").length)){var k=this.$element[0];f.className="bs-title-option",f.innerHTML=this.options.title,f.value="",k.insertBefore(f,k.firstChild);var l=a(k.options[k.selectedIndex]);void 0===l.attr("selected")&&void 0===this.$element.data("selected")&&(f.selected=!0)}var m=this.$element.find("option");return m.each(function(b){var f=a(this);if(g++,!f.hasClass("bs-title-option")){var k,l=this.className||"",n=j(this.style.cssText),o=f.data("content")?f.data("content"):f.html(),p=f.data("tokens")?f.data("tokens"):null,q="undefined"!=typeof f.data("subtext")?''+f.data("subtext")+"":"",r="undefined"!=typeof f.data("icon")?' ':"",s=f.parent(),t="OPTGROUP"===s[0].tagName,u=t&&s[0].disabled,v=this.disabled||u;if(""!==r&&v&&(r=""+r+""),c.options.hideDisabled&&(v&&!t||u))return k=f.data("prevHiddenIndex"),f.next().data("prevHiddenIndex",void 0!==k?k:b),void g--;if(f.data("content")||(o=r+''+o+q+""),t&&f.data("divider")!==!0){if(c.options.hideDisabled&&v){if(void 0===s.data("allOptionsDisabled")){var w=s.children();s.data("allOptionsDisabled",w.filter(":disabled").length===w.length)}if(s.data("allOptionsDisabled"))return void g--}var x=" "+s[0].className||"";if(0===f.index()){e+=1;var y=s[0].label,z="undefined"!=typeof s.data("subtext")?''+s.data("subtext")+"":"",A=s.data("icon")?' ':"";y=A+''+j(y)+z+"",0!==b&&d.length>0&&(g++,d.push(h("",null,"divider",e+"div"))),g++,d.push(h(y,null,"dropdown-header"+x,e))}if(c.options.hideDisabled&&v)return void g--;d.push(h(i(o,"opt "+l+x,n,p),b,"",e))}else if(f.data("divider")===!0)d.push(h("",b,"divider"));else if(f.data("hidden")===!0)k=f.data("prevHiddenIndex"),f.next().data("prevHiddenIndex",void 0!==k?k:b),d.push(h(i(o,l,n,p),b,"hidden is-hidden"));else{var B=this.previousElementSibling&&"OPTGROUP"===this.previousElementSibling.tagName;if(!B&&c.options.hideDisabled&&(k=f.data("prevHiddenIndex"),void 0!==k)){var C=m.eq(k)[0].previousElementSibling;C&&"OPTGROUP"===C.tagName&&!C.disabled&&(B=!0)}B&&(g++,d.push(h("",null,"divider",e+"div"))),d.push(h(i(o,l,n,p),b))}c.liObj[b]=g}}),this.multiple||0!==this.$element.find("option:selected").length||this.options.title||this.$element.find("option").eq(0).prop("selected",!0).attr("selected","selected"),d.join("")},findLis:function(){return null==this.$lis&&(this.$lis=this.$menu.find("li")),this.$lis},render:function(b){var c,d=this,e=this.$element.find("option");b!==!1&&e.each(function(a){var b=d.findLis().eq(d.liObj[a]);d.setDisabled(a,this.disabled||"OPTGROUP"===this.parentNode.tagName&&this.parentNode.disabled,b),d.setSelected(a,this.selected,b)}),this.togglePlaceholder(),this.tabIndex();var f=e.map(function(){if(this.selected){if(d.options.hideDisabled&&(this.disabled||"OPTGROUP"===this.parentNode.tagName&&this.parentNode.disabled))return;var b,c=a(this),e=c.data("icon")&&d.options.showIcon?' ':"";return b=d.options.showSubtext&&c.data("subtext")&&!d.multiple?' '+c.data("subtext")+"":"","undefined"!=typeof c.attr("title")?c.attr("title"):c.data("content")&&d.options.showContent?c.data("content").toString():e+c.html()+b}}).toArray(),g=this.multiple?f.join(this.options.multipleSeparator):f[0];if(this.multiple&&this.options.selectedTextFormat.indexOf("count")>-1){var h=this.options.selectedTextFormat.split(">");if(h.length>1&&f.length>h[1]||1==h.length&&f.length>=2){c=this.options.hideDisabled?", [disabled]":"";var i=e.not('[data-divider="true"], [data-hidden="true"]'+c).length,j="function"==typeof this.options.countSelectedText?this.options.countSelectedText(f.length,i):this.options.countSelectedText;g=j.replace("{0}",f.length.toString()).replace("{1}",i.toString())}}void 0==this.options.title&&(this.options.title=this.$element.attr("title")),"static"==this.options.selectedTextFormat&&(g=this.options.title),g||(g="undefined"!=typeof this.options.title?this.options.title:this.options.noneSelectedText),this.$button.attr("title",k(a.trim(g.replace(/<[^>]*>?/g,"")))),this.$button.children(".filter-option").html(g),this.$element.trigger("rendered.bs.select")},setStyle:function(a,b){this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,""));var c=a?a:this.options.style;"add"==b?this.$button.addClass(c):"remove"==b?this.$button.removeClass(c):(this.$button.removeClass(this.options.style),this.$button.addClass(c))},liHeight:function(b){if(b||this.options.size!==!1&&!this.sizeInfo){var c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("ul"),f=document.createElement("li"),g=document.createElement("li"),h=document.createElement("a"),i=document.createElement("span"),j=this.options.header&&this.$menu.find(".popover-title").length>0?this.$menu.find(".popover-title")[0].cloneNode(!0):null,k=this.options.liveSearch?document.createElement("div"):null,l=this.options.actionsBox&&this.multiple&&this.$menu.find(".bs-actionsbox").length>0?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,m=this.options.doneButton&&this.multiple&&this.$menu.find(".bs-donebutton").length>0?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null;if(i.className="text",c.className=this.$menu[0].parentNode.className+" open",d.className="dropdown-menu open",e.className="dropdown-menu inner",f.className="divider",i.appendChild(document.createTextNode("Inner text")),h.appendChild(i),g.appendChild(h),e.appendChild(g),e.appendChild(f),j&&d.appendChild(j),k){var n=document.createElement("input");k.className="bs-searchbox",n.className="form-control",k.appendChild(n),d.appendChild(k)}l&&d.appendChild(l),d.appendChild(e),m&&d.appendChild(m),c.appendChild(d),document.body.appendChild(c);var o=h.offsetHeight,p=j?j.offsetHeight:0,q=k?k.offsetHeight:0,r=l?l.offsetHeight:0,s=m?m.offsetHeight:0,t=a(f).outerHeight(!0),u="function"==typeof getComputedStyle&&getComputedStyle(d),v=u?null:a(d),w={vert:parseInt(u?u.paddingTop:v.css("paddingTop"))+parseInt(u?u.paddingBottom:v.css("paddingBottom"))+parseInt(u?u.borderTopWidth:v.css("borderTopWidth"))+parseInt(u?u.borderBottomWidth:v.css("borderBottomWidth")),horiz:parseInt(u?u.paddingLeft:v.css("paddingLeft"))+parseInt(u?u.paddingRight:v.css("paddingRight"))+parseInt(u?u.borderLeftWidth:v.css("borderLeftWidth"))+parseInt(u?u.borderRightWidth:v.css("borderRightWidth"))},x={vert:w.vert+parseInt(u?u.marginTop:v.css("marginTop"))+parseInt(u?u.marginBottom:v.css("marginBottom"))+2,horiz:w.horiz+parseInt(u?u.marginLeft:v.css("marginLeft"))+parseInt(u?u.marginRight:v.css("marginRight"))+2};document.body.removeChild(c),this.sizeInfo={liHeight:o,headerHeight:p,searchHeight:q,actionsHeight:r,doneButtonHeight:s,dividerHeight:t,menuPadding:w,menuExtras:x}}},setSize:function(){if(this.findLis(),this.liHeight(),this.options.header&&this.$menu.css("padding-top",0),this.options.size!==!1){var b,c,d,e,f,g,h,i,j=this,k=this.$menu,l=this.$menuInner,m=a(window),n=this.$newElement[0].offsetHeight,o=this.$newElement[0].offsetWidth,p=this.sizeInfo.liHeight,q=this.sizeInfo.headerHeight,r=this.sizeInfo.searchHeight,s=this.sizeInfo.actionsHeight,t=this.sizeInfo.doneButtonHeight,u=this.sizeInfo.dividerHeight,v=this.sizeInfo.menuPadding,w=this.sizeInfo.menuExtras,x=this.options.hideDisabled?".disabled":"",y=function(){var b,c=j.$newElement.offset(),d=a(j.options.container);j.options.container&&!d.is("body")?(b=d.offset(),b.top+=parseInt(d.css("borderTopWidth")),b.left+=parseInt(d.css("borderLeftWidth"))):b={top:0,left:0};var e=j.options.windowPadding;f=c.top-b.top-m.scrollTop(),g=m.height()-f-n-b.top-e[2],h=c.left-b.left-m.scrollLeft(),i=m.width()-h-o-b.left-e[1],f-=e[0],h-=e[3]};if(y(),"auto"===this.options.size){var z=function(){var m,n=function(b,c){return function(d){return c?d.classList?d.classList.contains(b):a(d).hasClass(b):!(d.classList?d.classList.contains(b):a(d).hasClass(b))}},u=j.$menuInner[0].getElementsByTagName("li"),x=Array.prototype.filter?Array.prototype.filter.call(u,n("hidden",!1)):j.$lis.not(".hidden"),z=Array.prototype.filter?Array.prototype.filter.call(x,n("dropdown-header",!0)):x.filter(".dropdown-header");y(),b=g-w.vert,c=i-w.horiz,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height"),k.data("width")||k.data("width",k.width()),e=k.data("width")):(d=k.height(),e=k.width()),j.options.dropupAuto&&j.$newElement.toggleClass("dropup",f>g&&b-w.verti&&c-w.horiz3?3*p+w.vert-2:0,k.css({"max-height":b+"px",overflow:"hidden","min-height":m+q+r+s+t+"px"}),l.css({"max-height":b-q-r-s-t-v.vert+"px","overflow-y":"auto","min-height":Math.max(m-v.vert,0)+"px"})};z(),this.$searchbox.off("input.getSize propertychange.getSize").on("input.getSize propertychange.getSize",z),m.off("resize.getSize scroll.getSize").on("resize.getSize scroll.getSize",z)}else if(this.options.size&&"auto"!=this.options.size&&this.$lis.not(x).length>this.options.size){var A=this.$lis.not(".divider").not(x).children().slice(0,this.options.size).last().parent().index(),B=this.$lis.slice(0,A+1).filter(".divider").length;b=p*this.options.size+B*u+v.vert,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height")):d=k.height(),j.options.dropupAuto&&this.$newElement.toggleClass("dropup",f>g&&b-w.vert');var b,c,d,e=this,f=a(this.options.container),g=function(a){e.$bsContainer.addClass(a.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass("dropup",a.hasClass("dropup")),b=a.offset(),f.is("body")?c={top:0,left:0}:(c=f.offset(),c.top+=parseInt(f.css("borderTopWidth"))-f.scrollTop(),c.left+=parseInt(f.css("borderLeftWidth"))-f.scrollLeft()),d=a.hasClass("dropup")?0:a[0].offsetHeight,e.$bsContainer.css({top:b.top-c.top+d,left:b.left-c.left,width:a[0].offsetWidth})};this.$button.on("click",function(){var b=a(this);e.isDisabled()||(g(e.$newElement),e.$bsContainer.appendTo(e.options.container).toggleClass("open",!b.hasClass("open")).append(e.$menu))}),a(window).on("resize scroll",function(){g(e.$newElement)}),this.$element.on("hide.bs.select",function(){e.$menu.data("height",e.$menu.height()),e.$bsContainer.detach()})},setSelected:function(a,b,c){c||(this.togglePlaceholder(),c=this.findLis().eq(this.liObj[a])),c.toggleClass("selected",b).find("a").attr("aria-selected",b)},setDisabled:function(a,b,c){c||(c=this.findLis().eq(this.liObj[a])),b?c.addClass("disabled").children("a").attr("href","#").attr("tabindex",-1).attr("aria-disabled",!0):c.removeClass("disabled").children("a").removeAttr("href").attr("tabindex",0).attr("aria-disabled",!1)},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){var a=this;this.isDisabled()?(this.$newElement.addClass("disabled"),this.$button.addClass("disabled").attr("tabindex",-1).attr("aria-disabled",!0)):(this.$button.hasClass("disabled")&&(this.$newElement.removeClass("disabled"),this.$button.removeClass("disabled").attr("aria-disabled",!1)),this.$button.attr("tabindex")!=-1||this.$element.data("tabindex")||this.$button.removeAttr("tabindex")),this.$button.click(function(){return!a.isDisabled()})},togglePlaceholder:function(){var a=this.$element.val();this.$button.toggleClass("bs-placeholder",null===a||""===a||a.constructor===Array&&0===a.length)},tabIndex:function(){this.$element.data("tabindex")!==this.$element.attr("tabindex")&&this.$element.attr("tabindex")!==-98&&"-98"!==this.$element.attr("tabindex")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex"))),this.$element.attr("tabindex",-98)},clickListener:function(){var b=this,c=a(document);c.data("spaceSelect",!1),this.$button.on("keyup",function(a){/(32)/.test(a.keyCode.toString(10))&&c.data("spaceSelect")&&(a.preventDefault(),c.data("spaceSelect",!1))}),this.$button.on("click",function(){b.setSize()}),this.$element.on("shown.bs.select",function(){if(b.options.liveSearch||b.multiple){if(!b.multiple){var a=b.liObj[b.$element[0].selectedIndex];if("number"!=typeof a||b.options.size===!1)return;var c=b.$lis.eq(a)[0].offsetTop-b.$menuInner[0].offsetTop;c=c-b.$menuInner[0].offsetHeight/2+b.sizeInfo.liHeight/2,b.$menuInner[0].scrollTop=c}}else b.$menuInner.find(".selected a").focus()}),this.$menuInner.on("click","li a",function(c){var d=a(this),f=d.parent().data("originalIndex"),g=b.$element.val(),h=b.$element.prop("selectedIndex"),i=!0;if(b.multiple&&1!==b.options.maxOptions&&c.stopPropagation(),c.preventDefault(),!b.isDisabled()&&!d.parent().hasClass("disabled")){var j=b.$element.find("option"),k=j.eq(f),l=k.prop("selected"),m=k.parent("optgroup"),n=b.options.maxOptions,o=m.data("maxOptions")||!1;if(b.multiple){if(k.prop("selected",!l),b.setSelected(f,!l),d.blur(),n!==!1||o!==!1){var p=n
          ');t[2]&&(u=u.replace("{var}",t[2][n>1?0:1]),v=v.replace("{var}",t[2][o>1?0:1])),k.prop("selected",!1),b.$menu.append(w),n&&p&&(w.append(a("
          "+u+"
          ")),i=!1,b.$element.trigger("maxReached.bs.select")),o&&q&&(w.append(a("
          "+v+"
          ")),i=!1,b.$element.trigger("maxReachedGrp.bs.select")),setTimeout(function(){b.setSelected(f,!1)},10),w.delay(750).fadeOut(300,function(){a(this).remove()})}}}else j.prop("selected",!1),k.prop("selected",!0),b.$menuInner.find(".selected").removeClass("selected").find("a").attr("aria-selected",!1),b.setSelected(f,!0);!b.multiple||b.multiple&&1===b.options.maxOptions?b.$button.focus():b.options.liveSearch&&b.$searchbox.focus(),i&&(g!=b.$element.val()&&b.multiple||h!=b.$element.prop("selectedIndex")&&!b.multiple)&&(e=[f,k.prop("selected"),l],b.$element.triggerNative("change"))}}),this.$menu.on("click","li.disabled a, .popover-title, .popover-title :not(.close)",function(c){c.currentTarget==this&&(c.preventDefault(),c.stopPropagation(),b.options.liveSearch&&!a(c.target).hasClass("close")?b.$searchbox.focus():b.$button.focus())}),this.$menuInner.on("click",".divider, .dropdown-header",function(a){a.preventDefault(),a.stopPropagation(),b.options.liveSearch?b.$searchbox.focus():b.$button.focus()}),this.$menu.on("click",".popover-title .close",function(){b.$button.click()}),this.$searchbox.on("click",function(a){a.stopPropagation()}),this.$menu.on("click",".actions-btn",function(c){b.options.liveSearch?b.$searchbox.focus():b.$button.focus(),c.preventDefault(),c.stopPropagation(),a(this).hasClass("bs-select-all")?b.selectAll():b.deselectAll()}),this.$element.change(function(){b.render(!1),b.$element.trigger("changed.bs.select",e),e=null})},liveSearchListener:function(){var c=this,d=a('
        1. ');this.$button.on("click.dropdown.data-api",function(){c.$menuInner.find(".active").removeClass("active"),c.$searchbox.val()&&(c.$searchbox.val(""),c.$lis.not(".is-hidden").removeClass("hidden"),d.parent().length&&d.remove()),c.multiple||c.$menuInner.find(".selected").addClass("active"),setTimeout(function(){c.$searchbox.focus()},10)}),this.$searchbox.on("click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api",function(a){a.stopPropagation()}),this.$searchbox.on("input propertychange",function(){if(c.$lis.not(".is-hidden").removeClass("hidden"),c.$lis.filter(".active").removeClass("active"),d.remove(),c.$searchbox.val()){var e,f=c.$lis.not(".is-hidden, .divider, .dropdown-header");if(e=c.options.liveSearchNormalize?f.not(":a"+c._searchStyle()+'("'+b(c.$searchbox.val())+'")'):f.not(":"+c._searchStyle()+'("'+c.$searchbox.val()+'")'),e.length===f.length)d.html(c.options.noneResultsText.replace("{0}",'"'+j(c.$searchbox.val())+'"')),c.$menuInner.append(d),c.$lis.addClass("hidden");else{e.addClass("hidden");var g,h=c.$lis.not(".hidden");h.each(function(b){var c=a(this);c.hasClass("divider")?void 0===g?c.addClass("hidden"):(g&&g.addClass("hidden"),g=c):c.hasClass("dropdown-header")&&h.eq(b+1).data("optgroup")!==c.data("optgroup")?c.addClass("hidden"):g=null}),g&&g.addClass("hidden"),f.not(".hidden").first().addClass("active"),c.$menuInner.scrollTop(0)}}})},_searchStyle:function(){var a={begins:"ibegins",startsWith:"ibegins"};return a[this.options.liveSearchStyle]||"icontains"},val:function(a){return"undefined"!=typeof a?(this.$element.val(a),this.render(),this.$element):this.$element.val()},changeAll:function(b){if(this.multiple){"undefined"==typeof b&&(b=!0),this.findLis();var c=this.$element.find("option"),d=this.$lis.not(".divider, .dropdown-header, .disabled, .hidden"),e=d.length,f=[];if(b){if(d.filter(".selected").length===d.length)return}else if(0===d.filter(".selected").length)return;d.toggleClass("selected",b);for(var g=0;g=48&&b.keyCode<=57||b.keyCode>=96&&b.keyCode<=105||b.keyCode>=65&&b.keyCode<=90))return i.options.container?i.$button.trigger("click"):(i.setSize(),i.$menu.parent().addClass("open"),f=!0),void i.$searchbox.focus();if(i.options.liveSearch&&/(^9$|27)/.test(b.keyCode.toString(10))&&f&&(b.preventDefault(),b.stopPropagation(),i.$menuInner.click(),i.$button.focus()),/(38|40)/.test(b.keyCode.toString(10))){if(c=i.$lis.filter(j),!c.length)return;d=i.options.liveSearch?c.index(c.filter(".active")):c.index(c.find("a").filter(":focus").parent()),e=i.$menuInner.data("prevIndex"),38==b.keyCode?(!i.options.liveSearch&&d!=e||d==-1||d--,d<0&&(d+=c.length)):40==b.keyCode&&((i.options.liveSearch||d==e)&&d++,d%=c.length),i.$menuInner.data("prevIndex",d),i.options.liveSearch?(b.preventDefault(),g.hasClass("dropdown-toggle")||(c.removeClass("active").eq(d).addClass("active").children("a").focus(),g.focus())):c.eq(d).children("a").focus()}else if(!g.is("input")){var l,m,n=[];c=i.$lis.filter(j),c.each(function(c){a.trim(a(this).children("a").text().toLowerCase()).substring(0,1)==k[b.keyCode]&&n.push(c)}),l=a(document).data("keycount"),l++,a(document).data("keycount",l),m=a.trim(a(":focus").text().toLowerCase()).substring(0,1),m!=k[b.keyCode]?(l=1,a(document).data("keycount",l)):l>=n.length&&(a(document).data("keycount",0),l>n.length&&(l=1)),c.eq(n[l-1]).children("a").focus()}if((/(13|32)/.test(b.keyCode.toString(10))||/(^9$)/.test(b.keyCode.toString(10))&&i.options.selectOnTab)&&f){if(/(32)/.test(b.keyCode.toString(10))||b.preventDefault(),i.options.liveSearch)/(32)/.test(b.keyCode.toString(10))||(i.$menuInner.find(".active a").click(),g.focus());else{var o=a(":focus");o.click(),o.focus(),b.preventDefault(),a(document).data("spaceSelect",!0)}a(document).data("keycount",0)}(/(^9$|27)/.test(b.keyCode.toString(10))&&f&&(i.multiple||i.options.liveSearch)||/(27)/.test(b.keyCode.toString(10))&&!f)&&(i.$menu.parent().removeClass("open"),i.options.container&&i.$newElement.removeClass("open"),i.$button.focus())},mobile:function(){this.$element.addClass("mobile-device")},refresh:function(){this.$lis=null,this.liObj={},this.reloadLi(),this.render(),this.checkDisabled(),this.liHeight(!0),this.setStyle(), +this.setWidth(),this.$lis&&this.$searchbox.trigger("propertychange"),this.$element.trigger("refreshed.bs.select")},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(".bs.select").removeData("selectpicker").removeClass("bs-select-hidden selectpicker")}};var m=a.fn.selectpicker;a.fn.selectpicker=c,a.fn.selectpicker.Constructor=l,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=m,this},a(document).data("keycount",0).on("keydown.bs.select",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',l.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);c.call(b,b.data())})})}(a)}); +//# sourceMappingURL=bootstrap-select.js.map \ No newline at end of file diff --git a/histview2/static/common/js/bootstrap-table-filter-control.min.js b/histview2/static/common/js/bootstrap-table-filter-control.min.js new file mode 100644 index 0000000..d5aeec2 --- /dev/null +++ b/histview2/static/common/js/bootstrap-table-filter-control.min.js @@ -0,0 +1,10 @@ +/** + * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) + * + * @version v1.16.0 + * @homepage https://bootstrap-table.com + * @author wenzhixin (http://wenzhixin.net.cn/) + * @license MIT + */ + +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):e((t=t||self).jQuery)}(this,(function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function n(t,e){return t(e={exports:{}},e.exports),e.exports}var r=function(t){return t&&t.Math==Math&&t},o=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof e&&e)||Function("return this")(),i=function(t){try{return!!t()}catch(t){return!0}},a=!i((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})),l={}.propertyIsEnumerable,c=Object.getOwnPropertyDescriptor,u={f:c&&!l.call({1:2},1)?function(t){var e=c(this,t);return!!e&&e.enumerable}:l},s=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},f={}.toString,p=function(t){return f.call(t).slice(8,-1)},d="".split,h=i((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==p(t)?d.call(t,""):Object(t)}:Object,g=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t},v=function(t){return h(g(t))},y=function(t){return"object"==typeof t?null!==t:"function"==typeof t},b=function(t,e){if(!y(t))return t;var n,r;if(e&&"function"==typeof(n=t.toString)&&!y(r=n.call(t)))return r;if("function"==typeof(n=t.valueOf)&&!y(r=n.call(t)))return r;if(!e&&"function"==typeof(n=t.toString)&&!y(r=n.call(t)))return r;throw TypeError("Can't convert object to primitive value")},C={}.hasOwnProperty,m=function(t,e){return C.call(t,e)},S=o.document,O=y(S)&&y(S.createElement),x=function(t){return O?S.createElement(t):{}},T=!a&&!i((function(){return 7!=Object.defineProperty(x("div"),"a",{get:function(){return 7}}).a})),w=Object.getOwnPropertyDescriptor,E={f:a?w:function(t,e){if(t=v(t),e=b(e,!0),T)try{return w(t,e)}catch(t){}if(m(t,e))return s(!u.f.call(t,e),t[e])}},j=function(t){if(!y(t))throw TypeError(String(t)+" is not an object");return t},k=Object.defineProperty,P={f:a?k:function(t,e,n){if(j(t),e=b(e,!0),j(n),T)try{return k(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(t[e]=n.value),t}},I=a?function(t,e,n){return P.f(t,e,s(1,n))}:function(t,e,n){return t[e]=n,t},D=function(t,e){try{I(o,t,e)}catch(n){o[t]=e}return e},L=o["__core-js_shared__"]||D("__core-js_shared__",{}),A=Function.toString;"function"!=typeof L.inspectSource&&(L.inspectSource=function(t){return A.call(t)});var R,F,_,M=L.inspectSource,N=o.WeakMap,V="function"==typeof N&&/native code/.test(M(N)),$=n((function(t){(t.exports=function(t,e){return L[t]||(L[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.6.0",mode:"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})})),B=0,H=Math.random(),G=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++B+H).toString(36)},U=$("keys"),K=function(t){return U[t]||(U[t]=G(t))},W={},q=o.WeakMap;if(V){var z=new q,Y=z.get,X=z.has,J=z.set;R=function(t,e){return J.call(z,t,e),e},F=function(t){return Y.call(z,t)||{}},_=function(t){return X.call(z,t)}}else{var Q=K("state");W[Q]=!0,R=function(t,e){return I(t,Q,e),e},F=function(t){return m(t,Q)?t[Q]:{}},_=function(t){return m(t,Q)}}var Z,tt,et={set:R,get:F,has:_,enforce:function(t){return _(t)?F(t):R(t,{})},getterFor:function(t){return function(e){var n;if(!y(e)||(n=F(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return n}}},nt=n((function(t){var e=et.get,n=et.enforce,r=String(String).split("String");(t.exports=function(t,e,i,a){var l=!!a&&!!a.unsafe,c=!!a&&!!a.enumerable,u=!!a&&!!a.noTargetGet;"function"==typeof i&&("string"!=typeof e||m(i,"name")||I(i,"name",e),n(i).source=r.join("string"==typeof e?e:"")),t!==o?(l?!u&&t[e]&&(c=!0):delete t[e],c?t[e]=i:I(t,e,i)):c?t[e]=i:D(e,i)})(Function.prototype,"toString",(function(){return"function"==typeof this&&e(this).source||M(this)}))})),rt=o,ot=function(t){return"function"==typeof t?t:void 0},it=function(t,e){return arguments.length<2?ot(rt[t])||ot(o[t]):rt[t]&&rt[t][e]||o[t]&&o[t][e]},at=Math.ceil,lt=Math.floor,ct=function(t){return isNaN(t=+t)?0:(t>0?lt:at)(t)},ut=Math.min,st=function(t){return t>0?ut(ct(t),9007199254740991):0},ft=Math.max,pt=Math.min,dt=function(t){return function(e,n,r){var o,i=v(e),a=st(i.length),l=function(t,e){var n=ct(t);return n<0?ft(n+e,0):pt(n,e)}(r,a);if(t&&n!=n){for(;a>l;)if((o=i[l++])!=o)return!0}else for(;a>l;l++)if((t||l in i)&&i[l]===n)return t||l||0;return!t&&-1}},ht={includes:dt(!0),indexOf:dt(!1)},gt=ht.indexOf,vt=function(t,e){var n,r=v(t),o=0,i=[];for(n in r)!m(W,n)&&m(r,n)&&i.push(n);for(;e.length>o;)m(r,n=e[o++])&&(~gt(i,n)||i.push(n));return i},yt=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],bt=yt.concat("length","prototype"),Ct={f:Object.getOwnPropertyNames||function(t){return vt(t,bt)}},mt={f:Object.getOwnPropertySymbols},St=it("Reflect","ownKeys")||function(t){var e=Ct.f(j(t)),n=mt.f;return n?e.concat(n(t)):e},Ot=function(t,e){for(var n=St(e),r=P.f,o=E.f,i=0;i=74)&&(Z=Gt.match(/Chrome\/(\d+)/))&&(tt=Z[1]);var qt=tt&&+tt,zt=$t("species"),Yt=function(t){return qt>=51||!i((function(){var e=[];return(e.constructor={})[zt]=function(){return{foo:1}},1!==e[t](Boolean).foo}))},Xt=$t("isConcatSpreadable"),Jt=qt>=51||!i((function(){var t=[];return t[Xt]=!1,t.concat()[0]!==t})),Qt=Yt("concat"),Zt=function(t){if(!y(t))return!1;var e=t[Xt];return void 0!==e?!!e:Lt(t)};Dt({target:"Array",proto:!0,forced:!Jt||!Qt},{concat:function(t){var e,n,r,o,i,a=At(this),l=Ht(a,0),c=0;for(e=-1,r=arguments.length;e9007199254740991)throw TypeError("Maximum allowed index exceeded");for(n=0;n=9007199254740991)throw TypeError("Maximum allowed index exceeded");Rt(l,c++,i)}return l.length=c,l}});var te=function(t){if("function"!=typeof t)throw TypeError(String(t)+" is not a function");return t},ee=[].push,ne=function(t){var e=1==t,n=2==t,r=3==t,o=4==t,i=6==t,a=5==t||i;return function(l,c,u,s){for(var f,p,d=At(l),g=h(d),v=function(t,e,n){if(te(t),void 0===e)return t;switch(n){case 0:return function(){return t.call(e)};case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}}(c,u,3),y=st(g.length),b=0,C=s||Ht,m=e?C(l,y):n?C(l,0):void 0;y>b;b++)if((a||b in g)&&(p=v(f=g[b],b,d),t))if(e)m[b]=p;else if(p)switch(t){case 3:return!0;case 5:return f;case 6:return b;case 2:ee.call(m,f)}else if(o)return!1;return i?-1:r||o?o:m}},re={forEach:ne(0),map:ne(1),filter:ne(2),some:ne(3),every:ne(4),find:ne(5),findIndex:ne(6)},oe=re.filter,ie=Yt("filter"),ae=ie&&!i((function(){[].filter.call({length:-1,0:1},(function(t){throw t}))}));Dt({target:"Array",proto:!0,forced:!ie||!ae},{filter:function(t){return oe(this,t,arguments.length>1?arguments[1]:void 0)}});var le,ce=Object.keys||function(t){return vt(t,yt)},ue=a?Object.defineProperties:function(t,e){j(t);for(var n,r=ce(e),o=r.length,i=0;o>i;)P.f(t,n=r[i++],e[n]);return t},se=it("document","documentElement"),fe=K("IE_PROTO"),pe=function(){},de=function(t){return" + + + + + + + + + + + \ No newline at end of file diff --git a/histview2/static/modules/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.css b/histview2/static/modules/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.css new file mode 100644 index 0000000..95a2242 --- /dev/null +++ b/histview2/static/modules/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.css @@ -0,0 +1,30 @@ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: left; } +.ui-timepicker-div dl dt { float: left; clear:left; padding: 0 0 0 5px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 40%; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +.ui-timepicker-div .ui_tpicker_unit_hide{ display: none; } + +.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input { background: none; color: inherit; border: none; outline: none; border-bottom: solid 1px #555; width: 95%; } +.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input:focus { border-bottom-color: #aaa; } + +.ui-timepicker-rtl{ direction: rtl; } +.ui-timepicker-rtl dl { text-align: right; padding: 0 5px 0 0; } +.ui-timepicker-rtl dl dt{ float: right; clear: right; } +.ui-timepicker-rtl dl dd { margin: 0 40% 10px 10px; } + +/* Shortened version style */ +.ui-timepicker-div.ui-timepicker-oneLine { padding-right: 2px; } +.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time, +.ui-timepicker-div.ui-timepicker-oneLine dt { display: none; } +.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time_label { display: block; padding-top: 2px; } +.ui-timepicker-div.ui-timepicker-oneLine dl { text-align: right; } +.ui-timepicker-div.ui-timepicker-oneLine dl dd, +.ui-timepicker-div.ui-timepicker-oneLine dl dd > div { display:inline-block; margin:0; } +.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_minute:before, +.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_second:before { content:':'; display:inline-block; } +.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_millisec:before, +.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_microsec:before { content:'.'; display:inline-block; } +.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide, +.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide:before{ display: none; } \ No newline at end of file diff --git a/histview2/static/modules/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.js b/histview2/static/modules/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.js new file mode 100644 index 0000000..81c55aa --- /dev/null +++ b/histview2/static/modules/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.js @@ -0,0 +1,2291 @@ +/*! jQuery Timepicker Addon - v1.6.3 - 2016-04-20 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2016 Trent Richardson; Licensed MIT */ +(function (factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', 'jquery-ui'], factory); + } else { + factory(jQuery); + } +}(function ($) { + + /* + * Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded" + */ + $.ui.timepicker = $.ui.timepicker || {}; + if ($.ui.timepicker.version) { + return; + } + + /* + * Extend jQueryUI, get it started with our version number + */ + $.extend($.ui, { + timepicker: { + version: "1.6.3" + } + }); + + /* + * Timepicker manager. + * Use the singleton instance of this class, $.timepicker, to interact with the time picker. + * Settings for (groups of) time pickers are maintained in an instance object, + * allowing multiple different settings on the same page. + */ + var Timepicker = function () { + this.regional = []; // Available regional settings, indexed by language code + this.regional[''] = { // Default regional settings + currentText: 'Now', + closeText: 'Done', + amNames: ['AM', 'A'], + pmNames: ['PM', 'P'], + timeFormat: 'HH:mm', + timeSuffix: '', + timeOnlyTitle: 'Choose Time', + timeText: 'Time', + hourText: 'Hour', + minuteText: 'Minute', + secondText: 'Second', + millisecText: 'Millisecond', + microsecText: 'Microsecond', + timezoneText: 'Time Zone', + isRTL: false + }; + this._defaults = { // Global defaults for all the datetime picker instances + showButtonPanel: true, + timeOnly: false, + timeOnlyShowDate: false, + showHour: null, + showMinute: null, + showSecond: null, + showMillisec: null, + showMicrosec: null, + showTimezone: null, + showTime: true, + stepHour: 1, + stepMinute: 1, + stepSecond: 1, + stepMillisec: 1, + stepMicrosec: 1, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null, + hourMin: 0, + minuteMin: 0, + secondMin: 0, + millisecMin: 0, + microsecMin: 0, + hourMax: 23, + minuteMax: 59, + secondMax: 59, + millisecMax: 999, + microsecMax: 999, + minDateTime: null, + maxDateTime: null, + maxTime: null, + minTime: null, + onSelect: null, + hourGrid: 0, + minuteGrid: 0, + secondGrid: 0, + millisecGrid: 0, + microsecGrid: 0, + alwaysSetTime: true, + separator: ' ', + altFieldTimeOnly: true, + altTimeFormat: null, + altSeparator: null, + altTimeSuffix: null, + altRedirectFocus: true, + pickerTimeFormat: null, + pickerTimeSuffix: null, + showTimepicker: true, + timezoneList: null, + addSliderAccess: false, + sliderAccessArgs: null, + controlType: 'slider', + oneLine: false, + defaultValue: null, + parse: 'strict', + afterInject: null + }; + $.extend(this._defaults, this.regional['']); + }; + + $.extend(Timepicker.prototype, { + $input: null, + $altInput: null, + $timeObj: null, + inst: null, + hour_slider: null, + minute_slider: null, + second_slider: null, + millisec_slider: null, + microsec_slider: null, + timezone_select: null, + maxTime: null, + minTime: null, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null, + hourMinOriginal: null, + minuteMinOriginal: null, + secondMinOriginal: null, + millisecMinOriginal: null, + microsecMinOriginal: null, + hourMaxOriginal: null, + minuteMaxOriginal: null, + secondMaxOriginal: null, + millisecMaxOriginal: null, + microsecMaxOriginal: null, + ampm: '', + formattedDate: '', + formattedTime: '', + formattedDateTime: '', + timezoneList: null, + units: ['hour', 'minute', 'second', 'millisec', 'microsec'], + support: {}, + control: null, + + /* + * Override the default settings for all instances of the time picker. + * @param {Object} settings object - the new settings to use as defaults (anonymous object) + * @return {Object} the manager object + */ + setDefaults: function (settings) { + extendRemove(this._defaults, settings || {}); + return this; + }, + + /* + * Create a new Timepicker instance + */ + _newInst: function ($input, opts) { + var tp_inst = new Timepicker(), + inlineSettings = {}, + fns = {}, + overrides, i; + + for (var attrName in this._defaults) { + if (this._defaults.hasOwnProperty(attrName)) { + var attrValue = $input.attr('time:' + attrName); + if (attrValue) { + try { + inlineSettings[attrName] = eval(attrValue); + } catch (err) { + inlineSettings[attrName] = attrValue; + } + } + } + } + + overrides = { + beforeShow: function (input, dp_inst) { + if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) { + return tp_inst._defaults.evnts.beforeShow.call($input[0], input, dp_inst, tp_inst); + } + }, + onChangeMonthYear: function (year, month, dp_inst) { + // Update the time as well : this prevents the time from disappearing from the $input field. + // tp_inst._updateDateTime(dp_inst); + if ($.isFunction(tp_inst._defaults.evnts.onChangeMonthYear)) { + tp_inst._defaults.evnts.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); + } + }, + onClose: function (dateText, dp_inst) { + if (tp_inst.timeDefined === true && $input.val() !== '') { + tp_inst._updateDateTime(dp_inst); + } + if ($.isFunction(tp_inst._defaults.evnts.onClose)) { + tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst); + } + } + }; + for (i in overrides) { + if (overrides.hasOwnProperty(i)) { + fns[i] = opts[i] || this._defaults[i] || null; + } + } + + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, opts, overrides, { + evnts: fns, + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); + }); + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function (val) { + return val.toUpperCase(); + }); + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function (val) { + return val.toUpperCase(); + }); + + // detect which units are supported + tp_inst.support = detectSupport( + tp_inst._defaults.timeFormat + + (tp_inst._defaults.pickerTimeFormat ? tp_inst._defaults.pickerTimeFormat : '') + + (tp_inst._defaults.altTimeFormat ? tp_inst._defaults.altTimeFormat : '')); + + // controlType is string - key to our this._controls + if (typeof(tp_inst._defaults.controlType) === 'string') { + if (tp_inst._defaults.controlType === 'slider' && typeof($.ui.slider) === 'undefined') { + tp_inst._defaults.controlType = 'select'; + } + tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType]; + } + // controlType is an object and must implement create, options, value methods + else { + tp_inst.control = tp_inst._defaults.controlType; + } + + // prep the timezone options + var timezoneList = [-720, -660, -600, -570, -540, -480, -420, -360, -300, -270, -240, -210, -180, -120, -60, + 0, 60, 120, 180, 210, 240, 270, 300, 330, 345, 360, 390, 420, 480, 525, 540, 570, 600, 630, 660, 690, 720, 765, 780, 840]; + if (tp_inst._defaults.timezoneList !== null) { + timezoneList = tp_inst._defaults.timezoneList; + } + var tzl = timezoneList.length, tzi = 0, tzv = null; + if (tzl > 0 && typeof timezoneList[0] !== 'object') { + for (; tzi < tzl; tzi++) { + tzv = timezoneList[tzi]; + timezoneList[tzi] = { value: tzv, label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) }; + } + } + tp_inst._defaults.timezoneList = timezoneList; + + // set the default units + tp_inst.timezone = tp_inst._defaults.timezone !== null ? $.timepicker.timezoneOffsetNumber(tp_inst._defaults.timezone) : + ((new Date()).getTimezoneOffset() * -1); + tp_inst.hour = tp_inst._defaults.hour < tp_inst._defaults.hourMin ? tp_inst._defaults.hourMin : + tp_inst._defaults.hour > tp_inst._defaults.hourMax ? tp_inst._defaults.hourMax : tp_inst._defaults.hour; + tp_inst.minute = tp_inst._defaults.minute < tp_inst._defaults.minuteMin ? tp_inst._defaults.minuteMin : + tp_inst._defaults.minute > tp_inst._defaults.minuteMax ? tp_inst._defaults.minuteMax : tp_inst._defaults.minute; + tp_inst.second = tp_inst._defaults.second < tp_inst._defaults.secondMin ? tp_inst._defaults.secondMin : + tp_inst._defaults.second > tp_inst._defaults.secondMax ? tp_inst._defaults.secondMax : tp_inst._defaults.second; + tp_inst.millisec = tp_inst._defaults.millisec < tp_inst._defaults.millisecMin ? tp_inst._defaults.millisecMin : + tp_inst._defaults.millisec > tp_inst._defaults.millisecMax ? tp_inst._defaults.millisecMax : tp_inst._defaults.millisec; + tp_inst.microsec = tp_inst._defaults.microsec < tp_inst._defaults.microsecMin ? tp_inst._defaults.microsecMin : + tp_inst._defaults.microsec > tp_inst._defaults.microsecMax ? tp_inst._defaults.microsecMax : tp_inst._defaults.microsec; + tp_inst.ampm = ''; + tp_inst.$input = $input; + + if (tp_inst._defaults.altField) { + tp_inst.$altInput = $(tp_inst._defaults.altField); + if (tp_inst._defaults.altRedirectFocus === true) { + tp_inst.$altInput.css({ + cursor: 'pointer' + }).focus(function () { + $input.trigger("focus"); + }); + } + } + + if (tp_inst._defaults.minDate === 0 || tp_inst._defaults.minDateTime === 0) { + tp_inst._defaults.minDate = new Date(); + } + if (tp_inst._defaults.maxDate === 0 || tp_inst._defaults.maxDateTime === 0) { + tp_inst._defaults.maxDate = new Date(); + } + + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. + if (tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) { + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); + } + if (tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) { + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); + } + if (tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) { + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); + } + if (tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) { + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); + } + tp_inst.$input.bind('focus', function () { + tp_inst._onFocus(); + }); + + return tp_inst; + }, + + /* + * add our sliders to the calendar + */ + _addTimePicker: function (dp_inst) { + var currDT = $.trim((this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val()); + + this.timeDefined = this._parseTime(currDT); + this._limitMinMaxDateTime(dp_inst, false); + this._injectTimePicker(); + this._afterInject(); + }, + + /* + * parse the time string from input value or _setTime + */ + _parseTime: function (timeString, withDate) { + if (!this.inst) { + this.inst = $.datepicker._getInst(this.$input[0]); + } + + if (withDate || !this._defaults.timeOnly) { + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); + try { + var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults); + if (!parseRes.timeObj) { + return false; + } + $.extend(this, parseRes.timeObj); + } catch (err) { + $.timepicker.log("Error parsing the date/time string: " + err + + "\ndate/time string = " + timeString + + "\ntimeFormat = " + this._defaults.timeFormat + + "\ndateFormat = " + dp_dateFormat); + return false; + } + return true; + } else { + var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults); + if (!timeObj) { + return false; + } + $.extend(this, timeObj); + return true; + } + }, + + /* + * Handle callback option after injecting timepicker + */ + _afterInject: function() { + var o = this.inst.settings; + if ($.isFunction(o.afterInject)) { + o.afterInject.call(this); + } + }, + + /* + * generate and inject html for timepicker into ui datepicker + */ + _injectTimePicker: function () { + var $dp = this.inst.dpDiv, + o = this.inst.settings, + tp_inst = this, + litem = '', + uitem = '', + show = null, + max = {}, + gridSize = {}, + size = null, + i = 0, + l = 0; + + // Prevent displaying twice + if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) { + var noDisplay = ' ui_tpicker_unit_hide', + html = '
          ' + '
          ' + o.timeText + '
          ' + + '
          '; + + // Create the markup + for (i = 0, l = this.units.length; i < l; i++) { + litem = this.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // Added by Peter Medeiros: + // - Figure out what the hour/minute/second max should be based on the step values. + // - Example: if stepMinute is 15, then minMax is 45. + max[litem] = parseInt((o[litem + 'Max'] - ((o[litem + 'Max'] - o[litem + 'Min']) % o['step' + uitem])), 10); + gridSize[litem] = 0; + + html += '
          ' + o[litem + 'Text'] + '
          ' + + '
          '; + + if (show && o[litem + 'Grid'] > 0) { + html += '
          '; + + if (litem === 'hour') { + for (var h = o[litem + 'Min']; h <= max[litem]; h += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + var tmph = $.datepicker.formatTime(this.support.ampm ? 'hht' : 'HH', {hour: h}, o); + html += ''; + } + } + else { + for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + html += ''; + } + } + + html += '
          ' + tmph + '' + ((m < 10) ? '0' : '') + m + '
          '; + } + html += '
          '; + } + + // Timezone + var showTz = o.showTimezone !== null ? o.showTimezone : this.support.timezone; + html += '
          ' + o.timezoneText + '
          '; + html += '
          '; + + // Create the elements from string + html += '
          '; + var $tp = $(html); + + // if we only want time picker... + if (o.timeOnly === true) { + $tp.prepend('
          ' + '
          ' + o.timeOnlyTitle + '
          ' + '
          '); + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); + } + + // add sliders, adjust grids, add events + for (i = 0, l = tp_inst.units.length; i < l; i++) { + litem = tp_inst.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // add the slider + tp_inst[litem + '_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_' + litem + '_slider'), litem, tp_inst[litem], o[litem + 'Min'], max[litem], o['step' + uitem]); + + // adjust the grid and add click event + if (show && o[litem + 'Grid'] > 0) { + size = 100 * gridSize[litem] * o[litem + 'Grid'] / (max[litem] - o[litem + 'Min']); + $tp.find('.ui_tpicker_' + litem + ' table').css({ + width: size + "%", + marginLeft: o.isRTL ? '0' : ((size / (-2 * gridSize[litem])) + "%"), + marginRight: o.isRTL ? ((size / (-2 * gridSize[litem])) + "%") : '0', + borderCollapse: 'collapse' + }).find("td").click(function (e) { + var $t = $(this), + h = $t.html(), + n = parseInt(h.replace(/[^0-9]/g), 10), + ap = h.replace(/[^apm]/ig), + f = $t.data('for'); // loses scope, so we use data-for + + if (f === 'hour') { + if (ap.indexOf('p') !== -1 && n < 12) { + n += 12; + } + else { + if (ap.indexOf('a') !== -1 && n === 12) { + n = 0; + } + } + } + + tp_inst.control.value(tp_inst, tp_inst[f + '_slider'], litem, n); + + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / gridSize[litem]) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + } // end if grid > 0 + } // end for loop + + // Add timezone options + this.timezone_select = $tp.find('.ui_tpicker_timezone').append('').find("select"); + $.fn.append.apply(this.timezone_select, + $.map(o.timezoneList, function (val, idx) { + return $("