diff --git a/NOTICE b/NOTICE index e4bbf1c5992f..ed0c9c10a84e 100644 --- a/NOTICE +++ b/NOTICE @@ -282,20 +282,6 @@ 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. - -======================================================================= -jquery.autotype (https://github.com/mmonteleone/jquery.autotype) -======================================================================= - -Copyright (c) 2009 Michael Monteleone, http://michaelmonteleone.net - -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. - - ======================================================================= Bootstrap (http://getbootstrap.com/) ======================================================================= @@ -871,3 +857,1054 @@ HBaseWD (https://github.com/sematext/HBaseWD) See the License for the specific language governing permissions and limitations under the License. + +======================================================================= +Open Sans (https://fonts.google.com/specimen/Open+Sans) +======================================================================= + +This software contains the following license and notice below: + + + 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. + +======================================================================= +nanumfont (http://hangeul.naver.com/2016/nanum) +======================================================================= + +Copyright (c) 2010, NAVER Corporation (http://www.nhncorp.com), + +with Reserved Font Name Nanum, Naver Nanum, NanumGothic, Naver NanumGothic, +NanumMyeongjo, Naver NanumMyeongjo, NanumBrush, Naver NanumBrush, NanumPen, +Naver NanumPen, Naver NanumGothicEco, NanumGothicEco, Naver NanumMyeongjoEco, +NanumMyeongjoEco, Naver NanumGothicLight, NanumGothicLight, NanumBarunGothic, +Naver NanumBarunGothic, + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting - in part or in whole - any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +======================================================================= +FortAwesome/Font-Awesome (https://fontawesome.com/) +======================================================================= + +Font Awesome Free License +------------------------- + +Font Awesome Free is free, open source, and GPL friendly. You can use it for +commercial projects, open source projects, or really almost whatever you want. +Full Font Awesome Free license: https://fontawesome.com/license. + +# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) +In the Font Awesome Free download, the CC BY 4.0 license applies to all icons +packaged as SVG and JS file types. + +# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL) +In the Font Awesome Free download, the SIL OLF license applies to all icons +packaged as web and desktop font files. + +SIL OPEN FONT LICENSE +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting - in part or in whole - any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +# Code: MIT License (https://opensource.org/licenses/MIT) +In the Font Awesome Free download, the MIT license applies to all non-font and +non-icon files. + +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. + +# Attribution +Attribution is required by MIT, SIL OLF, and CC BY licenses. Downloaded Font +Awesome Free files already contain embedded comments with sufficient +attribution, so you shouldn't need to do anything additional when using these +files normally. + +We've kept attribution comments terse, so we ask that you do not actively work +to remove them from files, especially code. They're a great way for folks to +learn about Font Awesome. + +# Brand Icons +All brand icons are trademarks of their respective owners. The use of these +trademarks does not indicate endorsement of the trademark holder by Font +Awesome, nor vice versa. **Please do not use brand logos for any purpose except +to represent the company, product, or service to which they refer.** + +======================================================================= +angular/angular (https://github.com/angular/angular) +======================================================================= + +The MIT License + +Copyright (c) 2014-2017 Google, Inc. http://angular.io + +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. + +======================================================================= +angular/material2 (https://material.angular.io/) +======================================================================= + +The MIT License + +Copyright (c) 2018 Google LLC. + +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. + +======================================================================= +ngrx/platform (https://github.com/ngrx/platform) +======================================================================= + +The MIT License (MIT) + +Copyright (c) 2017 Brandon Roberts, Mike Ryan, Victor Savkin, Rob Wormald + +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. + +======================================================================= +ng2-ui/datetime-picker (https://github.com/ng2-ui/datetime-picker) +======================================================================= + +Copyright (c) 2016 Allen Kim allenhwkim@gmail.com + +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. + +======================================================================= +ngx-translate/core (https://github.com/ngx-translate/core) +======================================================================= + +Copyright (c) 2018 Olivier Combe + +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. + +======================================================================= +ngx-translate/http-loader (https://github.com/ngx-translate/http-loader) +======================================================================= + +Copyright (c) 2018 Olivier Combe + +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. + +======================================================================= +ag-grid/ag-grid (https://www.ag-grid.com/) +======================================================================= + +This project is made up of many packages. There are two license types: MIT and Commercial. + +Each package has it's own license file explaining the license for that package. + +The following packages are MIT licensed: ++ ag-grid-community ++ ag-grid-angular ++ ag-grid-angular-cli-example ++ ag-grid-aurelia ++ ag-grid-aurelia-example ++ ag-grid-docs ++ ag-grid-react ++ ag-grid-react-example ++ ag-grid-vue ++ ag-grid-vue-example + +The following packages are Commercial licensed: ++ ag-grid-enterprise + +To view the commercial license for ag-grid-enterprise, +see the file packages/ag-grid-enterprise/LICENSE.md + +=== + +The MIT License + +Copyright (c) 2015-2016 AG GRID LTD + +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. + +======================================================================= +ag-grid/ag-grid-angular (https://github.com/ag-grid/ag-grid-angular) +======================================================================= + +The MIT License + +Copyright (c) 2015-2016 AG GRID LTD + +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. + +======================================================================= +phenomnomnominal/angular-2-local-storage (https://github.com/phenomnomnominal/angular-2-local-storage) +======================================================================= + +MIT License + +Copyright (c) 2018 Craig Spence + +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. + +======================================================================= +lancedikson/bowser (https://github.com/lancedikson/bowser) +======================================================================= + +Copyright 2015, Dustin Diaz (the "Original Author") +All rights reserved. + +MIT License + +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. + +Distributions of all or part of the Software intended to be used +by the recipients as they would use the unmodified Software, +containing modifications that substantially alter, remove, or +disable functionality of the Software, outside of the documented +configuration mechanisms provided by the Software, shall be +modified such that the Original Author's bug reporting email +addresses and urls are either replaced with the contact information +of the parties responsible for the changes, or removed entirely. + +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. + + +Except where noted, this license applies to any and all software +programs and associated documentation files created by the +Original Author, when distributed with the Software. + +======================================================================= +chartjs/Chart.js (https://www.chartjs.org/) +======================================================================= + +The MIT License (MIT) + +Copyright (c) 2018 Chart.js 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. + +======================================================================= +nagix/chartjs-plugin-streaming (https://github.com/nagix/chartjs-plugin-streaming) +======================================================================= + +The MIT License (MIT) + +Copyright (c) 2018 Akihiko Kusanagi + +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. + +======================================================================= +hammerjs/hammer.js (https://hammerjs.github.io/) +======================================================================= + +Copyright (C) 2011-2014 by Jorik Tangelder (Eight Media) + +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. + + +======================================================================= +arkon/ng-click-outside (https://github.com/arkon/ng-click-outside) +======================================================================= + +The MIT License (MIT) + +Copyright (c) 2016 Eugene Cheung + +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. + +======================================================================= +tb/ng2-nouislider (https://github.com/tb/ng2-nouislider) +======================================================================= + +The MIT License (MIT) + +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. + +======================================================================= +maxisam/ngx-clipboard (https://github.com/maxisam/ngx-clipboard) +======================================================================= + +MIT License + +Copyright (c) 2018 Sam Lin + +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. + +======================================================================= +MurhafSousli/ngx-highlightjs (https://github.com/MurhafSousli/ngx-highlightjs) +======================================================================= + +MIT License + +Copyright (c) 2018 Murhaf Sousli + +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. + +======================================================================= +orizens/ngx-infinite-scroll (https://github.com/orizens/ngx-infinite-scroll) +======================================================================= + +MIT License + +Copyright (c) 2017 Roberto Simonetti + +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. + +======================================================================= +leongersen/noUiSlider (https://github.com/leongersen/noUiSlider) +======================================================================= + +MIT License + +Copyright (c) 2018 Léon Gersen + +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. + +======================================================================= +adobe-webplatform/Snap.svg (https://github.com/adobe-webplatform/Snap.svg) +======================================================================= + +// Copyright (c) 2013 - 2015 Adobe Systems Incorporated. All rights reserved. +// +// 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. + +----- + +Snap.svg is licensed under the Apache license version 2.0, January 2004 (see LICENSE file). + +Snap.svg uses the following third party libraries that may have licenses +differing from that of Snap.svg itself. You can find the libraries and their +respective licenses below. + + - eve ./node_modules/eve + + https://github.com/adobe-webplatform/eve/ + + Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. + + 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. + + - Mocha ./node_modules/mocha + + https://github.com/visionmedia/mocha/ + + (The MIT License) + + Copyright (c) 2011-2013 TJ Holowaychuk + + 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. + + - Expect ./node_modules/expect.js + + https://github.com/LearnBoost/expect.js + + (The MIT License) + + Copyright (c) 2011 Guillermo Rauch + + 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. + + - Grunt ./node_modules/grunt + + http://gruntjs.com + + Copyright (c) 2013 "Cowboy" Ben Alman + + 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. + + + - Backbone ./demos/animated-game/js/backbone.js + + http://backbonejs.org/ + + (The MIT License) + + Copyright (c) 2010-2013 Jeremy Ashkenas, DocumentCloud + + 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. + + + - Underscore ./demos/animated-game/js/underscore.js + + http://underscorejs.org + + (The MIT License) + + Copyright (c) 2010-2013 Jeremy Ashkenas, DocumentCloud + + 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. + + + - jQuery ./demos/animated-game/js/jquery-1.9.0.min.js + + http://http://jquery.com/ + + (The MIT License) + + Copyright 2013 jQuery Foundation and other contributors + http://jquery.com/ + + 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. + +======================================================================= +almende/vis (http://visjs.org/) +======================================================================= + +Copyright (C) 2010-2017 Almende B.V. and Contributors + +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. + +--- + +The MIT License (MIT) + +Copyright (c) 2014-2017 Almende B.V. + +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. + + diff --git a/web/pom.xml b/web/pom.xml index 6a690d20a1f3..999bff61e391 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -1,19 +1,3 @@ - - 4.0.0 @@ -164,7 +148,7 @@ spring-messaging ${spring.version} - + org.springframework.security spring-security-web @@ -372,20 +356,22 @@ prepare-grunt + generate-resources run - generate-resources - + - + - - + + @@ -394,9 +380,8 @@ com.github.eirslett frontend-maven-plugin - 1.6 + 1.5 - ${basedir}/target ${basedir}/target/main/webapp ${basedir}/target/main/webapp target @@ -404,30 +389,36 @@ install node and npm + prepare-package install-node-and-npm v5.11.1 3.9.6 + ${basedir}/target npm install + prepare-package npm install + ${basedir}/target grunt build + prepare-package grunt ${grunt.build.command} + ${basedir}/target @@ -470,5 +461,57 @@ + + + v2 + + + + com.github.eirslett + frontend-maven-plugin + 1.5 + + target + + + + install node and npm v2 + prepare-package + + install-node-and-npm + + + v10.6.0 + 6.1.0 + ${basedir}/target/main/webapp/v2 + + + + npm install v2 + prepare-package + + npm + + + install + ${basedir}/target/main/webapp/v2 + + + + npm run build v2 + prepare-package + + npm + + + run build + ${basedir}/target/main/webapp/v2 + + + + + + + - + \ No newline at end of file diff --git a/web/src/main/webapp/WEB-INF/rewrite.config b/web/src/main/webapp/WEB-INF/rewrite.config new file mode 100644 index 000000000000..cfcf483f7abc --- /dev/null +++ b/web/src/main/webapp/WEB-INF/rewrite.config @@ -0,0 +1,2 @@ +RewriteCond %{REQUEST_URI} ^/v2/(admin|filteredMap|inspector|main|realtime|scatterFullScreenMode|threadDump|transactionDetail|transactionList|transactionView)(/.*)?$ +RewriteRule ^/v2/(admin|filteredMap|inspector|main|realtime|scatterFullScreenMode|threadDump|transactionDetail|transactionList|transactionView)(/.*)?$ /v2/index.html diff --git a/web/src/main/webapp/v2/angular.json b/web/src/main/webapp/v2/angular.json new file mode 100644 index 000000000000..370abbb012cd --- /dev/null +++ b/web/src/main/webapp/v2/angular.json @@ -0,0 +1,127 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 2, + "newProjectRoot": "projects", + "projects": { + "pinpoint": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "pp", + "schematics": {}, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "../../../../target/deploy/v2", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", + "./node_modules/ag-grid/dist/styles/ag-grid.css", + "./node_modules/ag-grid/dist/styles/ag-theme-balham.css", + "./node_modules/nouislider/distribute/nouislider.css", + "src/styles.css" + ], + "scripts": [ + "./node_modules/hammerjs/hammer.min.js" + ] + }, + "configurations": { + "production": { + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": false + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "pinpoint:build" + }, + "configurations": { + "production": { + "browserTarget": "pinpoint:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "pinpoint:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + "src/styles.css" + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**", + "**/src/app/core/components/angular-split/**" + ] + } + } + } + }, + "pinpoint-e2e": { + "root": "e2e/", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "pinpoint:serve" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + }, + "defaultProject": "pinpoint" +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/e2e/app.e2e-spec.ts b/web/src/main/webapp/v2/e2e/app.e2e-spec.ts new file mode 100644 index 000000000000..27b1646f793a --- /dev/null +++ b/web/src/main/webapp/v2/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { NpuPage } from './app.po'; + +describe('npu App', function() { + let page: NpuPage; + + beforeEach(() => { + page = new NpuPage(); + }); + + it('should display message saying app works', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('app works!'); + }); +}); diff --git a/web/src/main/webapp/v2/e2e/app.po.ts b/web/src/main/webapp/v2/e2e/app.po.ts new file mode 100644 index 000000000000..410477f63a1e --- /dev/null +++ b/web/src/main/webapp/v2/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, element, by } from 'protractor'; + +export class NpuPage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/web/src/main/webapp/v2/e2e/tsconfig.e2e.json b/web/src/main/webapp/v2/e2e/tsconfig.e2e.json new file mode 100644 index 000000000000..72a2d6d9f369 --- /dev/null +++ b/web/src/main/webapp/v2/e2e/tsconfig.e2e.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016" + ], + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es5", + "types":[ + "jasmine", + "node" + ] + } +} diff --git a/web/src/main/webapp/v2/karma.conf.js b/web/src/main/webapp/v2/karma.conf.js new file mode 100644 index 000000000000..6410fd7eeb14 --- /dev/null +++ b/web/src/main/webapp/v2/karma.conf.js @@ -0,0 +1,50 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/0.13/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', 'angular-cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-report'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma'), + require('karma-html-live-reporter') + ], + client: { + clearContext: false + }, + files: [ + { pattern: './src/test.ts', watched: false } + ], + preprocessors: { + './src/test.ts': ['@angular/cli'] + }, + mime: { + 'text/x-typescript': ['ts', 'tsx'] + }, + coverageIstanbulReporter: { + reports: ['html', 'lcovonly'], + fixWebpackSourcePaths: true + }, + angularCli: { + environment: 'dev' + }, + reporters: config.angularCli && config.angularCli.codeCoverage + ? ['progress', 'live-html', 'converage-istanbul'] + : ['progress', 'live-html', 'kjhtml'], + htmlLiveReporter: { // port: 5060 + colorScheme: 'jasmine', + defaultTab: 'summary', + focusMode: true + }, + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/web/src/main/webapp/v2/package-lock.json b/web/src/main/webapp/v2/package-lock.json new file mode 100644 index 000000000000..9eb0e5fa376f --- /dev/null +++ b/web/src/main/webapp/v2/package-lock.json @@ -0,0 +1,17877 @@ +{ + "name": "pinpoint", + "version": "2.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.7.4.tgz", + "integrity": "sha512-qcxLtA5XhUCqNyyMOD+s7oIVywNnhUNE1qoopnm6MN0FJ1n7iQMU5TPZBTiXDWQVnbGODObi7tGo7gFnEBML5Q==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.7.4", + "rxjs": "^6.0.0" + } + }, + "@angular-devkit/build-angular": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.7.4.tgz", + "integrity": "sha512-aNVhnWHxhx8s8VHn2ixKhrgK/I4h/fyQQd+FtvysvDia5jOb7ckiTeM4I+2hpPI/66Kr2CxSVxuPTlJkRAH+jQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.7.4", + "@angular-devkit/build-optimizer": "0.7.4", + "@angular-devkit/build-webpack": "0.7.4", + "@angular-devkit/core": "0.7.4", + "@ngtools/webpack": "6.1.4", + "ajv": "~6.4.0", + "autoprefixer": "^8.4.1", + "circular-dependency-plugin": "^5.0.2", + "clean-css": "^4.1.11", + "copy-webpack-plugin": "^4.5.2", + "file-loader": "^1.1.11", + "glob": "^7.0.3", + "html-webpack-plugin": "^3.0.6", + "istanbul": "^0.4.5", + "istanbul-instrumenter-loader": "^3.0.1", + "karma-source-map-support": "^1.2.0", + "less": "^3.7.1", + "less-loader": "^4.1.0", + "license-webpack-plugin": "^1.3.1", + "loader-utils": "^1.1.0", + "mini-css-extract-plugin": "~0.4.0", + "minimatch": "^3.0.4", + "node-sass": "^4.9.3", + "opn": "^5.1.0", + "parse5": "^4.0.0", + "portfinder": "^1.0.13", + "postcss": "^6.0.22", + "postcss-import": "^11.1.0", + "postcss-loader": "^2.1.5", + "postcss-url": "^7.3.2", + "raw-loader": "^0.5.1", + "rxjs": "^6.0.0", + "sass-loader": "~6.0.7", + "semver": "^5.5.0", + "source-map-loader": "^0.2.3", + "source-map-support": "^0.5.0", + "stats-webpack-plugin": "^0.6.2", + "style-loader": "^0.21.0", + "stylus": "^0.54.5", + "stylus-loader": "^3.0.2", + "tree-kill": "^1.2.0", + "uglifyjs-webpack-plugin": "^1.2.5", + "url-loader": "^1.0.1", + "webpack": "~4.9.2", + "webpack-dev-middleware": "^3.1.3", + "webpack-dev-server": "^3.1.4", + "webpack-merge": "^4.1.2", + "webpack-sources": "^1.1.0", + "webpack-subresource-integrity": "^1.1.0-rc.4" + } + }, + "@angular-devkit/build-optimizer": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.7.4.tgz", + "integrity": "sha512-R+Icu9XjIaKcYFscaMBJ1DyBK2prxK3JQSFi0S//0MdNP4gBFIpCtNdOQsNXovCkpVZ7YlgmdE5+vSb39GVHHA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "source-map": "^0.5.6", + "typescript": "~2.9.1", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "dev": true + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.7.4.tgz", + "integrity": "sha512-5oezCFtovcZ8fEkFyNEjs30b/t/DM6HIs3L1bP2xy2SFRNfwcWA0uyb5eag1DytZrzws2GEEmyO9qPtfwKjX7g==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.7.4", + "@angular-devkit/core": "0.7.4", + "rxjs": "^6.0.0" + } + }, + "@angular-devkit/core": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.4.tgz", + "integrity": "sha512-Blh44vzZVzE8B9xIwjRoo7hXPGSDdlrrax0rntvt3DDGVTjsSGm43qT95aDmXiwJruOCJNC5DsaP3+tTAkAyQQ==", + "dev": true, + "requires": { + "ajv": "~6.4.0", + "chokidar": "^2.0.3", + "rxjs": "^6.0.0", + "source-map": "^0.5.6" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "dev": true, + "optional": true + } + } + }, + "@angular-devkit/schematics": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.7.5.tgz", + "integrity": "sha512-E7HkQeJawUskf2gPnogMc+cTdjJ2Iv3QEZOgprh/ExEmBYByWkGDRX5fQOuy8wME8VZqUBvQACZaVkEredn5EA==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.7.5", + "rxjs": "^6.0.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.5.tgz", + "integrity": "sha512-r99BZvvuNAqSRm05jXfx0sb3Ip0zvHPtAM6NReXzWPoqaVFpjVUdj/CKA+9HWG/Zt9meG9pEQt/HKK8UXaZDVA==", + "dev": true, + "requires": { + "ajv": "~6.4.0", + "chokidar": "^2.0.3", + "rxjs": "^6.0.0", + "source-map": "^0.5.6" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "dev": true, + "optional": true + } + } + }, + "@angular/animations": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-6.1.3.tgz", + "integrity": "sha512-uLKq+bdfo+/jLW/C6lkUVsB7m+e8j18MjZGHlphI07jW6KvutX+AXdPUI/RMkkWRjZp11aF727PAQ6y3DyqB+Q==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/cdk": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.4.6.tgz", + "integrity": "sha512-XKSoeSP4htpOq2UIyF9KDhIJtEQ3wyhZRjDxyRSNmJ9OsuRZxJAGCAzOX5RpMszOyFZgUNVycOi+1lHDe0JrZg==", + "requires": { + "tslib": "^1.7.1" + } + }, + "@angular/cli": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-6.1.5.tgz", + "integrity": "sha512-QNVUSC8mPdiaxubneqNZISy+wec3gwbKoXjcaQ9/45baOnp662j2iJXwiMh6Atn0YUM4u1iUsz1uHyARMtgZmw==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.7.5", + "@angular-devkit/core": "0.7.5", + "@angular-devkit/schematics": "0.7.5", + "@schematics/angular": "0.7.5", + "@schematics/update": "0.7.5", + "opn": "^5.3.0", + "rxjs": "^6.0.0", + "semver": "^5.1.0", + "symbol-observable": "^1.2.0", + "yargs-parser": "^10.0.0" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.7.5.tgz", + "integrity": "sha512-zwCpGdx3JDE+Y+LiWh9ErRX+fpFPTRHtEd2PDJmfQsdlIWfjxSR5U9vi3+bSRW2n6IFiH2GCYMS31R64rfMwbg==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.7.5", + "rxjs": "^6.0.0" + } + }, + "@angular-devkit/core": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.5.tgz", + "integrity": "sha512-r99BZvvuNAqSRm05jXfx0sb3Ip0zvHPtAM6NReXzWPoqaVFpjVUdj/CKA+9HWG/Zt9meG9pEQt/HKK8UXaZDVA==", + "dev": true, + "requires": { + "ajv": "~6.4.0", + "chokidar": "^2.0.3", + "rxjs": "^6.0.0", + "source-map": "^0.5.6" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "dev": true, + "optional": true + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "@angular/common": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-6.1.3.tgz", + "integrity": "sha512-1V3pDdEty4hYsdpePlcNUE8rF1w1NP8LW6Q1ICNk86MI472W1U9ZTDFwCYcQYDiYMtzBrgXcnE1q6u1rqTdygQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-6.1.3.tgz", + "integrity": "sha512-r8Nv4wo2QNmsGs/sjxcR6z6YG17TfaAAxAl/6yk3z3DNdDM76cBwTi9hurXlmZKPU6/2YFI+ZwvhBrGwaOZd5Q==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler-cli": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-6.1.3.tgz", + "integrity": "sha512-YSVoLMaCF0mvt0CPRGldKMCJDGju82XhsupbDOZsMddVG6hRl8dRbem7OChJp+8GV+UNsfbP/X2o4ERLROHXRA==", + "dev": true, + "requires": { + "chokidar": "^1.4.2", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "tsickle": "^0.32.1" + } + }, + "@angular/core": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-6.1.3.tgz", + "integrity": "sha512-pqRfQphqIEExhDWM3RRusvLY6gFN0zdITC7TqQy6Wof6VKgWOvfHiHPbiamw4kpEzflMekuOeNm0s6h6hIUnWA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/forms": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-6.1.3.tgz", + "integrity": "sha512-8pHVQ97S0W//GsYsGuGV/SyzkMAF/bcE1AqDg4HXNU3mKOl5lthfM+PxHz23Cdzkg/GVInwRNxUl2Q+6lb8zuw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/http": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-6.1.3.tgz", + "integrity": "sha512-1IZ+8i1gIoPDD57Yv/8fEjzdFfWM7nY5JZGyjWUkWxq/A19zjkR4C1nRCI6KiWHk0QFx2e1Sl6OFqyiphwhalw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/language-service": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-6.1.3.tgz", + "integrity": "sha512-cAIptulTWn1LGFTmRzBbNY4axhnyQl/YaocX0JqutmI+eaWTofSiDwHVB2spYSDTWhy9fg+OMK2KmhghpZmc1g==", + "dev": true + }, + "@angular/material": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-6.4.6.tgz", + "integrity": "sha512-SUSg9MhLv4IZj6Nh8qoCLDImZugCQ+Jvvt+/cDIaTn6TrT6ZenDHc6jOhbGFesU6FuBDBFIXMiuBPD9kBr7vaA==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^1.7.1" + }, + "dependencies": { + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "optional": true + } + } + }, + "@angular/platform-browser": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-6.1.3.tgz", + "integrity": "sha512-ml844B5g8UtQaOK2QCdTRSTRHWa1elTbg4ph65GTs0lNMH/tLFNn8tvcPOfMh21ZV3fh6yqA6a9wMjSMTVSiWg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.1.3.tgz", + "integrity": "sha512-6b4Y8f4yhuUJrAVsHyYWRbRSaIQUzZWSbYvWLVZeXnt4HKzRhX+zO5yTA/eXCXAce+fRIU239FbkeyqtpuogPQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/router": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-6.1.3.tgz", + "integrity": "sha512-6sb1yH/a2CACcbXZ6d+PYWGgwV3BXupCQXBd8Q5h7o3/r5zz18VATdRqBWtSIOmFr11wekJrGGRMxDCpTDlXvg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngrx/store": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-6.1.0.tgz", + "integrity": "sha512-H5BGym1WtAX84/R4pTQ2MrrP87qYfXc6CoPghCZCK9LYxCodsI7KeQfpyNCg5qapxdH2EDqlHXTBJfMTLRiRGg==" + }, + "@ngtools/webpack": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-6.1.4.tgz", + "integrity": "sha512-LiDAvHWKdTyOp8YvjWH1oZtIY8qod13UomvTRQ2FuXXjJwNmi4Bk1VpfGCq3hhtdpo4x/aWHFBuBBpPxoJ74SQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.7.4", + "rxjs": "^6.0.0", + "tree-kill": "^1.0.0", + "webpack-sources": "^1.1.0" + } + }, + "@ngui/datetime-picker": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@ngui/datetime-picker/-/datetime-picker-0.16.2.tgz", + "integrity": "sha1-fZAXAqD8yRUn2j+IPAheXtxNhBw=" + }, + "@ngx-translate/core": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-10.0.2.tgz", + "integrity": "sha512-7nM3DrJaqKswwtJlbu2kuKNl+hE8Isr18sKsKvGGpSxQk+G0gO0reDlx2PhUNus7TJTkA1C59vU/JoN8hIvZ4g==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngx-translate/http-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-3.0.1.tgz", + "integrity": "sha1-ILD5i8bCUyESnT4zAqs8xInApCo=", + "requires": { + "tslib": "^1.9.0" + } + }, + "@schematics/angular": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.7.5.tgz", + "integrity": "sha512-NrtvFwHCoWon8KInsvA1jdPu4pVJGa8GAWM/jqnE7HpwPwM7hMML08lV0P8r3NX5t2/i0CKvfp4AAEr5MXorEQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.7.5", + "@angular-devkit/schematics": "0.7.5", + "typescript": ">=2.6.2 <2.10" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.5.tgz", + "integrity": "sha512-r99BZvvuNAqSRm05jXfx0sb3Ip0zvHPtAM6NReXzWPoqaVFpjVUdj/CKA+9HWG/Zt9meG9pEQt/HKK8UXaZDVA==", + "dev": true, + "requires": { + "ajv": "~6.4.0", + "chokidar": "^2.0.3", + "rxjs": "^6.0.0", + "source-map": "^0.5.6" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "dev": true, + "optional": true + } + } + }, + "@schematics/update": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.7.5.tgz", + "integrity": "sha512-pwNkXGtlzyCV6tsTPe8AgUuMCkmubcz94zgL6pSMdEe122yXBcKnr/PKqG9QzD/gGwmOcHUE9EWcuRtU5kdFpA==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.7.5", + "@angular-devkit/schematics": "0.7.5", + "npm-registry-client": "^8.5.1", + "rxjs": "^6.0.0", + "semver": "^5.3.0", + "semver-intersect": "^1.1.2" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.5.tgz", + "integrity": "sha512-r99BZvvuNAqSRm05jXfx0sb3Ip0zvHPtAM6NReXzWPoqaVFpjVUdj/CKA+9HWG/Zt9meG9pEQt/HKK8UXaZDVA==", + "dev": true, + "requires": { + "ajv": "~6.4.0", + "chokidar": "^2.0.3", + "rxjs": "^6.0.0", + "source-map": "^0.5.6" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "dev": true, + "optional": true + } + } + }, + "@types/chart.js": { + "version": "2.7.25", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.7.25.tgz", + "integrity": "sha512-fQMgub7qVhQQKKf99hbtj26VQQ9x3D9YldgnbpZPqa/KufgPRPhbEjp98fHPCEuidYVyMQ6UBpMYEenpe74jXQ==", + "dev": true + }, + "@types/jasmine": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.8.tgz", + "integrity": "sha512-OJSUxLaxXsjjhob2DBzqzgrkLmukM3+JMpRp0r0E4HTdT1nwDCWhaswjYxazPij6uOdzHCJfNbDjmQ1/rnNbCg==", + "dev": true + }, + "@types/jasminewd2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.3.tgz", + "integrity": "sha512-hYDVmQZT5VA2kigd4H4bv7vl/OhlympwREUemqBdOqtrYTo5Ytm12a5W5/nGgGYdanGVxj0x/VhZ7J3hOg/YKg==", + "dev": true, + "requires": { + "@types/jasmine": "*" + } + }, + "@types/node": { + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", + "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==", + "dev": true + }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "@types/selenium-webdriver": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.10.tgz", + "integrity": "sha512-ikB0JHv6vCR1KYUQAzTO4gi/lXLElT4Tx+6De2pc/OZwizE9LRNiTa+U8TBFKBD/nntPnr/MPSHSnOTybjhqNA==", + "dev": true + }, + "@types/vis": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/@types/vis/-/vis-4.21.5.tgz", + "integrity": "sha512-gviVeMYJqOz5AcdsviRnoDRWXYW/o8lKoWh0NUgx9ZZ1zfObR6atUr2GQYRQp+L1yfh0dHc7XETWBPM0X3bgqA==", + "dev": true, + "requires": { + "moment": ">=2.13.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.4.3.tgz", + "integrity": "sha512-S6npYhPcTHDYe9nlsKa9CyWByFi8Vj8HovcAgtmMAQZUOczOZbQ8CnwMYKYC5HEZzxEE+oY0jfQk4cVlI3J59Q==", + "dev": true, + "requires": { + "@webassemblyjs/helper-wasm-bytecode": "1.4.3", + "@webassemblyjs/wast-parser": "1.4.3", + "debug": "^3.1.0", + "webassemblyjs": "1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.4.3.tgz", + "integrity": "sha512-3zTkSFswwZOPNHnzkP9ONq4bjJSeKVMcuahGXubrlLmZP8fmTIJ58dW7h/zOVWiFSuG2em3/HH3BlCN7wyu9Rw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.4.3.tgz", + "integrity": "sha512-e8+KZHh+RV8MUvoSRtuT1sFXskFnWG9vbDy47Oa166xX+l0dD5sERJ21g5/tcH8Yo95e9IN3u7Jc3NbhnUcSkw==", + "dev": true, + "requires": { + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.4.3.tgz", + "integrity": "sha512-9FgHEtNsZQYaKrGCtsjswBil48Qp1agrzRcPzCbQloCoaTbOXLJ9IRmqT+uEZbenpULLRNFugz3I4uw18hJM8w==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.4.3" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.4.3.tgz", + "integrity": "sha512-JINY76U+702IRf7ePukOt037RwmtH59JHvcdWbTTyHi18ixmQ+uOuNhcdCcQHTquDAH35/QgFlp3Y9KqtyJsCQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.4.3.tgz", + "integrity": "sha512-I7bS+HaO0K07Io89qhJv+z1QipTpuramGwUSDkwEaficbSvCcL92CUZEtgykfNtk5wb0CoLQwWlmXTwGbNZUeQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.4.3.tgz", + "integrity": "sha512-p0yeeO/h2r30PyjnJX9xXSR6EDcvJd/jC6xa/Pxg4lpfcNi7JUswOpqDToZQ55HMMVhXDih/yqkaywHWGLxqyQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3", + "@webassemblyjs/helper-buffer": "1.4.3", + "@webassemblyjs/helper-wasm-bytecode": "1.4.3", + "@webassemblyjs/wasm-gen": "1.4.3", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/leb128": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.4.3.tgz", + "integrity": "sha512-4u0LJLSPzuRDWHwdqsrThYn+WqMFVqbI2ltNrHvZZkzFPO8XOZ0HFQ5eVc4jY/TNHgXcnwrHjONhPGYuuf//KQ==", + "dev": true, + "requires": { + "leb": "^0.3.0" + } + }, + "@webassemblyjs/validation": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/validation/-/validation-1.4.3.tgz", + "integrity": "sha512-R+rRMKfhd9mq0rj2mhU9A9NKI2l/Rw65vIYzz4lui7eTKPcCu1l7iZNi4b9Gen8D42Sqh/KGiaQNk/x5Tn/iBQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3" + } + }, + "@webassemblyjs/wasm-edit": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.4.3.tgz", + "integrity": "sha512-qzuwUn771PV6/LilqkXcS0ozJYAeY/OKbXIWU3a8gexuqb6De2p4ya/baBeH5JQ2WJdfhWhSvSbu86Vienttpw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3", + "@webassemblyjs/helper-buffer": "1.4.3", + "@webassemblyjs/helper-wasm-bytecode": "1.4.3", + "@webassemblyjs/helper-wasm-section": "1.4.3", + "@webassemblyjs/wasm-gen": "1.4.3", + "@webassemblyjs/wasm-opt": "1.4.3", + "@webassemblyjs/wasm-parser": "1.4.3", + "@webassemblyjs/wast-printer": "1.4.3", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.4.3.tgz", + "integrity": "sha512-eR394T8dHZfpLJ7U/Z5pFSvxl1L63JdREebpv9gYc55zLhzzdJPAuxjBYT4XqevUdW67qU2s0nNA3kBuNJHbaQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3", + "@webassemblyjs/helper-wasm-bytecode": "1.4.3", + "@webassemblyjs/leb128": "1.4.3" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.4.3.tgz", + "integrity": "sha512-7Gp+nschuKiDuAL1xmp4Xz0rgEbxioFXw4nCFYEmy+ytynhBnTeGc9W9cB1XRu1w8pqRU2lbj2VBBA4cL5Z2Kw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3", + "@webassemblyjs/helper-buffer": "1.4.3", + "@webassemblyjs/wasm-gen": "1.4.3", + "@webassemblyjs/wasm-parser": "1.4.3", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.4.3.tgz", + "integrity": "sha512-KXBjtlwA3BVukR/yWHC9GF+SCzBcgj0a7lm92kTOaa4cbjaTaa47bCjXw6cX4SGQpkncB9PU2hHGYVyyI7wFRg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3", + "@webassemblyjs/helper-wasm-bytecode": "1.4.3", + "@webassemblyjs/leb128": "1.4.3", + "@webassemblyjs/wasm-parser": "1.4.3", + "webassemblyjs": "1.4.3" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.4.3.tgz", + "integrity": "sha512-QhCsQzqV0CpsEkRYyTzQDilCNUZ+5j92f+g35bHHNqS22FppNTywNFfHPq8ZWZfYCgbectc+PoghD+xfzVFh1Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3", + "@webassemblyjs/floating-point-hex-parser": "1.4.3", + "@webassemblyjs/helper-code-frame": "1.4.3", + "@webassemblyjs/helper-fsm": "1.4.3", + "long": "^3.2.0", + "webassemblyjs": "1.4.3" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.4.3.tgz", + "integrity": "sha512-EgXk4anf8jKmuZJsqD8qy5bz2frEQhBvZruv+bqwNoLWUItjNSFygk8ywL3JTEz9KtxTlAmqTXNrdD1d9gNDtg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3", + "@webassemblyjs/wast-parser": "1.4.3", + "long": "^3.2.0" + } + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + }, + "dependencies": { + "mime-db": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", + "dev": true + }, + "mime-types": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "dev": true, + "requires": { + "mime-db": "~1.36.0" + } + } + } + }, + "acorn": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.2.tgz", + "integrity": "sha512-cJrKCNcr2kv8dlDnbw+JPUGjHZzo4myaxOLmpOX8a+rgX94YeTcTMv/LFJUSByRpc+i4GgVnnhLxvMu/2Y+rqw==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "dev": true, + "requires": { + "acorn": "^5.0.0" + } + }, + "adm-zip": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz", + "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==", + "dev": true + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "ag-grid": { + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/ag-grid/-/ag-grid-18.1.2.tgz", + "integrity": "sha512-HtJt8iFcRKCBj5UHBDmwSLLr72F3XDACeBNarH4nJWFHIqcnu7u0Ifrd2nftPmfEBj6YjFHawDqcZL2yo3YfmQ==" + }, + "ag-grid-angular": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/ag-grid-angular/-/ag-grid-angular-18.1.0.tgz", + "integrity": "sha512-Q3iBC24OxA4pj56uqVzp26WDW7VoKQYzcZlORvhjPWYURtAfmB0++mjBD79LsG1Q0nL3q/nOQ3tCi4LaTakU1Q==" + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", + "dev": true, + "requires": { + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" + } + }, + "ajv-errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz", + "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=", + "dev": true + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "angular-2-local-storage": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/angular-2-local-storage/-/angular-2-local-storage-2.0.0.tgz", + "integrity": "sha512-2Gs2DNMPA0uvn9HfddVBwKNeJxbumrpT9XgL+0CWJ4Lj8kPUX+RkmaY2Xz8ByGn1SsK0T35vqC/Gtl8o1mnjGQ==" + }, + "ansi-colors": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.0.5.tgz", + "integrity": "sha512-VVjWpkfaphxUBFarydrQ3n26zX5nIK7hcbT3/ielrvwDDyBBjuh2vuSw1P9zkPq0cfqvdw7lkYHnu+OLSfIBsg==", + "dev": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + }, + "dependencies": { + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "dev": true, + "requires": { + "color-name": "1.1.1" + } + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + } + } + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "app-root-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.1.0.tgz", + "integrity": "sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo=", + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-flatten": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", + "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-8.6.5.tgz", + "integrity": "sha512-PLWJN3Xo/rycNkx+mp8iBDMTm3FeWe4VmYaZDSqL5QQB9sLsQkG5k8n+LNDFnhh9kdq2K+egL/icpctOmDHwig==", + "dev": true, + "requires": { + "browserslist": "^3.2.8", + "caniuse-lite": "^1.0.30000864", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^6.0.23", + "postcss-value-parser": "^3.2.3" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-polyfill": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz", + "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=", + "requires": { + "babel-runtime": "^6.22.0", + "core-js": "^2.4.0", + "regenerator-runtime": "^0.10.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, + "blob": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "optional": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + }, + "dependencies": { + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "bowser": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.3.tgz", + "integrity": "sha512-/gp96UlcFw5DbV2KQPCqTqi0Mb9gZRyDAHiDsGEH+4B/KOQjeoE5lM1PxlVX8DQDvfEfitmC1rW2Oy8fk/XBDg==" + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" + } + }, + "browserstack": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.1.tgz", + "integrity": "sha512-O8VMT64P9NOLhuIoD4YngyxBURefaSdR4QdhG8l6HZ9VxtU7jc3m6jLufFwKA5gaf7fetfB2TnRJnMxyob+heg==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "dev": true + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + } + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "caniuse-lite": { + "version": "1.0.30000878", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000878.tgz", + "integrity": "sha512-/dCGTdLCnjVJno1mFRn7Y6eit3AYaeFzSrMQHCoK0LEQaWl5snuLex1Ky4b8/Qu2ig5NgTX4cJx65hH9546puA==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + }, + "chart.js": { + "version": "git+https://github.com/chartjs/Chart.js.git#48fefd92b6dc61345021c6508b23698830ff392f", + "from": "git+https://github.com/chartjs/Chart.js.git#48fefd92b6dc61345021c6508b23698830ff392f", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz", + "integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=", + "requires": { + "chartjs-color-string": "^0.5.0", + "color-convert": "^0.5.3" + } + }, + "chartjs-color-string": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz", + "integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==", + "requires": { + "color-name": "^1.0.0" + } + }, + "chartjs-plugin-streaming": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-streaming/-/chartjs-plugin-streaming-1.6.0.tgz", + "integrity": "sha512-DjHcdHGcy53rs0w7vVMzOqB8TVMBXy6qMsjfUxAn/zQlzlbQxQAOzlrDNf2lw8pmqD60km85U7lHXxMasGffhA==", + "requires": { + "chart.js": "^2.7.0", + "moment": "^2.10.2" + }, + "dependencies": { + "chart.js": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.2.tgz", + "integrity": "sha512-90wl3V9xRZ8tnMvMlpcW+0Yg13BelsGS9P9t0ClaDxv/hdypHDr/YAGf+728m11P5ljwyB0ZHfPKCapZFqSqYA==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + } + } + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "dev": true + }, + "chrome-trace-event": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-0.1.3.tgz", + "integrity": "sha512-sjndyZHrrWiu4RY7AkHgjn80GfAM2ZSzUkZLV/Js59Ldmh6JDThf0SUmOHU53rFu2rVxxfCzJ30Ukcfch3Gb/A==", + "dev": true + }, + "ci-info": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", + "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-dependency-plugin": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz", + "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "codelyzer": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.2.1.tgz", + "integrity": "sha512-CKwfgpfkqi9dyzy4s6ELaxJ54QgJ6A8iTSsM4bzHbLuTpbKncvNc3DUlCvpnkHBhK47gEf4qFsWoYqLrJPhy6g==", + "dev": true, + "requires": { + "app-root-path": "^2.0.1", + "css-selector-tokenizer": "^0.7.0", + "cssauron": "^1.4.0", + "semver-dsl": "^1.0.1", + "source-map": "^0.5.6", + "sprintf-js": "^1.0.3" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combine-lists": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", + "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", + "dev": true, + "requires": { + "lodash": "^4.5.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-versions": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.3.1.tgz", + "integrity": "sha512-GkIcfJ9sDt4+gS+RWH3X+kR7ezuKdu3fg2oA9nRA8HZoqZwAKv3ml3TyfB9OyV2iFXxCw7q5XfV6SyPbSCT2pw==", + "dev": true + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "compressible": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz", + "integrity": "sha1-MmxfUH+7BV9UEWeCuWmoG2einac=", + "dev": true, + "requires": { + "mime-db": ">= 1.34.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", + "dev": true + } + } + }, + "compression": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.14", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "connect": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.5.tgz", + "integrity": "sha1-+43ee6B2OHfQ7J352sC0tA5yx9o=", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.0.6", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "dependencies": { + "finalhandler": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", + "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + } + } + } + }, + "connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "dev": true + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.2.tgz", + "integrity": "sha512-zmC33E8FFSq3AbflTvqvPvBo621H36Afsxlui91d+QyZxPIuXghfnTsa1CuqiAaCPgJoSUWfTFbKJnadZpKEbQ==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + } + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz", + "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0", + "require-from-string": "^2.0.1" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "optional": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "dev": true + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-selector-tokenizer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + } + }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true + }, + "cssauron": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true, + "requires": { + "through": "X.X.X" + } + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-node": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", + "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "docopt": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/docopt/-/docopt-0.6.2.tgz", + "integrity": "sha1-so6eIiDaXsSffqW7JKR3h0Be6xE=", + "dev": true + }, + "dom-converter": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", + "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", + "dev": true, + "requires": { + "utila": "~0.3" + }, + "dependencies": { + "utila": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", + "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", + "dev": true + } + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", + "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "duplexify": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", + "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "ejs": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", + "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.61", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.61.tgz", + "integrity": "sha512-XjTdsm6x71Y48lF9EEvGciwXD70b20g0t+3YbrE+0fPFutqV08DSNrZXkoXAp3QuzX7TpL/OW+/VsNoR9GkuNg==", + "dev": true + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emitter-component": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz", + "integrity": "sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY=" + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", + "dev": true + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.3.tgz", + "integrity": "sha1-jef5eJXSDTm4X4ju7nd7K9QrE9Q=", + "dev": true, + "requires": { + "accepts": "1.3.3", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "2.3.3", + "engine.io-parser": "1.3.2", + "ws": "1.1.2" + }, + "dependencies": { + "accepts": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "dev": true, + "requires": { + "mime-types": "~2.1.11", + "negotiator": "0.6.1" + } + }, + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-client": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.3.tgz", + "integrity": "sha1-F5jtk0USRkU9TG9jXXogH+lA1as=", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "2.3.3", + "engine.io-parser": "1.3.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parsejson": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "1.1.2", + "xmlhttprequest-ssl": "1.5.3", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-parser": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz", + "integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "0.0.6", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.4", + "has-binary": "0.1.7", + "wtf-8": "1.0.0" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "dev": true, + "requires": { + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" + } + }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + } + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + } + } + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eve": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eve/-/eve-0.5.4.tgz", + "integrity": "sha1-Z9CAuXJSkdfjieNMJoYN2X8d66o=" + }, + "eventemitter3": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", + "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "eventsource": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", + "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", + "dev": true, + "requires": { + "original": ">=0.0.5" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-braces": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", + "dev": true, + "requires": { + "array-slice": "^0.2.3", + "array-unique": "^0.2.1", + "braces": "^0.1.2" + }, + "dependencies": { + "braces": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", + "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", + "dev": true, + "requires": { + "expand-range": "^0.1.0" + } + }, + "expand-range": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", + "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", + "dev": true, + "requires": { + "is-number": "^0.1.1", + "repeat-string": "^0.2.2" + } + }, + "is-number": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", + "dev": true + }, + "repeat-string": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", + "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", + "dev": true + } + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "^2.1.0" + } + }, + "express": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.3", + "qs": "6.5.1", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "mime-db": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", + "dev": true + }, + "mime-types": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "dev": true, + "requires": { + "mime-db": "~1.36.0" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + }, + "dependencies": { + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + } + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastparse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-loader": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^1.1.3", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true, + "requires": { + "null-check": "^1.0.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.3.0", + "node-pre-gyp": "^0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.x.x" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^0.4.1", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.x.x" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fstream": "^1.0.0", + "inherits": "2", + "minimatch": "^3.0.0" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ajv": "^4.9.1", + "har-schema": "^1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "~1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "hawk": "3.1.3", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "request": "2.81.0", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^2.2.1", + "tar-pack": "^3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "~0.4.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "dev": true, + "requires": { + "buffer-shims": "~1.0.0", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~1.0.0", + "util-deprecate": "~1.0.1" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~4.2.1", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "performance-now": "^0.2.0", + "qs": "~6.4.0", + "safe-buffer": "^5.0.1", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.0.0" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.x.x" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jodid25519": "^1.0.0", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.2.0", + "fstream": "^1.0.10", + "fstream-ignore": "^1.0.5", + "once": "^1.3.3", + "readable-stream": "^2.1.4", + "rimraf": "^2.5.1", + "tar": "^2.2.1", + "uid-number": "^0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + } + } + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "optional": true, + "requires": { + "globule": "^1.0.0" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "optional": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true, + "optional": true + } + } + }, + "gojs": { + "version": "1.8.28", + "resolved": "https://registry.npmjs.org/gojs/-/gojs-1.8.28.tgz", + "integrity": "sha512-eiz6FZDPgqwyf16vjZ7UU9vjrENpMZXORYGLJ+9ZB6NBKR0aNsOpko7n3zb+3vNNkN5RM3f1SGQvrgKBPOi2Nw==" + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, + "handle-thing": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", + "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=", + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "dev": true, + "requires": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "optional": true + } + } + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + } + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-binary": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", + "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.1.tgz", + "integrity": "sha512-Ba4+0M4YvIDUUsprMjhVTU1yN9F2/LJSAl69ZpzaLT4l4j5mwTS6jqqW9Ojvj6lKz/veqPzpJBqGbXspOb533A==", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "html-minifier": { + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz", + "integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.1.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + } + } + }, + "html-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "dev": true, + "requires": { + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", + "util.promisify": "1.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, + "htmlparser2": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.1", + "domutils": "1.1", + "readable-stream": "1.0" + }, + "dependencies": { + "domutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", + "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + }, + "dependencies": { + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", + "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=", + "dev": true + }, + "http-proxy": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", + "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", + "dev": true, + "requires": { + "eventemitter3": "1.x.x", + "requires-port": "1.x.x" + } + }, + "http-proxy-middleware": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", + "dev": true, + "requires": { + "http-proxy": "^1.16.2", + "is-glob": "^4.0.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "husky": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/husky/-/husky-1.0.0-rc.9.tgz", + "integrity": "sha512-iGGRXcpwl3qGysa73KAtiZWN/YuQVqwgVsPR0UihVasfIsWaAbOfAsswsGmBhKivGtDCdOiLJPTvEZfGJWiCVw==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.2", + "execa": "^0.9.0", + "find-up": "^2.1.0", + "get-stdin": "^6.0.0", + "is-ci": "^1.1.0", + "pkg-dir": "^2.0.0", + "read-pkg": "^3.0.0", + "run-node": "^1.0.0", + "slash": "^2.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.5.tgz", + "integrity": "sha512-94j37OtvxS5w7qr7Ta6dt67tWdnOxigBVN4VnSxNXFez9o18PGQ0D33SchKP17r9LAcWVTYV72G6vDayAUBFIg==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.9.0.tgz", + "integrity": "sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA==", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "dev": true, + "requires": { + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true, + "optional": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz", + "integrity": "sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c=", + "requires": { + "ansi-escapes": "^1.1.0", + "chalk": "^1.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.1", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx": "^4.1.0", + "string-width": "^2.0.0", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "internal-ip": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", + "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", + "dev": true, + "requires": { + "meow": "^3.3.0" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", + "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", + "dev": true, + "requires": { + "ci-info": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "istanbul-api": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.1.tgz", + "integrity": "sha512-duj6AlLcsWNwUpfyfHt0nWIeRiZpuShnP40YTxOGQgtaN8fd6JYSxsvxUphTDy8V5MfDXo4s/xVCIIvVCO808g==", + "dev": true, + "requires": { + "async": "^2.1.4", + "compare-versions": "^3.1.0", + "fileset": "^2.0.2", + "istanbul-lib-coverage": "^1.2.0", + "istanbul-lib-hook": "^1.2.0", + "istanbul-lib-instrument": "^1.10.1", + "istanbul-lib-report": "^1.1.4", + "istanbul-lib-source-maps": "^1.2.4", + "istanbul-reports": "^1.3.0", + "js-yaml": "^3.7.0", + "mkdirp": "^0.5.1", + "once": "^1.4.0" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } + } + }, + "istanbul-instrumenter-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", + "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", + "dev": true, + "requires": { + "convert-source-map": "^1.5.0", + "istanbul-lib-instrument": "^1.7.3", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "schema-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true, + "requires": { + "ajv": "^5.0.0" + } + } + } + }, + "istanbul-lib-coverage": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", + "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.1.tgz", + "integrity": "sha512-eLAMkPG9FU0v5L02lIkcj/2/Zlz9OuluaXikdr5iStk8FDbSwAixTK9TkYxbF0eNnzAJTwM2fkV2A1tpsIp4Jg==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", + "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.0", + "semver": "^5.3.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz", + "integrity": "sha512-Azqvq5tT0U09nrncK3q82e/Zjkxa4tkFZv7E6VcqP0QCPn6oNljDPfrZEC/umNXds2t7b8sRJfs6Kmpzt8m2kA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.5.tgz", + "integrity": "sha512-8O2T/3VhrQHn0XcJbP1/GN7kXMiRAlPi+fj3uEHrjBD8Oz7Py0prSC25C09NuAZS6bgW1NNKAvCSHZXB0irSGA==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "istanbul-reports": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.3.0.tgz", + "integrity": "sha512-y2Z2IMqE1gefWUaVjrBm0mSKvUkaBy9Vqz8iwr/r40Y9hBbIteH5wqHG/9DLTfJ9xUnUT2j7A3+VVJ6EaYBllA==", + "dev": true, + "requires": { + "handlebars": "^4.0.3" + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + } + } + }, + "jasmine-core": { + "version": "2.99.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", + "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", + "dev": true + }, + "jasmine-diff": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/jasmine-diff/-/jasmine-diff-0.1.3.tgz", + "integrity": "sha1-k8zC3MQQKMXd1GBlWAdIOfLe6qg=", + "dev": true, + "requires": { + "diff": "^3.2.0" + } + }, + "jasmine-spec-reporter": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", + "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", + "dev": true, + "requires": { + "colors": "1.1.2" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true + }, + "js-base64": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.8.tgz", + "integrity": "sha512-hm2nYpDrwoO/OzBhdcqs/XGT6XjSuSSCVEpia+Kl2J6x4CYt5hISlVL/AYU1khoDXv0AQVgxtdJySb9gjAn56Q==", + "dev": true, + "optional": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", + "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", + "dev": true, + "requires": { + "core-js": "~2.3.0", + "es6-promise": "~3.0.2", + "lie": "~3.1.0", + "pako": "~1.0.2", + "readable-stream": "~2.0.6" + }, + "dependencies": { + "core-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=", + "dev": true + }, + "es6-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "karma": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz", + "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==", + "dev": true, + "requires": { + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "chokidar": "^1.4.1", + "colors": "^1.1.0", + "combine-lists": "^1.0.0", + "connect": "^3.6.0", + "core-js": "^2.2.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "expand-braces": "^0.1.1", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^3.8.0", + "log4js": "^0.6.31", + "mime": "^1.3.4", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", + "socket.io": "1.7.3", + "source-map": "^0.5.3", + "tmp": "0.0.31", + "useragent": "^2.1.12" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, + "requires": { + "fs-access": "^1.0.0", + "which": "^1.2.1" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.0.2.tgz", + "integrity": "sha512-IIIrfsfYJKkAyyjRrBx8CZRl2UXi2OSrxKRAA95mkpOMF3Zw5FpjE+v79pWuwu1Keu0pdjcfElmmOuAEjFQshA==", + "dev": true, + "requires": { + "istanbul-api": "^1.3.1", + "minimatch": "^3.0.4" + } + }, + "karma-jasmine": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.1.tgz", + "integrity": "sha1-b+hA51oRYAydkehLM8RY4cRqNSk=", + "dev": true + }, + "karma-jasmine-html-reporter": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz", + "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=", + "dev": true, + "requires": { + "karma-jasmine": "^1.0.2" + } + }, + "karma-source-map-support": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.3.0.tgz", + "integrity": "sha512-HcPqdAusNez/ywa+biN4EphGz62MmQyPggUsDfsHqa7tSe4jdsxgvTKuDfIazjL+IOxpVWyT7Pr4dhAV+sxX5Q==", + "dev": true, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "keycharm": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.2.0.tgz", + "integrity": "sha1-+m6i5DuQpoAohD0n8gddNajD5vk=" + }, + "killable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", + "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "leb": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/leb/-/leb-0.3.0.tgz", + "integrity": "sha1-Mr7p+tFoMo1q6oUi2DP0GA7tHaM=", + "dev": true + }, + "less": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/less/-/less-3.8.1.tgz", + "integrity": "sha512-8HFGuWmL3FhQR0aH89escFNBQH/nEiYPP2ltDFdQw2chE28Yx2E3lhAIq9Y2saYwLSwa699s4dBVEfCY8Drf7Q==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "less-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz", + "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^3.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "license-webpack-plugin": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-1.4.0.tgz", + "integrity": "sha512-iwuNFMWbXS76WiQXJBTs8/7Tby4NQnY8AIkBMuJG5El79UT8zWrJQMfpW+KRXt4Y2Bs5uk+Myg/MO7ROSF8jzA==", + "dev": true, + "requires": { + "ejs": "^2.5.7" + } + }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "loader-runner": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", + "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "dev": true + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true, + "optional": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", + "dev": true, + "optional": true + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, + "log4js": { + "version": "0.6.38", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", + "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", + "dev": true, + "requires": { + "readable-stream": "~1.0.2", + "semver": "~4.3.3" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", + "dev": true + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "dev": true + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "dev": true + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "dev": true, + "requires": { + "mime-db": "~1.30.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "mini-css-extract-plugin": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.2.tgz", + "integrity": "sha512-ots7URQH4wccfJq9Ssrzu2+qupbncAce4TmTzunI9CIwlQMp2XI+WNUw6xWF6MMAGAm1cbUVINrSjATaVMyKXg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "moment": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + }, + "moment-timezone": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz", + "integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==", + "requires": { + "moment": ">= 2.9.0" + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "neo-async": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.2.tgz", + "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==", + "dev": true + }, + "ng-click-outside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ng-click-outside/-/ng-click-outside-4.0.0.tgz", + "integrity": "sha512-b5JIWKjaALmKWmy8SXQjxrRIaPqExmqktcZXRwHflzR0UqUKKBT/2C33jBMI425QD75BPqpAiGq5xujzEybkUw==" + }, + "ng2-nouislider": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/ng2-nouislider/-/ng2-nouislider-1.7.11.tgz", + "integrity": "sha1-uLpePS/8I+HjLf5U3Rcm4rS+MWs=" + }, + "ngx-clipboard": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-11.1.3.tgz", + "integrity": "sha512-L2lAwbw1ZpCmLzObOTVWbeiLLZaxQfVEUAcOjdvdk5i7/Dtg/wCYCTBVdb8RrWOWdv7OgbiUSCuhrijK89UiHA==", + "requires": { + "ngx-window-token": "^1.0.0", + "tslib": "^1.9.0" + } + }, + "ngx-highlightjs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ngx-highlightjs/-/ngx-highlightjs-2.1.1.tgz", + "integrity": "sha512-8LpImj51J8372Zztg/SHB5IznPVtujbW/Ner2KJOh1DjO7uph9jFt5AYdvtJrX9WeJ9Su+uuLlbjbSm2NgloKg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ngx-infinite-scroll": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-6.0.1.tgz", + "integrity": "sha512-20WcD+3Qh3O0IEFyIjt55JPTKw5W1hAxERXMUDgGDRveS3IBpBxv2DuX5vuHG/bNGC+WoTDlNR/XXScNNicRpw==", + "requires": { + "opencollective": "^1.0.3" + } + }, + "ngx-window-token": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-1.0.0.tgz", + "integrity": "sha512-n+ZTyuNKHGccKoaofIgNCSJ7XgfujDodSYOxauY5eE6s4sxCriMBZelBIMqjaEuIE2GleViIwlCzb/j45rakPA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-fetch": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz", + "integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "optional": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true, + "optional": true + } + } + }, + "node-libs-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.0", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.10.3", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-sass": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.3.tgz", + "integrity": "sha512-XzXyGjO+84wxyH7fV6IwBOTrEBe2f0a6SBze9QWWYR/cL74AcQUks2AsqcCZenl/Fp/JVbuEaLpgrLtocwBUww==", + "dev": true, + "optional": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash.assign": "^4.2.0", + "lodash.clonedeep": "^4.3.2", + "lodash.mergewith": "^4.6.0", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.10.0", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "2.87.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "optional": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "optional": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "optional": true, + "requires": { + "ajv": "^5.1.0", + "har-schema": "^2.0.0" + } + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "dev": true, + "optional": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true, + "optional": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true, + "optional": true + }, + "request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "optional": true + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, + "optional": true, + "requires": { + "punycode": "^1.4.1" + } + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "nouislider": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-11.1.0.tgz", + "integrity": "sha512-nD+Fgc8A8j6hnGvR5AaV+OBuLF446z4H2fmcEJ/6U6CJr6rAnFnionMXu7dmdghZ+bhgePvL3wrDRbu+0ux7Jg==" + }, + "npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-registry-client": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.6.0.tgz", + "integrity": "sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==", + "dev": true, + "requires": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "opencollective": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/opencollective/-/opencollective-1.0.3.tgz", + "integrity": "sha1-ruY3K8KBRFg2kMPKja7PwSDdDvE=", + "requires": { + "babel-polyfill": "6.23.0", + "chalk": "1.1.3", + "inquirer": "3.0.6", + "minimist": "1.2.0", + "node-fetch": "1.6.3", + "opn": "4.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "opn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz", + "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=", + "requires": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", + "dev": true + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "optional": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "parse-asn1": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "parsejson": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", + "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pbkdf2": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", + "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "portfinder": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.17.tgz", + "integrity": "sha512-syFcRIRzVI1BoEFOCaAiizwDolh1S1YXSodsVhncbhjzjZQulhczNRbqnUl9N31Q4dKGOXsNDqxC2BWBgSMqeQ==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-import": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-11.1.0.tgz", + "integrity": "sha512-5l327iI75POonjxkXgdRCUS+AlzAdBx4pOvMEhTKTCjb1p8IEeVR9yx3cPbmN7LIWJLbfnIXxAhoB4jpD0c/Cw==", + "dev": true, + "requires": { + "postcss": "^6.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-load-config": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.0.0.tgz", + "integrity": "sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ==", + "dev": true, + "requires": { + "cosmiconfig": "^4.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.6.tgz", + "integrity": "sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^6.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^0.4.0" + } + }, + "postcss-url": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-7.3.2.tgz", + "integrity": "sha512-QMV5mA+pCYZQcUEPQkmor9vcPQ2MT+Ipuu8qdi1gVxbNiIiErEGft+eny1ak19qALoBkccS5AHaCaCDzh7b9MA==", + "dev": true, + "requires": { + "mime": "^1.4.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.0", + "postcss": "^6.0.1", + "xxhashjs": "^0.2.1" + } + }, + "postcss-value-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", + "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "optional": true, + "requires": { + "asap": "~2.0.3" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "propagating-hammerjs": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-1.4.6.tgz", + "integrity": "sha1-/tAOmwB2f/1C0U9bUxvEk+tnLjc=", + "requires": { + "hammerjs": "^2.0.6" + } + }, + "protractor": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.0.tgz", + "integrity": "sha512-6TSYqMhUUzxr4/wN0ttSISqPMKvcVRXF4k8jOEpGWD8OioLak4KLgfzHK9FJ49IrjzRrZ+Mx1q2Op8Rk0zEcnQ==", + "dev": true, + "requires": { + "@types/node": "^6.0.46", + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "optimist": "~0.6.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "2.0.0", + "webdriver-manager": "^12.0.6" + }, + "dependencies": { + "@types/node": { + "version": "6.0.116", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.116.tgz", + "integrity": "sha512-vToa8YEeulfyYg1gSOeHjvvIRqrokng62VMSj2hoZrwZNcYrp2h3AWo6KeBVuymIklQUaY5zgVJvVsC4KiiLkQ==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "webdriver-manager": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.0.tgz", + "integrity": "sha512-oEc5fmkpz6Yh6udhwir5m0eN5mgRPq9P/NU5YWuT3Up5slt6Zz+znhLU7q4+8rwCZz/Qq3Fgpr/4oao7NPCm2A==", + "dev": true, + "requires": { + "adm-zip": "^0.4.9", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + } + } + } + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", + "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "qjobs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.1.5.tgz", + "integrity": "sha1-ZZ3p8s+NzCehSBJ28gU3cnI4LnM=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", + "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==", + "dev": true + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + } + }, + "raw-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "dev": true + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "dependencies": { + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "reflect-metadata": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==", + "dev": true + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "renderkid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz", + "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "~0.1", + "htmlparser2": "~3.3.0", + "strip-ansi": "^3.0.0", + "utila": "~0.3" + }, + "dependencies": { + "utila": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", + "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", + "dev": true + } + } + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "mime-db": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", + "dev": true + }, + "mime-types": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "dev": true, + "requires": { + "mime-db": "~1.36.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", + "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", + "dev": true + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" + }, + "rxjs": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.1.tgz", + "integrity": "sha512-OwMxHxmnmHTUpgO+V7dZChf3Tixf4ih95cmXjzzadULziVl/FKhHScGLj4goEw9weePVOH2Q0+GcCBUhKCZc/g==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "optional": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true, + "optional": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + } + } + } + }, + "sass-loader": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.7.tgz", + "integrity": "sha512-JoiyD00Yo1o61OJsoP2s2kb19L1/Y2p3QFcCdWdF6oomBGKVYuZyqHWemRBfQ2uGYsk+CH3eCguXNfpjzlcpaA==", + "dev": true, + "requires": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0" + } + }, + "saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "dev": true + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "optional": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "dependencies": { + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + } + } + }, + "selfsigned": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.3.tgz", + "integrity": "sha512-vmZenZ+8Al3NLHkWnhBQ0x6BkML1eCP2xEi3JE+f3D9wW9fipD9NNJHYtE9XJM4TsPaHGZJIamrSI6MTg1dU2Q==", + "dev": true, + "requires": { + "node-forge": "0.7.5" + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "semver-dsl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", + "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "semver-intersect": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", + "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==", + "dev": true, + "requires": { + "semver": "^5.0.0" + } + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", + "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", + "dev": true + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + } + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, + "snapsvg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/snapsvg/-/snapsvg-0.5.1.tgz", + "integrity": "sha1-DK9Sx5GJopB0b8RGzF6GP2vd3+M=", + "requires": { + "eve": "~0.5.1" + } + }, + "snapsvg-cjs": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/snapsvg-cjs/-/snapsvg-cjs-0.0.6.tgz", + "integrity": "sha1-Oy9WryVz09Nkw+1b+IhXRfTS3eE=", + "requires": { + "snapsvg": "0.5.1" + } + }, + "socket.io": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.3.tgz", + "integrity": "sha1-uK+cq6AJSeVo42nxMn6pvp6iRhs=", + "dev": true, + "requires": { + "debug": "2.3.3", + "engine.io": "1.8.3", + "has-binary": "0.1.7", + "object-assign": "4.1.0", + "socket.io-adapter": "0.5.0", + "socket.io-client": "1.7.3", + "socket.io-parser": "2.3.1" + }, + "dependencies": { + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "object-assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true + } + } + }, + "socket.io-adapter": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", + "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=", + "dev": true, + "requires": { + "debug": "2.3.3", + "socket.io-parser": "2.3.1" + }, + "dependencies": { + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-client": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.3.tgz", + "integrity": "sha1-sw6GqhDV7zVGYBwJzeR2Xjgdo3c=", + "dev": true, + "requires": { + "backo2": "1.0.2", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "2.3.3", + "engine.io-client": "1.8.3", + "has-binary": "0.1.7", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseuri": "0.0.5", + "socket.io-parser": "2.3.1", + "to-array": "0.1.4" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", + "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=", + "dev": true, + "requires": { + "component-emitter": "1.1.2", + "debug": "2.2.0", + "isarray": "0.0.1", + "json3": "3.3.2" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.5.tgz", + "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=", + "dev": true, + "requires": { + "debug": "^2.6.6", + "eventsource": "0.1.6", + "faye-websocket": "~0.11.0", + "inherits": "^2.0.1", + "json3": "^3.3.2", + "url-parse": "^1.1.8" + }, + "dependencies": { + "faye-websocket": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", + "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + } + } + }, + "source-list-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-explorer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-1.6.0.tgz", + "integrity": "sha512-Om4L02wExk1tWZ2KJqW7A/gadFoU6/6vuPUOiYyeMCtkQqhexTod6Pi6aCQ6HiIEd7ZSbiOOPgIrG6bn/72foQ==", + "dev": true, + "requires": { + "btoa": "^1.1.2", + "convert-source-map": "^1.1.1", + "docopt": "^0.6.2", + "glob": "^7.1.2", + "opn": "^5.3.0", + "source-map": "^0.5.1", + "temp": "^0.8.3", + "underscore": "^1.8.3" + } + }, + "source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "requires": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } + } + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", + "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "dev": true + }, + "spdy": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", + "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", + "dev": true, + "requires": { + "debug": "^2.6.8", + "handle-thing": "^1.2.5", + "http-deceiver": "^1.2.7", + "safe-buffer": "^5.0.1", + "select-hose": "^2.0.0", + "spdy-transport": "^2.0.18" + } + }, + "spdy-transport": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz", + "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "detect-node": "^2.0.3", + "hpack.js": "^2.1.6", + "obuf": "^1.1.1", + "readable-stream": "^2.2.9", + "safe-buffer": "^5.0.1", + "wbuf": "^1.7.2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stats-webpack-plugin": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/stats-webpack-plugin/-/stats-webpack-plugin-0.6.2.tgz", + "integrity": "sha1-LFlJtTHgf4eojm6k3PrFOqjHWis=", + "dev": true, + "requires": { + "lodash": "^4.17.4" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "optional": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "style-loader": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz", + "integrity": "sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^0.4.5" + } + }, + "stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "dev": true, + "requires": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "stylus-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", + "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "when": "~3.6.x" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, + "tapable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", + "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", + "dev": true + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "optional": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "temp": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", + "dev": true, + "requires": { + "os-tmpdir": "^1.0.0", + "rimraf": "~2.2.6" + }, + "dependencies": { + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.2.tgz", + "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tree-kill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", + "integrity": "sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.2" + } + }, + "ts-node": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-5.0.1.tgz", + "integrity": "sha512-XK7QmDcNHVmZkVtkiwNDWiERRHPyU8nBqZB1+iv2UhOG0q3RQ9HsZ2CMqISlFbxjrYFGfG2mX7bW4dAyxBVzUw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "chalk": "^2.3.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.3", + "yn": "^2.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "tsickle": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.32.1.tgz", + "integrity": "sha512-JW9j+W0SaMSZGejIFZBk0AiPfnhljK3oLx5SaqxrJhjlvzFyPml5zqG1/PuScUj6yTe1muEqwk5CnDK0cOZmKw==", + "dev": true, + "requires": { + "jasmine-diff": "^0.1.3", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map": "^0.6.0", + "source-map-support": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "tslint": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", + "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.12.1" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "tsutils": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", + "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.15" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", + "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", + "dev": true + }, + "uglify-js": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.8.tgz", + "integrity": "sha512-WatYTD84gP/867bELqI2F/2xC9PQBETn/L+7RGq9MQOA/7yFBNvY1UwXqvtILeE6n0ITwBXxp34M0/o70dzj6A==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "uglifyjs-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "dev": true, + "requires": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + } + } + } + }, + "ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", + "dev": true + }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", + "dev": true + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unique-filename": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", + "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", + "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-join": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", + "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=", + "dev": true + }, + "url-loader": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.1.tgz", + "integrity": "sha512-vugEeXjyYFBCUOpX+ZuaunbK3QXMKaQ3zUnRfIpRBlGkY7QizCnzyyn2ASfcxsvyU3ef+CJppVywnl3Kgf13Gg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "dev": true + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "url-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", + "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", + "dev": true, + "requires": { + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "useragent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.2.1.tgz", + "integrity": "sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=", + "dev": true, + "requires": { + "lru-cache": "2.2.x", + "tmp": "0.0.x" + }, + "dependencies": { + "lru-cache": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=", + "dev": true + } + } + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vis": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/vis/-/vis-4.21.0.tgz", + "integrity": "sha1-3XFji/9/ZJXQC8n0DCU1JhM97Ws=", + "requires": { + "emitter-component": "^1.1.1", + "hammerjs": "^2.0.8", + "keycharm": "^0.2.0", + "moment": "^2.18.1", + "propagating-hammerjs": "^1.4.6" + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "dev": true, + "optional": true + } + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "webassemblyjs": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webassemblyjs/-/webassemblyjs-1.4.3.tgz", + "integrity": "sha512-4lOV1Lv6olz0PJkDGQEp82HempAn147e6BXijWDzz9g7/2nSebVP9GVg62Fz5ZAs55mxq13GA0XLyvY8XkyDjg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3", + "@webassemblyjs/validation": "1.4.3", + "@webassemblyjs/wasm-parser": "1.4.3", + "@webassemblyjs/wast-parser": "1.4.3", + "long": "^3.2.0" + } + }, + "webdriver-js-extender": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.0.0.tgz", + "integrity": "sha512-fbyKiVu3azzIc5d4+26YfuPQcFTlgFQV5yQ/0OQj4Ybkl4g1YQuIPskf5v5wqwRJhHJnPHthB6tqCjWHOKLWag==", + "dev": true, + "requires": { + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" + } + }, + "webpack": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.9.2.tgz", + "integrity": "sha512-jlWrCrJDU3sdWFprel6jHH8esN2C++Q8ehedRo74u7MWLTUJn9SD7RSgsCTEZCSRpVpMascDylAqPoldauOMfA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.4.3", + "@webassemblyjs/wasm-edit": "1.4.3", + "@webassemblyjs/wasm-parser": "1.4.3", + "acorn": "^5.0.0", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^0.1.1", + "enhanced-resolve": "^4.0.0", + "eslint-scope": "^3.7.1", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.0.0", + "uglifyjs-webpack-plugin": "^1.2.4", + "watchpack": "^1.5.0", + "webpack-sources": "^1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "requires": { + "source-list-map": "~0.1.7", + "source-map": "~0.4.1" + }, + "dependencies": { + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.2.0.tgz", + "integrity": "sha512-YJLMF/96TpKXaEQwaLEo+Z4NDK8aV133ROF6xp9pe3gQoS7sxfpXh4Rv9eC+8vCvWfmDjRQaMSlRPbO+9G6jgA==", + "dev": true, + "requires": { + "loud-rejection": "^1.6.0", + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "path-is-absolute": "^1.0.0", + "range-parser": "^1.0.3", + "url-join": "^4.0.0", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.6.tgz", + "integrity": "sha512-uc6YP0DzzW4870TDKoK73uONytLgu27h+x6XfgSvERRChkpd5Ils7US6d5k22LBoh0sDkmPZ6ERHSsrkwtkFFQ==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.18.0", + "import-local": "^1.0.0", + "internal-ip": "1.2.0", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "selfsigned": "^1.9.1", + "serve-index": "^1.7.2", + "sockjs": "0.3.19", + "sockjs-client": "1.1.5", + "spdy": "^3.4.1", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "webpack-dev-middleware": "3.2.0", + "webpack-log": "^2.0.0", + "yargs": "12.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "requires": { + "xregexp": "4.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "dev": true, + "optional": true + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz", + "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-merge": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.4.tgz", + "integrity": "sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ==", + "dev": true, + "requires": { + "lodash": "^4.17.5" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } + } + }, + "webpack-sources": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", + "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "webpack-subresource-integrity": { + "version": "1.1.0-rc.4", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.4.tgz", + "integrity": "sha1-xcTj1pD50vZKlVDgeodn+Xlqpdg=", + "dev": true, + "requires": { + "webpack-core": "^0.6.8" + } + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "when": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", + "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "worker-farm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", + "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz", + "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=", + "dev": true, + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + }, + "wtf-8": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", + "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + }, + "dependencies": { + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + } + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", + "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", + "dev": true + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "requires": { + "cuint": "^0.2.2" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "optional": true + } + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + }, + "zone.js": { + "version": "0.8.26", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.26.tgz", + "integrity": "sha512-W9Nj+UmBJG251wkCacIkETgra4QgBo/vgoEkb4a2uoLzpQG7qF9nzwoLXWU5xj3Fg2mxGvEDh47mg24vXccYjA==" + } + } +} diff --git a/web/src/main/webapp/v2/package.json b/web/src/main/webapp/v2/package.json new file mode 100644 index 000000000000..33202ebdd5c0 --- /dev/null +++ b/web/src/main/webapp/v2/package.json @@ -0,0 +1,81 @@ +{ + "name": "pinpoint", + "version": "2.0.0", + "license": "MIT", + "scripts": { + "ng": "ng", + "start": "ng serve pinpoint --proxy-config proxy.conf.json", + "start:real": "ng serve pinpoint --proxy-config proxy.conf.json --prod", + "build": "ng build pinpoint --base-href /v2/", + "build:real": "ng build pinpoint --prod --base-href /v2/", + "build:watch": "ng build pinpoint --base-href /v2/ --watch", + "test": "ng test pinpoint", + "lint": "ng lint pinpoint --type-check", + "e2e": "ng e2e pinpoint", + "precommit": "npm run lint" + }, + "private": true, + "dependencies": { + "@angular/animations": "^6.1.0", + "@angular/cdk": "^6.4.6", + "@angular/common": "^6.1.0", + "@angular/compiler": "^6.1.0", + "@angular/core": "^6.1.0", + "@angular/forms": "^6.1.0", + "@angular/http": "^6.1.0", + "@angular/material": "^6.4.6", + "@angular/platform-browser": "^6.1.0", + "@angular/platform-browser-dynamic": "^6.1.0", + "@angular/router": "^6.1.0", + "@ngrx/store": "^6.1.0", + "@ngui/datetime-picker": "^0.16.2", + "@ngx-translate/core": "^10.0.2", + "@ngx-translate/http-loader": "3.0.1", + "ag-grid": "^18.1.2", + "ag-grid-angular": "^18.1.0", + "angular-2-local-storage": "^2.0.0", + "bowser": "^1.9.3", + "chart.js": "git+https://github.com/chartjs/Chart.js.git#48fefd92b6dc61345021c6508b23698830ff392f", + "chartjs-plugin-streaming": "^1.6.0", + "core-js": "^2.5.4", + "gojs": "^1.8.28", + "hammerjs": "^2.0.8", + "moment": "^2.22.2", + "moment-timezone": "^0.5.21", + "ng-click-outside": "^4.0.0", + "ng2-nouislider": "^1.7.11", + "ngx-clipboard": "^11.1.3", + "ngx-highlightjs": "^2.1.1", + "ngx-infinite-scroll": "^6.0.1", + "nouislider": "^11.1.0", + "rxjs": "^6.2.1", + "snapsvg-cjs": "0.0.6", + "vis": "^4.21.0", + "zone.js": "^0.8.26" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~0.7.0", + "@angular/cli": "~6.1.5", + "@angular/compiler-cli": "^6.1.0", + "@angular/language-service": "^6.1.0", + "@types/chart.js": "^2.7.22", + "@types/jasmine": "~2.8.6", + "@types/jasminewd2": "~2.0.3", + "@types/node": "~8.9.4", + "@types/vis": "^4.21.5", + "codelyzer": "~4.2.1", + "husky": "^1.0.0-rc.9", + "jasmine-core": "~2.99.1", + "jasmine-spec-reporter": "~4.2.1", + "karma": "~1.7.1", + "karma-chrome-launcher": "~2.2.0", + "karma-coverage-istanbul-reporter": "~2.0.0", + "karma-jasmine": "~1.1.1", + "karma-jasmine-html-reporter": "^0.2.2", + "protractor": "~5.4.0", + "source-map-explorer": "^1.6.0", + "ts-node": "~5.0.1", + "tslint": "~5.9.1", + "typescript": "^2.7.2" + } +} diff --git a/web/src/main/webapp/v2/protractor.conf.js b/web/src/main/webapp/v2/protractor.conf.js new file mode 100644 index 000000000000..586384fd73a2 --- /dev/null +++ b/web/src/main/webapp/v2/protractor.conf.js @@ -0,0 +1,31 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +/*global jasmine */ +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + beforeLaunch: function() { + require('ts-node').register({ + project: 'e2e/tsconfig.e2e.json' + }); + }, + onPrepare: function() { + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/web/src/main/webapp/v2/proxy.conf.json b/web/src/main/webapp/v2/proxy.conf.json new file mode 100644 index 000000000000..eb4b558c17be --- /dev/null +++ b/web/src/main/webapp/v2/proxy.conf.json @@ -0,0 +1,191 @@ +{ + "/agent/activeThread.pinpointws": { + "target": "http://localhost:8080", + "secure": false, + "ws": true + }, + "/configuration.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/serverTime.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/userConfiguration.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/applications.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getServerMapData.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getServerMapDataV2.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getResponseTimeHistogramDataV2.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getLinkTimeHistogramData.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getFilteredServerMapDataMadeOfDotGroup.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentList.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentStat/jvmGc/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentStat/cpuLoad/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentStat/transaction/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentStat/activeTrace/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentStat/responseTime/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentStat/dataSource/chartList.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getApplicationStat/memory/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getApplicationStat/cpuLoad/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getApplicationStat/transaction/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getApplicationStat/activeTrace/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getApplicationStat/responseTime/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getApplicationStat/dataSource/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentStatusTimeline.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentEvents.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentInfo.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/transactionmetadata.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/transactionInfo.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/sqlBind.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/jsonBind.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getScatterData.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/agent/activeThreadLightDump.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/agent/activeThreadDump.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentInstallationInfo.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/isAvailableApplicationName.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/isAvailableAgentId.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/userGroup.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/user.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/userGroup/member.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/application/alarmRule/checker.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/application/alarmRule.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentStat/fileDescriptor/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getApplicationStat/fileDescriptor/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getAgentStat/directBuffer/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/getApplicationStat/directBuffer/chart.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/admin/removeAgentId.pinpoint": { + "target": "http://localhost:8080", + "secure": false + }, + "/admin/removeInactiveAgents.pinpoint": { + "target": "http://localhost:8080", + "secure": false + } +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/app.component.css b/web/src/main/webapp/v2/src/app/app.component.css new file mode 100644 index 000000000000..7f26ddcb5503 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/app.component.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/web/src/main/webapp/v2/src/app/app.component.html b/web/src/main/webapp/v2/src/app/app.component.html new file mode 100644 index 000000000000..0680b43f9c6a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/web/src/main/webapp/v2/src/app/app.component.ts b/web/src/main/webapp/v2/src/app/app.component.ts new file mode 100644 index 000000000000..cbca738af0ea --- /dev/null +++ b/web/src/main/webapp/v2/src/app/app.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { WindowRefService } from 'app/shared/services'; + +@Component({ + selector: 'pp-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent implements OnInit { + constructor( + private windowRefService: WindowRefService, + private translateService: TranslateService + ) { + const supportLanguages = ['en', 'ko']; + const defaultLang = 'en'; + const currentLang = this.windowRefService.nativeWindow.navigator.language.substring(0, 2); + this.translateService.addLangs(supportLanguages); + this.translateService.setDefaultLang(defaultLang); + if (supportLanguages.find((lang: string) => { + return lang === currentLang; + })) { + this.translateService.use(currentLang); + } else { + this.translateService.use(defaultLang); + } + } + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/app.module.ts b/web/src/main/webapp/v2/src/app/app.module.ts new file mode 100644 index 000000000000..acb91e64a3d2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/app.module.ts @@ -0,0 +1,115 @@ +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NgModule } from '@angular/core'; +import { HttpClientModule } from '@angular/common/http'; +import { RouterModule, Routes } from '@angular/router'; +import { StoreModule } from '@ngrx/store'; +import { LocalStorageModule } from 'angular-2-local-storage'; +import { HttpClient } from '@angular/common/http'; +import { APP_BASE_HREF } from '@angular/common'; +import { COMPOSITION_BUFFER_MODE } from '@angular/forms'; + +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; + +import { httpInterceptorProviders } from './core/httpInterceptor'; + +import { AppComponent } from './app.component'; +import { PageNotFoundComponent } from './shared/components/page-not-found'; + +import { reducers } from './shared/store'; +import { UrlPath } from './shared/models'; +import { SERVER_MAP_TYPE, ServerMapType } from 'app/core/components/server-map/class/server-map-factory'; +import { WindowRefService } from './shared/services/window-ref.service'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, 'assets/i18n/', '.json'); +} + +export const appRoutes: Routes = [ + { + path: UrlPath.ADMIN, + loadChildren: './routes/admin-page/index#AdminPageModule' + }, + { + path: UrlPath.BROWSER_NOT_SUPPORT, + loadChildren: './routes/browser-support-page/index#BrowserSupportPageModule' + }, + { + path: UrlPath.SCATTER_FULL_SCREEN_MODE, + loadChildren: './routes/scatter-full-screen-mode-page/index#ScatterFullScreenModePageModule' + }, + { + path: UrlPath.THREAD_DUMP, + loadChildren: './routes/thread-dump-page/index#ThreadDumpPageModule' + }, + { + path: UrlPath.REAL_TIME, + loadChildren: './routes/real-time-page/index#RealTimePageModule' + }, + { + path: UrlPath.TRANSACTION_VIEW, + loadChildren: './routes/transaction-view-page/index#TransactionViewPageModule' + }, + { + path: UrlPath.TRANSACTION_DETAIL, + loadChildren: './routes/transaction-detail-page/index#TransactionDetailPageModule' + }, + { + path: UrlPath.TRANSACTION_LIST, + loadChildren: './routes/transaction-list-page/index#TransactionListPageModule' + }, + { + path: UrlPath.INSPECTOR, + loadChildren: './routes/inspector-page/index#InspectorPageModule' + }, + { + path: UrlPath.FILTERED_MAP, + loadChildren: './routes/filtered-map-page/index#FilteredMapPageModule' + }, + { + path: UrlPath.MAIN, + loadChildren: './routes/main-page/index#MainPageModule' + }, + { + path: '', + redirectTo: '/' + UrlPath.MAIN, + pathMatch: 'full' + }, + { + path: '**', + component: PageNotFoundComponent + } +]; + +@NgModule({ + declarations: [ + AppComponent, + PageNotFoundComponent, + ], + imports: [ + BrowserAnimationsModule, + HttpClientModule, + StoreModule.forRoot(reducers, {}), + LocalStorageModule.withConfig({ + prefix: 'pp', + storageType: 'localStorage' + }), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + RouterModule.forRoot(appRoutes, { enableTracing: false }) + ], + providers: [ + WindowRefService, + httpInterceptorProviders, + { provide: APP_BASE_HREF, useValue: window.document.querySelector('base').getAttribute('href') }, + { provide: COMPOSITION_BUFFER_MODE, useValue: false }, + { provide: SERVER_MAP_TYPE, useValue: ServerMapType.GOJS } + ], + bootstrap: [AppComponent] +}) +export class AppModule {} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart-container.component.css new file mode 100644 index 000000000000..cfac521bb995 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart-container.component.css @@ -0,0 +1,6 @@ +.l-wrapper { + display: grid; + grid-template-columns: 45% 10% 45%; + grid-template-rows: 100%; + padding: 16px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart-container.component.html new file mode 100644 index 000000000000..9ff265df159d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart-container.component.html @@ -0,0 +1,18 @@ +
+
+ +
+
+
+ +
+ +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart-container.component.ts new file mode 100644 index 000000000000..b117e75afa45 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart-container.component.ts @@ -0,0 +1,56 @@ +import { Component, OnInit } from '@angular/core'; +import { Subject } from 'rxjs'; +import { StoreHelperService } from 'app/shared/services'; + +@Component({ + selector: 'pp-agent-admin-chart-container', + templateUrl: './agent-admin-chart-container.component.html', + styleUrls: ['./agent-admin-chart-container.component.css'] +}) +export class AgentAdminChartContainerComponent implements OnInit { + private unsubscribe: Subject = new Subject(); + showLoading = true; + agentCount = 0; + chartData: { + jvmVersion: { + [key: string]: number + }, + agentVersion: { + [key: string]: number + } + } = { + jvmVersion: {}, + agentVersion: {} + }; + constructor( + private storeHelperService: StoreHelperService + ) {} + ngOnInit() { + this.storeHelperService.getAgentList(this.unsubscribe).subscribe((agentList: IAgentList) => { + this.extractChartData(agentList); + this.showLoading = false; + }); + } + private extractChartData(agentList: IAgentList): void { + this.chartData = { + jvmVersion: {}, + agentVersion: {} + }; + let count = 0; + Object.keys(agentList).forEach((key: string) => { + const agents = agentList[key]; + agents.forEach((agent: IAgent) => { + count++; + if (agent.agentVersion) { + this.chartData.agentVersion[agent.agentVersion] = (this.chartData.agentVersion[agent.agentVersion] || 0) + 1; + } + if (agent.jvmInfo && agent.jvmInfo.jvmVersion) { + this.chartData.jvmVersion[agent.jvmInfo.jvmVersion] = (this.chartData.jvmVersion[agent.jvmInfo.jvmVersion] || 0) + 1; + } else { + this.chartData.jvmVersion['UNKNOWN'] = (this.chartData.jvmVersion['UNKNOWN'] || 0) + 1; + } + }); + }); + this.agentCount = count; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart.component.css new file mode 100644 index 000000000000..15beff3fe184 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart.component.css @@ -0,0 +1,6 @@ +:host { + width: 100%; + height: 420px; + display: block; + position: relative; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart.component.html new file mode 100644 index 000000000000..fad1fabe4c1e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart.component.html @@ -0,0 +1 @@ + diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart.component.ts new file mode 100644 index 000000000000..9ddd3555865a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/agent-admin-chart.component.ts @@ -0,0 +1,105 @@ +import { Component, OnInit, OnDestroy, Input, ViewChild, ElementRef } from '@angular/core'; +import { Chart } from 'chart.js'; + +@Component({ + selector: 'pp-agent-admin-chart', + templateUrl: './agent-admin-chart.component.html', + styleUrls: ['./agent-admin-chart.component.css'] +}) +export class AgentAdminChartComponent implements OnInit, OnDestroy { + @ViewChild('agentChart') el: ElementRef; + _chartData: any; + chartObj: Chart; + @Input() type: string; + @Input() title: string; + @Input() barColor: string; + @Input() + set chartData(value: any) { + if (value && value.jvmVersion && value.agentVersion) { + this._chartData = value; + this.initChartObj(); + } + } + constructor() {} + ngOnInit() {} + ngOnDestroy() {} + private initChartObj() { + this.chartObj = new Chart(this.el.nativeElement.getContext('2d'), { + type: 'horizontalBar', + data: this.makeDataOption(), + options: this.makeNormalOption() + }); + } + private makeDataOption(): any { + const labels: string[] = []; + const values: number[] = []; + const dataSet = this._chartData[this.type]; + Object.keys(dataSet).sort().forEach((key: string) => { + labels.push(key); + values.push(dataSet[key]); + }); + const dataOption = { + labels: labels, + borderWidth: 0, + datasets: [] + }; + dataOption.datasets.push({ + label: this.title, + data: values, + backgroundColor: this.barColor, + borderWidth: 0 + }); + return dataOption; + } + private makeNormalOption(): any { + return { + maintainAspectRatio: false, + tooltips: { + enabled: false + }, + scales: { + yAxes: [{ + gridLines: { + display: false + }, + ticks: { + fontFamily: 'monospace' + } + }], + xAxes: [{ + ticks: { + fontFamily: 'monospace' + } + }] + }, + animation: { + duration: 0, + onComplete: (chartElement: any) => { + const ctx = chartElement.chart.ctx; + ctx.fillStyle = chartElement.chart.config.options.defaultFontColor; + ctx.fontSize = 9; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + chartElement.chart.data.datasets.forEach((dataset: any) => { + for (let i = 0 ; i < dataset.data.length ; i++) { + const model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model; + ctx.fillText(dataset.data[i], model.x, model.y - 5); + } + }); + } + }, + hover: { + animationDuration: 0 + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10, + fontFamily: 'monospace' + }, + position: 'bottom' + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/index.ts b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/index.ts new file mode 100644 index 000000000000..3d3e5088a246 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-admin-chart/index.ts @@ -0,0 +1,20 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { AgentAdminChartContainerComponent } from './agent-admin-chart-container.component'; +import { AgentAdminChartComponent } from './agent-admin-chart.component'; + +@NgModule({ + declarations: [ + AgentAdminChartComponent, + AgentAdminChartContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + AgentAdminChartContainerComponent + ], + providers: [] +}) +export class AgentAdminChartModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view-container.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view-container.component.css new file mode 100644 index 000000000000..b16de9367293 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view-container.component.css @@ -0,0 +1,4 @@ +:host { + display: block; + border-bottom: 1px solid #E5E8F0; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view-container.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view-container.component.html new file mode 100644 index 000000000000..972c73a41877 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view-container.component.html @@ -0,0 +1,6 @@ + diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view-container.component.ts new file mode 100644 index 000000000000..5f1c7be33a38 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view-container.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { takeUntil, switchMap } from 'rxjs/operators'; + +import { StoreHelperService } from 'app/shared/services'; +import { ITimelineEventSegment } from 'app/core/components/timeline/class/timeline-data.class'; +import { TimelineInteractionService } from 'app/core/components/timeline/timeline-interaction.service'; +import { AgentEventsDataService, IEventStatus } from './agent-events-data.service'; + +@Component({ + selector: 'pp-agent-event-view-container', + templateUrl: './agent-event-view-container.component.html', + styleUrls: ['./agent-event-view-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentEventViewContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + viewComponent = false; + eventData: IEventStatus[]; + timezone$: Observable; + dateFormat$: Observable; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private timelineInteractionService: TimelineInteractionService, + private agentEventsDataService: AgentEventsDataService + ) {} + ngOnInit() { + this.connectStore(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.dateFormat$ = this.storeHelperService.getDateFormat(this.unsubscribe, 1); + this.timelineInteractionService.onSelectEventStatus$.pipe( + takeUntil(this.unsubscribe), + switchMap((eventSegment: ITimelineEventSegment) => { + return this.agentEventsDataService.getData(eventSegment.startTimestamp, eventSegment.endTimestamp); + }) + ).subscribe((response: IEventStatus[]) => { + this.eventData = response; + this.viewComponent = true; + this.changeDetectorRef.detectChanges(); + }); + } + onClose(): void { + this.viewComponent = false; + this.changeDetectorRef.detectChanges(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view.component.css new file mode 100644 index 000000000000..78361b6a4345 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view.component.css @@ -0,0 +1,25 @@ +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background-color: #9DD4BB; +} +.l-title-group button { + color: #000; + float: right; + font-size: 14px; +} +thead th { + text-align: left; +} +td div { + width: 100%; + min-height: 40px; + max-height: 200px; + overflow-y: auto; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view.component.html new file mode 100644 index 000000000000..b6565dfceb3d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view.component.html @@ -0,0 +1,23 @@ +
Event - {{eventData?.length}}
+
+ + + + + + + + + + + + + + + + + + + +
Events
TimeDescriptionMessage
{{formatDate(eventStatus.eventTimestamp)}}{{eventStatus.eventTypeDesc}}
{{eventStatus!.eventMessage}}
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view.component.ts new file mode 100644 index 000000000000..b218831d8926 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-event-view.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import * as moment from 'moment-timezone'; +import { IEventStatus } from './agent-events-data.service'; + +@Component({ + selector: 'pp-agent-event-view', + templateUrl: './agent-event-view.component.html', + styleUrls: ['./agent-event-view.component.css'] +}) +export class AgentEventViewComponent implements OnInit { + @Input() eventData: IEventStatus[]; + @Input() timezone: string; + @Input() dateFormat: string; + @Output() outClose: EventEmitter = new EventEmitter(); + constructor() {} + ngOnInit() {} + onClickClose(): void { + this.outClose.next(); + } + formatDate(time: number): string { + return moment(time).tz(this.timezone).format(this.dateFormat) ; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-events-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-events-data.service.ts new file mode 100644 index 000000000000..7b23930a9f29 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/agent-events-data.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService } from 'app/shared/services'; + +export interface IEventStatus { + agentId: string; + eventMessage?: string; + eventTimestamp: number; + eventTypeCode: number; + eventTypeDesc: string; + hasEventMessage: boolean; + startTimestamp: number; +} + +@Injectable() +export class AgentEventsDataService { + requestURL = 'getAgentEvents.pinpoint'; + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService + ) { } + getData(from: number, to: number): Observable { + return this.http.get(this.requestURL, this.makeRequestOptionsArgs(from, to)); + } + private makeRequestOptionsArgs(from: number, to: number): object { + return { + params: { + agentId: this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), + from: from, + to: to, + exclude: 10199 + // DESC: + // [exclude] 요청에 대한 응답에서 제외하고 싶은 eventCode를 넣어줌. + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-event-view/index.ts b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/index.ts new file mode 100644 index 000000000000..191d8e6d6871 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-event-view/index.ts @@ -0,0 +1,23 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { AgentEventViewContainerComponent } from './agent-event-view-container.component'; +import { AgentEventViewComponent } from './agent-event-view.component'; +import { AgentEventsDataService } from './agent-events-data.service'; + +@NgModule({ + declarations: [ + AgentEventViewComponent, + AgentEventViewContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + AgentEventViewContainerComponent + ], + providers: [ + AgentEventsDataService + ] +}) +export class AgentEventViewModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-container.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-container.component.css new file mode 100644 index 000000000000..3c6c93c9e463 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-container.component.css @@ -0,0 +1,21 @@ +:host { + width: calc(100% - 20px); + display: block; + margin: 20px 10px; + border: 1px solid #000; + position: relative; +} + +.l-title-group { + height: 34px; + font-size: 13px; + font-weight: 600; + padding: 0 20px; + color: #333; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + align-items: center; + justify-content: space-between; + background: #f6f8fb; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-container.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-container.component.html new file mode 100644 index 000000000000..8ce5d77a0c38 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-container.component.html @@ -0,0 +1,11 @@ +
Information
+ + + + + diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-container.component.ts new file mode 100644 index 000000000000..e6b28f903d00 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-container.component.ts @@ -0,0 +1,91 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Observable, Subject, combineLatest } from 'rxjs'; +import { takeUntil, filter, tap, map, switchMap } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { + StoreHelperService, + NewUrlStateNotificationService, + DynamicPopupService +} from 'app/shared/services'; +import { ApplicationNameIssuePopupContainerComponent } from 'app/core/components/application-name-issue-popup/application-name-issue-popup-container.component'; +import { AgentInfoDataService } from './agent-info-data.service'; + +@Component({ + selector: 'pp-agent-info-container', + templateUrl: './agent-info-container.component.html', + styleUrls: ['./agent-info-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentInfoContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private selectedTime$: Observable; + private urlAgentId$: Observable; + urlApplicationName$: Observable; + agentData$: Observable; + timezone$: Observable; + dateFormat$: Observable; + showLoading = true; + + constructor( + private changeDetectorRef: ChangeDetectorRef, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private storeHelperService: StoreHelperService, + private agentInfoDataService: AgentInfoDataService, + private dynamicPopupService: DynamicPopupService, + ) {} + + ngOnInit() { + this.urlAgentId$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + map((urlService: NewUrlStateNotificationService) => { + return urlService.getPathValue(UrlPathId.AGENT_ID); + }) + ); + this.urlApplicationName$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + map((urlService: NewUrlStateNotificationService) => { + return urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName(); + }) + ); + this.connectStore(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.dateFormat$ = this.storeHelperService.getDateFormat(this.unsubscribe, 1); + this.selectedTime$ = this.storeHelperService.getInspectorTimelineSelectedTime(this.unsubscribe); + this.agentData$ = combineLatest( + this.urlAgentId$, + this.selectedTime$ + ).pipe( + tap(() => { + this.showLoading = true; + this.changeDetectorRef.detectChanges(); + }), + switchMap((data: [string, number]) => { + return this.agentInfoDataService.getData(data[0], data[1]); + }), + filter((agentData: IServerAndAgentData) => { + return !!(agentData && agentData.applicationName); + }), + tap(() => { + this.showLoading = false; + this.changeDetectorRef.detectChanges(); + }) + ); + } + + onClickApplicationNameIssue({data, coord}: {data: {[key: string]: string}, coord: ICoordinate}): void { + this.dynamicPopupService.openPopup({ + data, + coord, + component: ApplicationNameIssuePopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-data.service.ts new file mode 100644 index 000000000000..1973dda2f919 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info-data.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable() +export class AgentInfoDataService { + private requestURL = 'getAgentInfo.pinpoint'; + constructor( + private http: HttpClient + ) {} + getData(agentId: string, timestamp: number): Observable { + return this.http.get(this.requestURL, this.makeRequestOptionsArgs(agentId, timestamp)); + } + private makeRequestOptionsArgs(agentId: string, timestamp: number): object { + return { + params: { + agentId, + timestamp + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info.component.css new file mode 100644 index 000000000000..7cf5879c9b4d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info.component.css @@ -0,0 +1,96 @@ +:host { + display: block; +} +.l-info-table { + border: none; +} +.l-info-table tbody th, .l-info-table td { + border-top:1px solid #e5e8f0; +} +.l-info-table tr:nth-child(2n) { + background-color: #FAFAFC; +} +.l-argument-list { + width: 100%; + height: 150px; + overflow: auto; +} +.l-server-info-service-list { + float: left; + width: 40%; + max-height: 140px; + overflow: auto; +} +.l-server-info-lib-list { + float: left; + width: 60%; + height: 150px; + overflow: auto; +} +.l-server-info-list { + border: 1px solid #e7e8ec; + position: relative; + margin-right: 10px; +} +.l-server-info-list li { + height: 30px; + font-size: 12px; + color:#999; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 10px; + cursor: pointer; + border-top: 1px solid #e7e8ec; +} +.l-server-info-list li:first-child { + border-top-color: transparent; +} +.l-server-info-list li.active { + color:#fff; + background-color:#4a8fd2; +} +.l-server-info-list li ul { + position: absolute; + right: -250px; + top: 0; + color:#999; +} +.l-server-info-list li ul li { + height: auto; + border-top: none; +} +.l-category { + display: inline-block; + padding: 2px 5px; + font-size: 10px; + color: #fff; + font-weight: 600; + cursor: pointer; +} +.l-category.green { + background:#23c6c8; +} +.l-category.blue { + background:#5597d5; +} +.l-sub-table { + display: table-cell; + padding:16px 19px;background:#fff +} +.l-sub-table .table th, .l-sub-table .table td { + padding:14px 10px; +} +.l-sub-table .table td + td { + border-left:1px solid #e5e8f0; +} +.l-sub-table .table tr { + background:#fff; +} +.l-not-same { + color: #f27d70; +} +.l-not-same-button { + float: right; + font-size: 14px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info.component.html new file mode 100644 index 000000000000..eacb43826d37 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info.component.html @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Information
Application Name{{agentData.applicationName}} Agent Version{{agentData.agentVersion}}
Agent Id{{agentData.agentId}}PID{{agentData.pid}}
Hostname{{agentData.hostName}}JVM (GC Type){{agentData.jvmInfo?.jvmVersion}} ({{agentData.jvmInfo?.gcTypeName}})
IP{{agentData.ip}}Start Time{{formatDate(agentData.startTimestamp)}}
Service Type{{agentData.serviceType}} ({{agentData.serverMetaData?.serverInfo}}) Detail End Status{{agentData.status?.state?.desc}} (last checked : {{formatDate(agentData.status?.eventTimestamp)}})
+ + + + + + + + + + + + + + + + + + + + +
Server Info{{isServiceInfoEmpty() ? "" : agentData.serverMetaData.serverInfo}}
JVM Arguments +
+
{{vm}}
+
+
Services + +
+
    +
  • {{service.serviceName}} +
  • +
+
+
+
    +
  • {{lib}}
  • +
+
+
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info.component.ts new file mode 100644 index 000000000000..6d6d0a5b1f0c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-info/agent-info.component.ts @@ -0,0 +1,70 @@ +import { Component, OnInit, Input, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core'; +import * as moment from 'moment-timezone'; + +import { AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +@Component({ + selector: 'pp-agent-info', + templateUrl: './agent-info.component.html', + styleUrls: ['./agent-info.component.css'] +}) +export class AgentInfoComponent implements OnInit, OnChanges { + @Input() urlApplicationName: string; + @Input() agentData: IServerAndAgentData; + @Input() timezone: string; + @Input() dateFormat: string; + @Output() outClickApplicationNameIssue = new EventEmitter<{[key: string]: any}>(); + + isHideDetailInfo = true; + selectedServiceIndex = 0; + + constructor( + private analyticsService: AnalyticsService, + ) {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + Object.keys(changes) + .filter((propName: string) => { + return changes[propName].currentValue; + }) + .forEach((propName: string) => { + switch (propName) { + case 'agentData': + this.isHideDetailInfo = true; + this.selectedServiceIndex = 0; + break; + } + }); + } + toggleDetailInfo(): boolean { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_SERVER_TYPE_DETAIL); + return this.isHideDetailInfo = !this.isHideDetailInfo; + } + onSelectService(index: number): void { + this.selectedServiceIndex = index; + } + getSelectedServiceLib(): string[] { + return this.agentData.serverMetaData.serviceInfos[this.selectedServiceIndex].serviceLibs; + } + isServiceInfoEmpty(): boolean { + return !this.agentData.serverMetaData || this.agentData.serverMetaData.serviceInfos.length === 0; + } + isSameApplication(): boolean { + return this.agentData.applicationName === this.urlApplicationName; + } + formatDate(time: number): string { + return moment(time).tz(this.timezone).format(this.dateFormat) ; + } + onClickNotSameBtn($event: MouseEvent): void { + const {left, top, width, height} = ($event.currentTarget as HTMLElement).getBoundingClientRect(); + const { agentId, applicationName } = this.agentData; + + this.outClickApplicationNameIssue.emit({ + data: { agentId, applicationName }, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + } + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-info/index.ts b/web/src/main/webapp/v2/src/app/core/components/agent-info/index.ts new file mode 100644 index 000000000000..c77c662faf27 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-info/index.ts @@ -0,0 +1,26 @@ + +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { AgentInfoContainerComponent } from './agent-info-container.component'; +import { AgentInfoComponent } from './agent-info.component'; +import { AgentInfoDataService } from './agent-info-data.service'; +import { ApplicationNameIssuePopupModule } from 'app/core/components/application-name-issue-popup'; + +@NgModule({ + declarations: [ + AgentInfoComponent, + AgentInfoContainerComponent + ], + imports: [ + SharedModule, + ApplicationNameIssuePopupModule + ], + exports: [ + AgentInfoContainerComponent + ], + providers: [ + AgentInfoDataService + ] +}) +export class AgentInfoModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/agent-inspector-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/agent-inspector-contents-container.component.css new file mode 100644 index 000000000000..011f388f9d5d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/agent-inspector-contents-container.component.css @@ -0,0 +1,23 @@ +:host { + display: flex; + flex-flow: column nowrap; +} +.l-date-range { + border-bottom: 1px solid #e5e8f0; + background-color: #FFF; + min-height: 161px; + max-height: 1000px +} +.l-main-contents { + height: 100%; + position: relative; + overflow: auto; +} +.l-chart-group-wrap { + display: flex; + flex-flow: row wrap; +} +.l-empty-content { + width: calc(50% - 20px); + margin: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/agent-inspector-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/agent-inspector-contents-container.component.html new file mode 100644 index 000000000000..147350153586 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/agent-inspector-contents-container.component.html @@ -0,0 +1,24 @@ +
+ + +
+ +
+ +
+ + + +
+ + + + + +
+ + + + +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/agent-inspector-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/agent-inspector-contents-container.component.ts new file mode 100644 index 000000000000..788bf1116c0b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/agent-inspector-contents-container.component.ts @@ -0,0 +1,11 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'pp-agent-inspector-contents-container', + templateUrl: './agent-inspector-contents-container.component.html', + styleUrls: ['./agent-inspector-contents-container.component.css'] +}) +export class AgentInspectorContentsContainerComponent implements OnInit { + constructor() {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/index.ts new file mode 100644 index 000000000000..3938939af7ba --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-inspector-contents/index.ts @@ -0,0 +1,28 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { AgentInspectorContentsContainerComponent } from './agent-inspector-contents-container.component'; +import { TimelineCommandGroupModule } from 'app/core/components/timeline-command-group'; +import { AgentEventViewModule } from 'app/core/components/agent-event-view'; +import { AgentInfoModule } from 'app/core/components/agent-info'; +import { TimelineModule } from 'app/core/components/timeline'; +import { InspectorChartModule } from 'app/core/components/inspector-chart'; + +@NgModule({ + declarations: [ + AgentInspectorContentsContainerComponent + ], + imports: [ + SharedModule, + TimelineCommandGroupModule, + AgentEventViewModule, + AgentInfoModule, + TimelineModule, + InspectorChartModule + ], + exports: [ + AgentInspectorContentsContainerComponent + ], + providers: [] +}) +export class AgentInspectorContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list-container.component.css new file mode 100644 index 000000000000..40d41d0eaddb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list-container.component.css @@ -0,0 +1,4 @@ +:host { + width: 100%; + height: 600px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list-container.component.html new file mode 100644 index 000000000000..1b9c9682ffb1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list-container.component.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list-container.component.ts new file mode 100644 index 000000000000..b9c17b5cda76 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list-container.component.ts @@ -0,0 +1,92 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; + +import { UrlPath } from 'app/shared/models'; +import { StoreHelperService, UrlRouteManagerService } from 'app/shared/services'; + +interface IGridData { + index: number; + application: string; + serviceType: string; + agent: string; + agentVersion: string; + jvmVersion: string; + folder?: boolean; + open?: boolean; + children?: IGridData[]; +} + +@Component({ + selector: 'pp-agent-list-container', + templateUrl: './agent-list-container.component.html', + styleUrls: ['./agent-list-container.component.css'] +}) +export class AgentListContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + agentCount = 0; + agentListData: any; + constructor( + private storeHelperService: StoreHelperService, + private urlRouteManagerService: UrlRouteManagerService + ) {} + ngOnInit() { + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getAgentList(this.unsubscribe).subscribe((agentList: IAgentList) => { + this.makeGridData(agentList); + }); + } + private makeGridData(agentList: IAgentList): void { + let index = 1; + const resultData: IGridData[] = []; + Object.keys(agentList).forEach((key: string, innerIndex: number) => { + const list: IAgent[] = agentList[key]; + let row: IGridData; + + if (list.length === 0) { + row = this.makeRow(list[0], index, false, false); + index++; + } else { + list.forEach((agent: IAgent, agentIndex: number) => { + if (agentIndex === 0) { + row = this.makeRow(agent, index, true, false); + } else { + row.children.push(this.makeRow(agent, index, false, true)); + } + index++; + }); + } + resultData.push(row); + }); + this.agentCount = index - 1; + this.agentListData = resultData; + } + private makeRow(agent: IAgent, index: number, hasChild: boolean, isChild: boolean): any { + const oRow: IGridData = { + index: index, + application: isChild ? '' : agent.applicationName, + serviceType: agent.serviceType, + agent: agent.agentId, + agentVersion: agent.agentVersion, + jvmVersion: agent.jvmInfo ? agent.jvmInfo.jvmVersion : '' + }; + if (hasChild) { + oRow.folder = true; + oRow.open = true; + oRow.children = []; + } + + return oRow; + } + onCellClick(params: any): void { + this.urlRouteManagerService.openPage([ + UrlPath.MAIN, + params.application + '@' + params.serviceType + ]); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list.component.html new file mode 100644 index 000000000000..a7504ac4ebc9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list.component.html @@ -0,0 +1,8 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list.component.ts new file mode 100644 index 000000000000..31666d77fea6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-list/agent-list.component.ts @@ -0,0 +1,102 @@ +import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; +import { GridOptions } from 'ag-grid'; + +@Component({ + selector: 'pp-agent-list', + templateUrl: './agent-list.component.html', + styleUrls: ['./agent-list.component.css'], + encapsulation: ViewEncapsulation.None +}) +export class AgentListComponent implements OnInit, OnDestroy { + @Input() gridData: any; + @Input() agentCount: number; + @Output() outCellClick: EventEmitter = new EventEmitter(); + gridOptions: GridOptions; + constructor() {} + ngOnInit() { + this.initGridOptions(); + } + ngOnDestroy() {} + private initGridOptions() { + this.gridOptions = { + columnDefs : this.makeColumnDefs(), + headerHeight: 34, + enableFilter: true, + floatingFilter: true, + enableColResize: true, + enableSorting: false, + animateRows: true, + rowHeight: 30, + getNodeChildDetails: (file) => { + if (file && file.folder) { + return { + group: true, + children: file.children, + expanded: file.open + }; + } else { + return null; + } + }, + suppressRowClickSelection: true, + rowSelection: 'multiple' + }; + } + private makeColumnDefs(): any { + return [ + { + headerName: '#', + field: 'index', + width: 60, + suppressFilter: true + }, + { + headerName: `Application`, + field: 'application', + width: 550, + cellRenderer: 'group', + cellRendererParams: { + innerRenderer: (params: any) => { + return ' ' + params.data.application; + }, + suppressCount: true + }, + cellStyle: { + color: 'rgb(54, 162, 235)', + 'font-weight': 600 + }, + filter: 'agTextColumnFilter', + tooltipField: 'application' + }, + { + headerName: `Agent`, + field: 'agent', + width: 300, + filter: 'agTextColumnFilter', + tooltipField: 'agent' + }, + { + headerName: 'Agent Version', + field: 'agentVersion', + width: 150, + filter: 'agTextColumnFilter', + tooltipField: 'agentVersion' + }, + { + headerName: 'JVM Version', + field: 'jvmVersion', + width: 150, + filter: 'agTextColumnFilter', + tooltipField: 'jvmVersion' + }, + ]; + } + onCellClick(params: any): void { + if (params.colDef.field === 'application') { + this.outCellClick.next({ + application: params.data.application, + serviceType: params.data.serviceType + }); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-list/index.ts b/web/src/main/webapp/v2/src/app/core/components/agent-list/index.ts new file mode 100644 index 000000000000..564c5ccc2857 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-list/index.ts @@ -0,0 +1,22 @@ + +import { NgModule } from '@angular/core'; +import { AgGridModule } from 'ag-grid-angular/main'; +import { SharedModule } from 'app/shared'; +import { AgentListContainerComponent } from './agent-list-container.component'; +import { AgentListComponent } from './agent-list.component'; + +@NgModule({ + declarations: [ + AgentListComponent, + AgentListContainerComponent + ], + imports: [ + SharedModule, + AgGridModule.withComponents([]) + ], + exports: [ + AgentListContainerComponent + ], + providers: [] +}) +export class AgentListModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/agent-management-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/agent-management-contents-container.component.css new file mode 100644 index 000000000000..ea9482f2089a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/agent-management-contents-container.component.css @@ -0,0 +1,3 @@ +:host { + overflow: auto; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/agent-management-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/agent-management-contents-container.component.html new file mode 100644 index 000000000000..c43cc51b42b8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/agent-management-contents-container.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/agent-management-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/agent-management-contents-container.component.ts new file mode 100644 index 000000000000..c9f85eeecbf0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/agent-management-contents-container.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { StoreHelperService } from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { ApplicationListDataService } from 'app/core/components/application-list/application-list-data.service'; + +@Component({ + selector: 'pp-agent-management-contents-container', + templateUrl: './agent-management-contents-container.component.html', + styleUrls: ['./agent-management-contents-container.component.css'] +}) +export class AgentManagementContentsContainerComponent implements OnInit { + private unsubscribe: Subject = new Subject(); + constructor( + private storeHelperService: StoreHelperService, + private applicationListDataService: ApplicationListDataService + ) { + + } + ngOnInit() { + this.applicationListDataService.getApplicationList().pipe( + takeUntil(this.unsubscribe) + ).subscribe((applicationList: IApplication[]) => { + this.storeHelperService.dispatch(new Actions.UpdateApplicationList(applicationList)); + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/index.ts new file mode 100644 index 000000000000..58257d099646 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-management-contents/index.ts @@ -0,0 +1,22 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { AgentManagerModule } from 'app/core/components/agent-manager'; +import { ApplicationListModule } from 'app/core/components/application-list'; +import { AgentManagementContentsContainerComponent } from './agent-management-contents-container.component'; + +@NgModule({ + declarations: [ + AgentManagementContentsContainerComponent + ], + imports: [ + SharedModule, + ApplicationListModule, + AgentManagerModule + ], + exports: [ + AgentManagementContentsContainerComponent + ], + providers: [] +}) +export class AgentManagementContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-container.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-container.component.css new file mode 100644 index 000000000000..5c42dc4188f4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-container.component.css @@ -0,0 +1,30 @@ +.l-header { + padding: 10px; + margin-left: 14px; + margin-right: 20px; +} +.l-inactive-remover { + float: right; +} +.l-inactive-remover button { + margin-left: 10px; +} +.l-inactive-remover button[disabled] { + background-color: #AAA; +} +.l-header input { + width: 300px; + color: #b3b3b4; + border: 1px solid #469ae4; + padding: 6px 11px; + font-size: 13px; +} +.l-wrapper { + width: 100%; + display: grid; + padding: 10px; + font-family: sans-serif; + background-color: #FFF; + grid-template-rows: auto; + grid-template-columns: 33% 33% 33%; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-container.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-container.component.html new file mode 100644 index 000000000000..88f7525e423f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-container.component.html @@ -0,0 +1,21 @@ + +
+ + + +
+ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-container.component.ts new file mode 100644 index 000000000000..7c80cc44e51c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-container.component.ts @@ -0,0 +1,86 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { ApplicationListDataService } from 'app/core/components/application-list/application-list-data.service'; +import { AgentManagerDataService } from './agent-manager-data.service'; + +@Component({ + selector: 'pp-agent-manager-container', + templateUrl: './agent-manager-container.component.html', + styleUrls: ['./agent-manager-container.component.css'] +}) +export class AgentManagerContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + applicationFilter = ''; + showLoading = false; + applicationList: IApplication[]; + agentList: { + [key: string]: any; + } = {}; + canRemoveInactiveAgent = false; + constructor( + private applicationListDataService: ApplicationListDataService, + private agentManagerDataService: AgentManagerDataService + ) {} + ngOnInit() { + this.applicationListDataService.getApplicationList().pipe( + takeUntil(this.unsubscribe) + ).subscribe((applicationList: IApplication[]) => { + this.applicationList = applicationList; + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + getAgentList(application: IApplication): string[] { + return this.agentList[application.applicationName]; + } + onLoadAgentList(applicationName: string): void { + this.agentManagerDataService.getAgentList(applicationName).subscribe((agentList: any) => { + const agentInfoList: any[] = []; + Object.keys(agentList).forEach((key: string) => { + agentList[key].forEach((agent: IAgent) => { + agentInfoList.push({ + applicationName: agent.applicationName, + agentId: agent.agentId + }); + }); + }); + this.agentList[applicationName] = agentInfoList; + }); + } + onRemoveAgent([applicationName, agentId]: [string, string]): void { + this.agentManagerDataService.removeAgentId(applicationName, agentId).subscribe((result: string) => { + if (result === 'OK') { + const appInfo = this.agentList[applicationName]; + const index = appInfo.findIndex((app: any) => { + return app.agentId === agentId; + }); + appInfo.splice(index, 1); + } + }); + } + onRemoveInactiveAgents(): void { + if (this.canRemoveInactiveAgent === false) { + return; + } + this.showLoading = true; + } + hasFilterStr(appName: string): boolean { + const filter = this.applicationFilter.trim(); + if (filter === '') { + return true; + } + if (appName.indexOf(filter) === -1) { + return false; + } else { + return true; + } + } + onChangeCanRemoveInactiveAgent($event: any): void { + this.canRemoveInactiveAgent = $event.checked; + console.log( this.canRemoveInactiveAgent ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-data.service.ts new file mode 100644 index 000000000000..b101a857577e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager-data.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@angular/core'; +import { HttpParams } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Observable, throwError, of } from 'rxjs'; +import { catchError, tap } from 'rxjs/operators'; + + +@Injectable() +export class AgentManagerDataService { + private listUrl = 'getAgentList.pinpoint'; + private removeUrl = 'admin/removeAgentId.pinpoint'; + private removeInactiveUrl = 'admin/removeInactiveAgents.pinpoint'; + + constructor(private http: HttpClient) {} + getAgentList(appName: string): Observable { + return this.http.get(this.listUrl, { + params: new HttpParams().set('application', appName) + }).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + removeAgentId(appName: string, agentId: string): Observable { + return this.http.post(this.removeUrl, { + params: new HttpParams().set('applicationName', appName).set('agentId', agentId) + }).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + removeInactiveAgents(): Observable { + return this.http.get(this.removeInactiveUrl).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + private handleError(error: HttpErrorResponse) { + return throwError(error.statusText || error); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager.component.css new file mode 100644 index 000000000000..fcc6b610c5ca --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager.component.css @@ -0,0 +1,62 @@ +.l-out-wrapper { + background: #FFF; + width: 95%; + position: relative; + z-index: 2; + overflow: hidden; + padding: 4px; + margin: 10px 14px; + box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.23); +} +.l-wrapper { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.l-wrapper button { + color: #4b99e3; + float: right; + font-size: 18px; +} +.l-wrapper button.l-remove { + color: #b5213c; + height: 20px; + width: 20px; + font-size: 16px; +} +.l-none { + float: right; + height: 18px; +} +.l-agent-list { + color: #000; + background-color: #F1F1F1; + padding: 6px; + height: 120px; + overflow: auto; + min-height: 120px; + max-height: 120px; + margin-top: 12px; +} +.l-agent { + padding: 2px 6px; +} +.l-loading { + height: 120px; + position: relative; + margin-top: 12px; +} +.l-empty { + color: #AAA; + background-color: #DDD; +} +.l-dup { + color: #F00; +} +.l-has { + +} +.l-yet { + +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager.component.html new file mode 100644 index 000000000000..22a96417ef87 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager.component.html @@ -0,0 +1,17 @@ +
+
+ + empty + {{application.applicationName + '@' + application.serviceType}} +
+
+ +
+
+
+ + + {{agent.agentId}} +
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager.component.ts new file mode 100644 index 000000000000..ee7bed36ec99 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-manager/agent-manager.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +interface IAgentShortInfo { + applicationName: string; + agentId: string; +} + +@Component({ + selector: 'pp-agent-manager', + templateUrl: './agent-manager.component.html', + styleUrls: ['./agent-manager.component.css'] +}) +export class AgentManagerComponent implements OnInit { + @Input() application: IApplication; + @Input() + set agentList(value: IAgentShortInfo[]) { + this._agentList = value; + this.showLoading = false; + } + @Output() outLoadAgentList: EventEmitter = new EventEmitter(); + @Output() outRemoveAgent: EventEmitter = new EventEmitter(); + _agentList: IAgentShortInfo[]; + showLoading = false; + constructor() {} + ngOnInit() {} + onLoadAgentList(): void { + if (this.showLoading === true) { + return; + } + this.showLoading = true; + this.outLoadAgentList.emit(this.application.applicationName); + } + onRemoveAgent(agentId: string): void { + this.outRemoveAgent.emit([this.application.applicationName, agentId]); + } + getAgentStateClass(): string { + return 'l-' + this.getAgentState(); + } + getAgentState(): string { + if (this._agentList) { + return this._agentList.length > 0 ? 'has' : 'empty'; + } else { + return 'yet'; + } + } + isDup(agent: IAgentShortInfo): boolean { + return this.application.applicationName !== agent.applicationName; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-manager/index.ts b/web/src/main/webapp/v2/src/app/core/components/agent-manager/index.ts new file mode 100644 index 000000000000..c2a21e2555b4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-manager/index.ts @@ -0,0 +1,26 @@ + +import { NgModule } from '@angular/core'; +import { MatTooltipModule, MatSlideToggleModule } from '@angular/material'; +import { SharedModule } from 'app/shared'; +import { AgentManagerContainerComponent } from './agent-manager-container.component'; +import { AgentManagerComponent } from './agent-manager.component'; +import { AgentManagerDataService } from './agent-manager-data.service'; + +@NgModule({ + declarations: [ + AgentManagerComponent, + AgentManagerContainerComponent + ], + imports: [ + MatSlideToggleModule, + MatTooltipModule, + SharedModule + ], + exports: [ + AgentManagerContainerComponent + ], + providers: [ + AgentManagerDataService + ] +}) +export class AgentManagerModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-search-input/agent-search-input-container.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-search-input/agent-search-input-container.component.css new file mode 100644 index 000000000000..06c6835cc70c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-search-input/agent-search-input-container.component.css @@ -0,0 +1,41 @@ +.l-container-wrapper { + display: flex; + flex-flow: row wrap; + height: 66px; + background: #e0e7ee; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + position: absolute; + width: 100%; + bottom: 0px; + left: 0px; +} +.l-container-wrapper button { + color: #a8acb5; + font-size: 18px; +} +.l-wrapper { + background: #fff; + height: 32px; + width: 183px; + color: #b3b3b4; + position: relative; + margin-right: 10px; +} +.l-wrapper input { + width: 100%; + height: 100%; + border: 1px solid #d7dde4; + padding: 0 10px 0 10px; +} +.l-wrapper button { + position: absolute; + top: 50%; + right: 10px; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-search-input/agent-search-input-container.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-search-input/agent-search-input-container.component.html new file mode 100644 index 000000000000..3dce246abff5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-search-input/agent-search-input-container.component.html @@ -0,0 +1,11 @@ +
+
+ + +
+ +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-search-input/agent-search-input-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-search-input/agent-search-input-container.component.ts new file mode 100644 index 000000000000..3aaa4fa127fc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-search-input/agent-search-input-container.component.ts @@ -0,0 +1,56 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { combineLatest } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; + +import { Actions } from 'app/shared/store'; +import { TranslateReplaceService, AnalyticsService, TRACKED_EVENT_LIST, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-agent-search-input-container', + templateUrl: './agent-search-input-container.component.html', + styleUrls: ['./agent-search-input-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentSearchInputContainerComponent implements OnInit { + i18nText: { [key: string]: string } = { + MIN_LENGTH_MSG: '' + }; + searchUseEnter = false; + SEARCH_MIN_LENGTH = 2; + constructor( + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.getI18NText(); + } + private getI18NText(): void { + combineLatest( + this.translateService.get('COMMON.MIN_LENGTH') + ).subscribe((i18n: string[]) => { + this.i18nText.MIN_LENGTH_MSG = this.translateReplaceService.replace(i18n[0], this.SEARCH_MIN_LENGTH); + }); + } + onSearchQuery(query: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SEARCH_AGENT); + this.storeHelperService.dispatch(new Actions.UpdateFilterOfServerAndAgentList(query)); + } + + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.AGENT_LIST); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.AGENT_LIST, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-search-input/index.ts b/web/src/main/webapp/v2/src/app/core/components/agent-search-input/index.ts new file mode 100644 index 000000000000..070acf13c07a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-search-input/index.ts @@ -0,0 +1,21 @@ + +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { AgentSearchInputContainerComponent } from './agent-search-input-container.component'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + AgentSearchInputContainerComponent + ], + imports: [ + SharedModule, + HelpViewerPopupModule + ], + exports: [ + AgentSearchInputContainerComponent + ], + providers: [] +}) +export class AgentSearchInputModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-list-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-list-data.service.ts new file mode 100644 index 000000000000..c8e64223115b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-list-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, tap } from 'rxjs/operators'; + +@Injectable() +export class AgentListDataService { + private url = 'getAgentList.pinpoint'; + + constructor(private http: HttpClient) {} + retrieve(): Observable { + return this.http.get(this.url).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + private handleError(error: HttpErrorResponse) { + return throwError(error.statusText || error); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-stat-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-stat-contents-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-stat-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-stat-contents-container.component.html new file mode 100644 index 000000000000..123e342d62d0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-stat-contents-container.component.html @@ -0,0 +1,2 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-stat-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-stat-contents-container.component.ts new file mode 100644 index 000000000000..804c01e5caa9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/agent-stat-contents-container.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { StoreHelperService } from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { AgentListDataService } from './agent-list-data.service'; + +@Component({ + selector: 'pp-agent-stat-contents-container', + templateUrl: './agent-stat-contents-container.component.html', + styleUrls: ['./agent-stat-contents-container.component.css'] +}) +export class AgentStatContentsContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + + constructor( + private storeHelperService: StoreHelperService, + private agentListDataService: AgentListDataService + ) {} + + ngOnInit() { + this.agentListDataService.retrieve().pipe( + takeUntil(this.unsubscribe) + ).subscribe((agentList: { [key: string]: IAgent[] }) => { + this.storeHelperService.dispatch(new Actions.UpdateAdminAgentList(agentList)); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/index.ts new file mode 100644 index 000000000000..3d8a7805aea6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/agent-stat-contents/index.ts @@ -0,0 +1,26 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { AgentStatContentsContainerComponent } from './agent-stat-contents-container.component'; +import { AgentListModule } from 'app/core/components/agent-list'; +import { AgentAdminChartModule } from 'app/core/components/agent-admin-chart'; + +import { AgentListDataService } from './agent-list-data.service'; + +@NgModule({ + declarations: [ + AgentStatContentsContainerComponent + ], + imports: [ + SharedModule, + AgentListModule, + AgentAdminChartModule + ], + exports: [ + AgentStatContentsContainerComponent + ], + providers: [ + AgentListDataService + ] +}) +export class AgentStatContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-create-and-update.component.css b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-create-and-update.component.css new file mode 100644 index 000000000000..99e611fac28e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-create-and-update.component.css @@ -0,0 +1,88 @@ +.l-wrapper { + top: 0px; + left: 0px; + width: 100%; + height: 100%; + z-index: 15; + display: flex; + padding: 0px 20px; + position: absolute; + align-items: center; + flex-direction: column; + justify-content: center; + background-color: rgba(226, 226, 226, 0.8); +} +.l-wrapper h1 { + margin-bottom: 6px; +} +form { + width: 100%; +} +.l-form-grid { + color: #333; + border: 1px solid #e5e8f0; + height: 100%; + display: grid; + position: relative; + font-size: 13px; + font-family: 'Open Sans', sans-serif; + font-weight: 600; + grid-template-columns: 50% 50%; + grid-template-rows: auto; +} +.l-form-grid > div { + position: relative; + margin-bottom: 10px; +} +.l-form-grid > div:nth-child(odd) { + margin-right: 10px; +} +.l-wrapper input { + width: 100%; + border: 1px solid #469ae4; + padding: 6px 11px; + font-size: 13px; + margin-bottom: 2px; + background-color: #FFF; +} +.l-wrapper .l-create { + width: 100%; + margin-top: 6px; +} +.l-wrapper .l-close { + top: 0px; + right: 0px; + position: absolute; +} +.l-wrapper .l-alert { + color: #FFF; + padding: 4px; + margin-bottom: 4px; + background-color: #000; +} +select { + width: 100%; + appearance: none; + -webkit-appearance: none; + border-radius: 0px; + background-color: #FFF; + height: 32px; + padding: 0 9px; + border:1px solid #4488cb; + font-size:13px; + color:#666; +} +textarea { + width: 100%; + height: 120px; + background-color: #FFF; +} +.fa-angle-down { + position: absolute; + top: 26px; + right: 8px; + font-size: 15px; +} +input[disabled] { + border: 1px solid #DDD !important; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-create-and-update.component.html b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-create-and-update.component.html new file mode 100644 index 000000000000..84d2711476c1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-create-and-update.component.html @@ -0,0 +1,56 @@ +
+ +

{{title}}

+
+
+
+
{{i18nLabel.CHECKER_LABEL}}
+ + +
+
+ {{i18nGuide.CHECKER_REQUIRED}} +
+
+
+
+
{{i18nLabel.USER_GROUP_LABEL}}
+ + +
+
+ {{i18nGuide.USER_GROUP_REQUIRED}} +
+
+
+
+
{{i18nLabel.THRESHOLD_LABEL}}
+ +
+
+ Must be greater than 0 +
+
+
+
+
{{i18nLabel.TYPE_LABEL}}
+ + +
+
+
{{i18nLabel.NOTES_LABEL}}
+ + +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-create-and-update.component.ts b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-create-and-update.component.ts new file mode 100644 index 000000000000..faa2881d0296 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-create-and-update.component.ts @@ -0,0 +1,118 @@ +import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; + +export class Alarm { + public applicationId: string; + public ruleId: string; + public smsSend: boolean; + public emailSend: boolean; + constructor( + public checkerName: string, + public userGroupId: string, + public threshold: number, + public type: string, + public notes?: string + ) { + this.setTypeInternalStatus(); + } + setTypeInternalStatus(): void { + this.smsSend = this.type === 'all' || this.type === 'sms' ? true : false; + this.emailSend = this.type === 'all' || this.type === 'email' ? true : false; + } +} + +@Component({ + selector: 'pp-alarm-rule-create-and-update', + templateUrl: './alarm-rule-create-and-update.component.html', + styleUrls: ['./alarm-rule-create-and-update.component.css'] +}) +export class AlarmRuleCreateAndUpdateComponent implements OnInit, OnChanges { + @Input() showCreate: boolean; + @Input() checkerList: string[]; + @Input() userGroupList: string[]; + @Input() i18nLabel: any; + @Input() i18nGuide: any; + @Input() editAlarm: Alarm = null; + @Output() outUpdateAlarm: EventEmitter = new EventEmitter(); + @Output() outCreateAlarm: EventEmitter = new EventEmitter(); + @Output() outClose: EventEmitter = new EventEmitter(); + newAlarmModel = new Alarm('', '', 1, 'all', ''); + alarmForm: FormGroup; + title = 'Alarm'; + + constructor() {} + ngOnInit() { + this.alarmForm = new FormGroup({ + 'checkerName': new FormControl(this.newAlarmModel.checkerName, [ + Validators.required + ]), + 'userGroupId': new FormControl(this.newAlarmModel.userGroupId, [ + Validators.required + ]), + 'threshold': new FormControl(this.newAlarmModel.threshold, [ + Validators.required, + Validators.min(1) + ]), + 'type': new FormControl(this.newAlarmModel.type, []), + 'notes': new FormControl(this.newAlarmModel.notes, []), + 'applicationId': new FormControl(this.newAlarmModel.applicationId, []), + 'ruleId': new FormControl(this.newAlarmModel.ruleId, []) + }); + } + ngOnChanges(changes: SimpleChanges) { + if (changes['showCreate'] && changes['showCreate'].currentValue === true) { + this.setValue('', '', 1, 'all', ''); + } + if (changes['editAlarm'] && changes['editAlarm'].currentValue) { + this.setValue( + this.editAlarm.checkerName, + this.editAlarm.userGroupId, + this.editAlarm.threshold, + this.editAlarm.type, + this.editAlarm.notes + ); + } + } + private setValue(checkerName: string, userGroupId: string, threshold: number, type: string, notes: string): void { + this.alarmForm.get('checkerName').setValue(checkerName); + this.alarmForm.get('userGroupId').setValue(userGroupId); + this.alarmForm.get('threshold').setValue(threshold); + this.alarmForm.get('type').setValue(type); + this.alarmForm.get('notes').setValue(notes); + } + onCreateOrUpdate() { + const alarm = new Alarm( + this.alarmForm.get('checkerName').value, + this.alarmForm.get('userGroupId').value, + this.alarmForm.get('threshold').value, + this.alarmForm.get('type').value, + this.alarmForm.get('notes').value + ); + if (this.editAlarm) { + this.outUpdateAlarm.emit(alarm); + } else { + this.outCreateAlarm.emit(alarm); + } + this.onClose(); + } + onClose() { + this.editAlarm = null; + this.outClose.emit(); + this.alarmForm.reset(); + } + get checkerName() { + return this.alarmForm.get('checkerName'); + } + get userGroupId() { + return this.alarmForm.get('userGroupId'); + } + get threshold() { + return this.alarmForm.get('threshold'); + } + get type() { + return this.alarmForm.get('type'); + } + get notes() { + return this.alarmForm.get('notes'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-data.service.ts new file mode 100644 index 000000000000..dec1b234a53f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-data.service.ts @@ -0,0 +1,93 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, tap, shareReplay } from 'rxjs/operators'; + +export interface IAlarmRule { + applicationId: string; + checkerName: string; + emailSend: boolean; + notes: string; + ruleId: string; + serviceType: string; + smsSend: boolean; + threshold: number; + userGroupId: string; +} +export interface IAlarmRuleCreated { + number: string; +} +export interface IAlarmRuleResponse { + result: string; +} + +@Injectable() +export class AlarmRuleDataService { + private alarmRuleURL = 'application/alarmRule.pinpoint'; + private checkerListURL = 'application/alarmRule/checker.pinpoint'; + private cache$: Observable; + + constructor(private http: HttpClient) {} + getCheckerList(): Observable { + if (!this.cache$) { + const httpRequest$ = this.http.get(this.checkerListURL); + this.cache$ = httpRequest$.pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError), + shareReplay(1) + ); + } + return this.cache$; + } + retrieve(applicationId: string): Observable { + return this.http.get(this.alarmRuleURL, this.makeRequestOptionsArgs(applicationId)).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + create(params: IAlarmRule): Observable { + return this.http.post(this.alarmRuleURL, params).pipe( + tap(this.checkError), + catchError(this.handleError) + ); + } + update(params: IAlarmRule): Observable { + return this.http.put(this.alarmRuleURL, params).pipe( + tap(this.checkError), + catchError(this.handleError) + ); + } + remove(ruleId: string): Observable { + return this.http.request('delete', this.alarmRuleURL, { + body: { ruleId } + }).pipe( + tap(this.checkError), + catchError(this.handleError) + ); + } + private checkError(data: any) { + if (data.errorCode) { + throw data.errorMessage; + } else if (data.result !== 'SUCCESS') { + throw data; + } + } + private handleError(error: HttpErrorResponse) { + return throwError(error.statusText || error); + } + private makeRequestOptionsArgs(applicationId: string): object { + return applicationId ? { + params: { + applicationId + } + } : {}; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list-container.component.css new file mode 100644 index 000000000000..b27b7b53c39e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list-container.component.css @@ -0,0 +1,80 @@ +:host { + position: relative; +} +.l-wrapper { + position: relative; +} +.l-alarm-rules-table th { + color:#333; + font-size: 13px; +} +.l-alarm-rules-table td { + padding:10px 15px; + height:auto; + font-size: 13px; +} +.l-alarm-rules-table th .l-table-title { + height: 47px; + justify-content: space-between; + display: flex; + align-items: center; +} +.l-alarm-rules-tr-wrap { + height: 440px; + overflow-y: scroll; + position: relative; +} +.l-alarm-rules-th { + display: flex; + height: 30px; + align-items: center; + padding: 0 15px; + position:relative; + color: #777879; + font-weight: 600; + border-bottom: 1px solid #e6e8ec; + background: #fff; + margin: 0 -21px; +} +.l-alarm-rules-th div { + float :left; +} +.l-alarm-rules-th div:nth-child(1) { + width: 50%; +} +.l-alarm-rules-th div:nth-child(2) { + width: 16.66666667%; +} +.l-alarm-rules-th div:nth-child(3) { + width: 16.66666667%; +} +.l-alarm-rules-th div:nth-child(4) { + width: 25%; + margin-right: 22px; +} +.l-message { + width: 100%; + height: 100%; + z-index: 15; + display: flex; + position: absolute; + align-items: center; + justify-content: center; + background-color: rgba(226, 226, 226, 0.8); +} +.l-message span { + color: #ff8c00; + text-align: center; +} +.l-message button { + top: 0px; + right: 0px; + position: absolute; +} +.l-not-selected { + padding-top: 30%; + text-align: center; +} +.l-threshold { + text-align: center; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list-container.component.html new file mode 100644 index 000000000000..65cfdf65cbee --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list-container.component.html @@ -0,0 +1,54 @@ +
+ + + + + + + + + + + + + + +
+
+ Alarm + +
+
+
Rule Name
+
User Group
+
Threshold
+
Type
+
+
+
+ +
Select Application
+
+
+
+ + {{message}} +
+ + + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list-container.component.ts new file mode 100644 index 000000000000..00b93b61ab63 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list-container.component.ts @@ -0,0 +1,218 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject, combineLatest } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { TranslateReplaceService } from 'app/shared/services'; +import { UserGroupDataService, IUserGroup } from 'app/core/components/user-group/user-group-data.service'; +import { ApplicationListInteractionForConfigurationService } from 'app/core/components/application-list/application-list-interaction-for-configuration.service'; +import { Alarm } from './alarm-rule-create-and-update.component'; +import { AlarmRuleDataService, IAlarmRule, IAlarmRuleCreated, IAlarmRuleResponse } from './alarm-rule-data.service'; + +@Component({ + selector: 'pp-alarm-rule-list-container', + templateUrl: './alarm-rule-list-container.component.html', + styleUrls: ['./alarm-rule-list-container.component.css'] +}) +export class AlarmRuleListContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private currentApplication: IApplication = null; + private editAlarmIndex: number; + useDisable = false; + showLoading = false; + showCreate = false; + message = ''; + checkerList: string[]; + alarmRuleList: IAlarmRule[]; + userGroupList: string[]; + + i18nLabel = { + CHECKER_LABEL: '', + USER_GROUP_LABEL: '', + THRESHOLD_LABEL: '', + TYPE_LABEL: '', + NOTES_LABEL: '', + }; + i18nGuide = { + CHECKER_REQUIRED: '', + USER_GROUP_REQUIRED: '', + THRESHOLD_REQUIRED: '', + TYPE_REQUIRED: '' + }; + editAlarm: any; + + constructor( + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + private alarmRuleDataService: AlarmRuleDataService, + private userGroupDataSerivce: UserGroupDataService, + private applicationListInteractionForConfigurationService: ApplicationListInteractionForConfigurationService + ) { } + ngOnInit() { + this.alarmRuleDataService.getCheckerList().pipe( + takeUntil(this.unsubscribe) + ).subscribe((checkerList: string[]) => { + this.checkerList = checkerList; + }); + this.userGroupDataSerivce.retrieve().pipe( + takeUntil(this.unsubscribe) + ).subscribe((userGroupList: IUserGroup[]) => { + this.userGroupList = userGroupList.map((userGroup: IUserGroup) => { + return userGroup.id; + }); + }); + this.applicationListInteractionForConfigurationService.onSelectApplication$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((selectedApplication: IApplication) => { + this.currentApplication = selectedApplication; + this.onCloseCreateAlarmPopup(); + this.getAlarmData(); + }); + this.getI18NText(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private getI18NText(): void { + combineLatest( + this.translateService.get('COMMON.REQUIRED_SELECT'), + this.translateService.get('CONFIGURATION.COMMON.CHECKER'), + this.translateService.get('CONFIGURATION.COMMON.USER_GROUP'), + this.translateService.get('CONFIGURATION.COMMON.THRESHOLD'), + this.translateService.get('CONFIGURATION.COMMON.TYPE'), + this.translateService.get('CONFIGURATION.COMMON.NOTES'), + ).subscribe((i18n: string[]) => { + this.i18nGuide.CHECKER_REQUIRED = this.translateReplaceService.replace(i18n[0], i18n[1]); + this.i18nGuide.USER_GROUP_REQUIRED = this.translateReplaceService.replace(i18n[0], i18n[2]); + this.i18nGuide.THRESHOLD_REQUIRED = this.translateReplaceService.replace(i18n[0], i18n[3]); + this.i18nGuide.TYPE_REQUIRED = this.translateReplaceService.replace(i18n[0], i18n[4]); + + this.i18nLabel.CHECKER_LABEL = i18n[1]; + this.i18nLabel.USER_GROUP_LABEL = i18n[2]; + this.i18nLabel.THRESHOLD_LABEL = i18n[3]; + this.i18nLabel.TYPE_LABEL = i18n[4]; + this.i18nLabel.NOTES_LABEL = i18n[5]; + }); + } + private getAlarmData(): void { + this.showProcessing(); + this.alarmRuleDataService.retrieve(this.currentApplication.getApplicationName()).subscribe((alarmRuleList: IAlarmRule[]) => { + this.alarmRuleList = alarmRuleList; + this.hideProcessing(); + }); + } + private getAlarmIndexByRuleId(ruleId: string): number { + let index = -1; + for (let i = 0 ; i < this.alarmRuleList.length ; i++) { + if (this.alarmRuleList[i].ruleId === ruleId) { + index = i; + break; + } + } + return index; + } + private getTypeStr(smsSend: boolean, emailSend: boolean): string { + if (smsSend && emailSend) { + return 'all'; + } else { + if (smsSend) { + return 'sms'; + } + if (emailSend) { + return 'email'; + } + return 'none'; + } + } + onCreateAlarm(alarm: Alarm): void { + this.showProcessing(); + this.alarmRuleDataService.create({ + applicationId: this.currentApplication.getApplicationName(), + serviceType: this.currentApplication.getServiceType(), + userGroupId: alarm.userGroupId, + checkerName: alarm.checkerName, + threshold: alarm.threshold, + smsSend: alarm.smsSend, + emailSend: alarm.emailSend, + notes: alarm.notes + } as IAlarmRule).subscribe((response: IAlarmRuleCreated) => { + this.getAlarmData(); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + } + onUpdateAlarm(alarm: Alarm): void { + const editAlarm = this.alarmRuleList[this.editAlarmIndex]; + this.alarmRuleDataService.update({ + applicationId: editAlarm.applicationId, + ruleId: editAlarm.ruleId, + serviceType: editAlarm.serviceType, + checkerName: alarm.checkerName, + userGroupId: alarm.userGroupId, + threshold: alarm.threshold, + smsSend: alarm.smsSend, + emailSend: alarm.emailSend, + notes: alarm.notes + } as IAlarmRule).subscribe((response: IAlarmRuleResponse) => { + this.getAlarmData(); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + } + onShowCreateAlarmPopup(): void { + if (this.isApplicationSelected() === false) { + return; + } + this.showCreate = true; + } + onCloseCreateAlarmPopup(): void { + this.showCreate = false; + } + onCloseMessage(): void { + this.message = ''; + } + onRemoveAlarm(ruleId: string): void { + this.showProcessing(); + this.alarmRuleDataService.remove(ruleId).subscribe((response: IAlarmRuleResponse) => { + this.getAlarmData(); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + } + onEditAlarm(ruleId: string): void { + this.editAlarmIndex = this.getAlarmIndexByRuleId(ruleId); + const editAlarm = this.alarmRuleList[this.editAlarmIndex]; + this.editAlarm = new Alarm( + editAlarm.checkerName, + editAlarm.userGroupId, + editAlarm.threshold, + this.getTypeStr(editAlarm.smsSend, editAlarm.emailSend), + editAlarm.notes + ); + this.onShowCreateAlarmPopup(); + } + hasMessage(): boolean { + return this.message !== ''; + } + isApplicationSelected(): boolean { + return this.currentApplication !== null; + } + getAddButtonClass(): object { + return { + 'btn-blue': this.isApplicationSelected(), + 'btn-gray': !this.isApplicationSelected() + }; + } + private showProcessing(): void { + this.useDisable = true; + this.showLoading = true; + } + private hideProcessing(): void { + this.useDisable = false; + this.showLoading = false; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list.component.css b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list.component.css new file mode 100644 index 000000000000..67150bd4a9b6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list.component.css @@ -0,0 +1,52 @@ +.l-alarm-rules-tr { + display: flex; + height: 30px; + align-items: center; + padding: 0 15px; + position: relative; +} +.l-alarm-rules-tr div { + float: left; +} +.l-alarm-rules-tr div:nth-child(1) { + width: 41.66666667%; +} +.l-alarm-rules-tr div:nth-child(2) { + width: 16.66666667%; +} +.l-alarm-rules-tr div:nth-child(3) { + width: 16.66666667%; +} +.l-alarm-rules-tr div:nth-child(4) { + width: 16.66666667%; +} +.l-alarm-rules-tr div:nth-child(5) { + width: 8.33333333% +} +.l-alarm-rules-btn-group { + display: flex; + color: #b3b6bf; + font-size: 13px; + align-items: center; + justify-content: flex-end; +} +.l-alarm-rules-btn-group button { + margin-left: 11px; +} +.l-alarm-rules-btn-group button:first-child { + margin-left: 0; +} +.l-threshold { + text-align: center; +} +.fa-trash-alt { + color: #b3b6bf; + font-size: 14px; +} +.fa-edit { + margin-left: 10px; +} +.fa-check { + color: #F00; + margin-left: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list.component.html b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list.component.html new file mode 100644 index 000000000000..f738115e03de --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list.component.html @@ -0,0 +1,12 @@ +
+
{{alarm.checkerName}}
+
{{alarm.userGroupId}}
+
{{alarm.threshold}}
+
{{getNotificationType(alarm.emailSend, alarm.smsSend)}}
+
+ + + + +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list.component.ts b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list.component.ts new file mode 100644 index 000000000000..05014cb11d11 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/alarm-rule-list.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-alarm-rule-list', + templateUrl: './alarm-rule-list.component.html', + styleUrls: ['./alarm-rule-list.component.css'] +}) +export class AlarmRuleListComponent implements OnInit { + @Input() alarmRuleList: any; + @Output() outRemove: EventEmitter = new EventEmitter(); + @Output() outEdit: EventEmitter = new EventEmitter(); + private removeConformId = ''; + constructor() { } + ngOnInit() {} + getNotificationType(emailSend: boolean, smsSend: boolean): string { + const returnStr = []; + if (emailSend === false && smsSend === false) { + return 'None'; + } else { + if (emailSend) { + returnStr.push('Email'); + } + if (smsSend) { + returnStr.push('SMS'); + } + return returnStr.join(','); + } + } + onRemove(ruleId: string): void { + this.removeConformId = ruleId; + } + onEdit(ruleId: string): void { + this.outEdit.emit(ruleId); + } + onCancelRemove(): void { + this.removeConformId = ''; + } + onConfirmRemove(): void { + this.outRemove.emit(this.removeConformId); + this.removeConformId = ''; + } + isRemoveTarget(ruleId: string): boolean { + return this.removeConformId === ruleId; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/index.ts b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/index.ts new file mode 100644 index 000000000000..b348dab7b302 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/alarm-rule-list/index.ts @@ -0,0 +1,29 @@ + +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { SharedModule } from 'app/shared'; +import { AlarmRuleListComponent } from './alarm-rule-list.component'; +import { AlarmRuleListContainerComponent } from './alarm-rule-list-container.component'; +import { AlarmRuleCreateAndUpdateComponent } from './alarm-rule-create-and-update.component'; +import { AlarmRuleDataService } from './alarm-rule-data.service'; + +@NgModule({ + declarations: [ + AlarmRuleListComponent, + AlarmRuleListContainerComponent, + AlarmRuleCreateAndUpdateComponent + ], + imports: [ + FormsModule, + ReactiveFormsModule, + SharedModule + ], + exports: [ + AlarmRuleListContainerComponent, + AlarmRuleCreateAndUpdateComponent + ], + providers: [ + AlarmRuleDataService + ] +}) +export class AlarmRuleListModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/angular-split/angular-split.ts b/web/src/main/webapp/v2/src/app/core/components/angular-split/angular-split.ts new file mode 100644 index 000000000000..8f5d8143a9bb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/angular-split/angular-split.ts @@ -0,0 +1,4 @@ +// Public classes. +export { AngularSplitModule } from './modules/angularSplit.module'; +export { SplitComponent } from './components/split.component'; +export { SplitAreaDirective } from './components/splitArea.directive'; diff --git a/web/src/main/webapp/v2/src/app/core/components/angular-split/components/split.component.ts b/web/src/main/webapp/v2/src/app/core/components/angular-split/components/split.component.ts new file mode 100644 index 000000000000..1b9610542ff1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/angular-split/components/split.component.ts @@ -0,0 +1,594 @@ +import { Component, ChangeDetectorRef, Input, Output, HostBinding, ChangeDetectionStrategy, EventEmitter, Renderer2, OnDestroy, ElementRef, AfterViewInit, NgZone } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; + +import { IArea } from './../interface/IArea'; +import { IPoint } from './../interface/IPoint'; +import { SplitAreaDirective } from './splitArea.directive'; + +@Component({ + selector: 'split', + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [` + :host { + display: flex; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: stretch; + overflow: hidden; + /* + Important to keep following rules even if overrided later by 'HostBinding' + because if [width] & [height] not provided, when build() is executed, + 'HostBinding' hasn't been applied yet so code: + this.elRef.nativeElement["offsetHeight"] gives wrong value! + */ + width: 100%; + height: 100%; + } + split-gutter { + flex-grow: 0; + flex-shrink: 0; + background-position: center center; + background-repeat: no-repeat; + } + `], + template: ` + + + + `, +}) +export class SplitComponent implements AfterViewInit, OnDestroy { + + private _direction: 'horizontal' | 'vertical' = 'horizontal'; + + @Input() set direction(v: 'horizontal' | 'vertical') { + v = (v === 'vertical') ? 'vertical' : 'horizontal'; + this._direction = v; + + [...this.displayedAreas, ...this.hidedAreas].forEach(area => { + area.comp.setStyleVisibleAndDir(area.comp.visible, this.isDragging, this.direction); + }); + + this.build(false, false); + } + + get direction(): 'horizontal' | 'vertical' { + return this._direction; + } + + //// + private _useTransition: boolean = false; + + @Input() set useTransition(v: boolean) { + v = (typeof(v) === 'boolean') ? v : (v === 'false' ? false : true); + this._useTransition = v; + } + + get useTransition(): boolean { + return this._useTransition; + } + + //// + private _disabled: boolean = false; + + @Input() set disabled(v: boolean) { + v = (typeof(v) === 'boolean') ? v : (v === 'false' ? false : true); + this._disabled = v; + + // Force repaint if modified from TS class (instead of the template) + this.cdRef.markForCheck(); + } + + get disabled(): boolean { + return this._disabled; + } + + //// + private _width: number | null = null; + + @Input() set width(v: number | null) { + v = Number(v); + this._width = (!isNaN(v) && v > 0) ? v : null; + + this.build(false, false); + } + + get width(): number | null { + return this._width; + } + + //// + private _height: number | null = null; + + @Input() set height(v: number | null) { + v = Number(v); + this._height = (!isNaN(v) && v > 0) ? v : null; + + this.build(false, false); + } + + get height(): number | null { + return this._height; + } + + //// + private _gutterSize: number = 11; + + @Input() set gutterSize(v: number) { + v = Number(v); + this._gutterSize = (!isNaN(v) && v > 0) ? v : 11; + + this.build(false, false); + } + + get gutterSize(): number { + return this._gutterSize; + } + + //// + private _gutterColor: string = ''; + + @Input() set gutterColor(v: string) { + this._gutterColor = (typeof v === 'string' && v !== '') ? v : ''; + + // Force repaint if modified from TS class (instead of the template) + this.cdRef.markForCheck(); + } + + get gutterColor(): string { + return this._gutterColor; + } + + //// + private _gutterImageH: string = ''; + + @Input() set gutterImageH(v: string) { + this._gutterImageH = (typeof v === 'string' && v !== '') ? v : ''; + + // Force repaint if modified from TS class (instead of the template) + this.cdRef.markForCheck(); + } + + get gutterImageH(): string { + return this._gutterImageH; + } + + //// + private _gutterImageV: string = ''; + + @Input() set gutterImageV(v: string) { + this._gutterImageV = (typeof v === 'string' && v !== '') ? v : ''; + + // Force repaint if modified from TS class (instead of the template) + this.cdRef.markForCheck(); + } + + get gutterImageV(): string { + return this._gutterImageV; + } + + //// + private _dir: 'ltr' | 'rtl' = 'ltr'; + + @Input() set dir(v: 'ltr' | 'rtl') { + v = (v === 'rtl') ? 'rtl' : 'ltr'; + this._dir = v; + } + + get dir(): 'ltr' | 'rtl' { + return this._dir; + } + + //// + @Output() dragStart = new EventEmitter<{gutterNum: number, sizes: Array}>(false); + @Output() dragProgress = new EventEmitter<{gutterNum: number, sizes: Array}>(false); + @Output() dragEnd = new EventEmitter<{gutterNum: number, sizes: Array}>(false); + @Output() gutterClick = new EventEmitter<{gutterNum: number, sizes: Array}>(false); + + private transitionEndInternal = new Subject>(); + @Output() transitionEnd = (>> this.transitionEndInternal.asObservable()).pipe( + debounceTime(20) + ); + + @HostBinding('style.flex-direction') get cssFlexdirection() { + return (this.direction === 'horizontal') ? 'row' : 'column'; + } + + @HostBinding('style.width') get cssWidth() { + return this.width ? `${ this.width }px` : '100%'; + } + + @HostBinding('style.height') get cssHeight() { + return this.height ? `${ this.height }px` : '100%'; + } + + @HostBinding('style.min-width') get cssMinwidth() { + return (this.direction === 'horizontal') ? `${ this.getNbGutters() * this.gutterSize }px` : null; + } + + @HostBinding('style.min-height') get cssMinheight() { + return (this.direction === 'vertical') ? `${ this.getNbGutters() * this.gutterSize }px` : null; + } + + public isViewInitialized: boolean = false; + private isDragging: boolean = false; + private draggingWithoutMove: boolean = false; + private currentGutterNum: number = 0; + + public readonly displayedAreas: Array = []; + private readonly hidedAreas: Array = []; + + private readonly dragListeners: Array = []; + private readonly dragStartValues = { + sizePixelContainer: 0, + sizePixelA: 0, + sizePixelB: 0, + sizePercentA: 0, + sizePercentB: 0, + }; + + constructor(private ngZone: NgZone, + private elRef: ElementRef, + private cdRef: ChangeDetectorRef, + private renderer: Renderer2) {} + + public ngAfterViewInit() { + this.isViewInitialized = true; + } + + private getNbGutters(): number { + return this.displayedAreas.length - 1; + } + + public addArea(comp: SplitAreaDirective): void { + const newArea: IArea = { + comp, + order: 0, + size: 0, + }; + + if (comp.visible === true) { + this.displayedAreas.push(newArea); + } else { + this.hidedAreas.push(newArea); + } + + comp.setStyleVisibleAndDir(comp.visible, this.isDragging, this.direction); + + this.build(true, true); + } + + public removeArea(comp: SplitAreaDirective): void { + if (this.displayedAreas.some(a => a.comp === comp)) { + const area = this.displayedAreas.find(a => a.comp === comp) + this.displayedAreas.splice(this.displayedAreas.indexOf(area), 1); + + this.build(true, true); + } else if(this.hidedAreas.some(a => a.comp === comp)) { + const area = this.hidedAreas.find(a => a.comp === comp) + this.hidedAreas.splice(this.hidedAreas.indexOf(area), 1); + } + } + + public updateArea(comp: SplitAreaDirective, resetOrders: boolean, resetSizes: boolean): void { + // Only refresh if area is displayed (No need to check inside 'hidedAreas') + const item = this.displayedAreas.find(a => a.comp === comp); + + if (item) { + this.build(resetOrders, resetSizes); + } + } + + public showArea(comp: SplitAreaDirective): void { + const area = this.hidedAreas.find(a => a.comp === comp); + + if (area) { + comp.setStyleVisibleAndDir(comp.visible, this.isDragging, this.direction); + + const areas = this.hidedAreas.splice(this.hidedAreas.indexOf(area), 1); + this.displayedAreas.push(...areas); + + this.build(true, true); + } + } + + public hideArea(comp: SplitAreaDirective): void { + const area = this.displayedAreas.find(a => a.comp === comp); + + if (area) { + comp.setStyleVisibleAndDir(comp.visible, this.isDragging, this.direction); + + const areas = this.displayedAreas.splice(this.displayedAreas.indexOf(area), 1); + areas.forEach(area => { + area.order = 0; + area.size = 0; + }) + this.hidedAreas.push(...areas); + + this.build(true, true); + } + } + + private build(resetOrders: boolean, resetSizes: boolean): void { + this.stopDragging(); + + // ¤ AREAS ORDER + + if (resetOrders === true) { + + // If user provided 'order' for each area, use it to sort them. + if (this.displayedAreas.every(a => a.comp.order !== null)) { + this.displayedAreas.sort((a, b) => ( a.comp.order) - ( b.comp.order)); + } + + // Then set real order with multiples of 2, numbers between will be used by gutters. + this.displayedAreas.forEach((area, i) => { + area.order = i * 2; + area.comp.setStyleOrder(area.order); + }); + + } + + // ¤ AREAS SIZE PERCENT + + if (resetSizes === true) { + + const totalUserSize = this.displayedAreas.reduce((total: number, s: IArea) => s.comp.size ? total + s.comp.size : total, 0); + + // If user provided 'size' for each area and total == 1, use it. + if (this.displayedAreas.every(a => a.comp.size !== null) && totalUserSize > .999 && totalUserSize < 1.001 ) { + + this.displayedAreas.forEach(area => { + area.size = area.comp.size; + }); + } else { + const size = 1 / this.displayedAreas.length; + + this.displayedAreas.forEach(area => { + area.size = size; + }); + } + } + + // + // If some real area sizes are less than gutterSize, + // set them to zero and dispatch size to others. + let percentToDispatch = 0; + + // Get container pixel size + let containerSizePixel = this.getNbGutters() * this.gutterSize; + if (this.direction === 'horizontal') { + containerSizePixel = this.width ? this.width : this.elRef.nativeElement['offsetWidth']; + } else { + containerSizePixel = this.height ? this.height : this.elRef.nativeElement['offsetHeight']; + } + + this.displayedAreas.forEach(area => { + if (area.size * containerSizePixel < this.gutterSize) { + percentToDispatch += area.size; + area.size = 0; + } + }); + + if (percentToDispatch > 0 && this.displayedAreas.length > 0) { + const nbAreasNotZero = this.displayedAreas.filter(a => a.size !== 0).length; + + if (nbAreasNotZero > 0) { + const percentToAdd = percentToDispatch / nbAreasNotZero; + + this.displayedAreas.filter(a => a.size !== 0).forEach(area => { + area.size += percentToAdd; + }); + } else { + this.displayedAreas[this.displayedAreas.length - 1].size = 1; + } + } + + + this.refreshStyleSizes(); + this.cdRef.markForCheck(); + } + + private refreshStyleSizes(): void { + const sumGutterSize = this.getNbGutters() * this.gutterSize; + + this.displayedAreas.forEach(area => { + area.comp.setStyleFlexbasis(`calc( ${ area.size * 100 }% - ${ area.size * sumGutterSize }px )`, this.isDragging); + }); + } + + public startDragging(startEvent: MouseEvent | TouchEvent, gutterOrder: number, gutterNum: number): void { + startEvent.preventDefault(); + + // Place code here to allow '(gutterClick)' event even if '[disabled]="true"'. + this.currentGutterNum = gutterNum; + this.draggingWithoutMove = true; + this.ngZone.runOutsideAngular(() => { + this.dragListeners.push( this.renderer.listen('document', 'mouseup', (e: MouseEvent) => this.stopDragging()) ); + this.dragListeners.push( this.renderer.listen('document', 'touchend', (e: TouchEvent) => this.stopDragging()) ); + this.dragListeners.push( this.renderer.listen('document', 'touchcancel', (e: TouchEvent) => this.stopDragging()) ); + }); + + if(this.disabled) { + return; + } + + const areaA = this.displayedAreas.find(a => a.order === gutterOrder - 1); + const areaB = this.displayedAreas.find(a => a.order === gutterOrder + 1); + + if (!areaA || !areaB) { + return; + } + + const prop = (this.direction === 'horizontal') ? 'offsetWidth' : 'offsetHeight'; + this.dragStartValues.sizePixelContainer = this.elRef.nativeElement[prop]; + this.dragStartValues.sizePixelA = areaA.comp.getSizePixel(prop); + this.dragStartValues.sizePixelB = areaB.comp.getSizePixel(prop); + this.dragStartValues.sizePercentA = areaA.size; + this.dragStartValues.sizePercentB = areaB.size; + + let start: IPoint; + if (startEvent instanceof MouseEvent) { + start = { + x: startEvent.screenX, + y: startEvent.screenY, + }; + } else if (startEvent instanceof TouchEvent) { + start = { + x: startEvent.touches[0].screenX, + y: startEvent.touches[0].screenY, + }; + } else { + return; + } + + this.ngZone.runOutsideAngular(() => { + this.dragListeners.push( this.renderer.listen('document', 'mousemove', (e: MouseEvent) => this.dragEvent(e, start, areaA, areaB)) ); + this.dragListeners.push( this.renderer.listen('document', 'touchmove', (e: TouchEvent) => this.dragEvent(e, start, areaA, areaB)) ); + }); + + areaA.comp.lockEvents(); + areaB.comp.lockEvents(); + + this.isDragging = true; + + this.notify('start'); + } + + private dragEvent(event: MouseEvent | TouchEvent, start: IPoint, areaA: IArea, areaB: IArea): void { + if (!this.isDragging) { + return; + } + + let end: IPoint; + if (event instanceof MouseEvent) { + end = { + x: event.screenX, + y: event.screenY, + }; + } else if (event instanceof TouchEvent) { + end = { + x: event.touches[0].screenX, + y: event.touches[0].screenY, + }; + } else { + return; + } + + this.draggingWithoutMove = false; + this.drag(start, end, areaA, areaB); + } + + private drag(start: IPoint, end: IPoint, areaA: IArea, areaB: IArea): void { + + // ¤ AREAS SIZE PIXEL + const devicePixelRatio = window.devicePixelRatio || 1; + let offsetPixel = (this.direction === 'horizontal') ? (start.x - end.x) : (start.y - end.y); + offsetPixel = offsetPixel / devicePixelRatio; + + if(this.dir === 'rtl') { + offsetPixel = -offsetPixel; + } + + let newSizePixelA = this.dragStartValues.sizePixelA - offsetPixel; + let newSizePixelB = this.dragStartValues.sizePixelB + offsetPixel; + + if (newSizePixelA < this.gutterSize && newSizePixelB < this.gutterSize) { + // WTF.. get out of here! + return; + } else if (newSizePixelA < this.gutterSize) { + newSizePixelB += newSizePixelA; + newSizePixelA = 0; + } else if (newSizePixelB < this.gutterSize) { + newSizePixelA += newSizePixelB; + newSizePixelB = 0; + } + + // ¤ AREAS SIZE PERCENT + if (newSizePixelA === 0) { + areaB.size += areaA.size; + areaA.size = 0; + } else if (newSizePixelB === 0) { + areaA.size += areaB.size; + areaB.size = 0; + } else { + // NEW_PERCENT = START_PERCENT / START_PIXEL * NEW_PIXEL; + if (this.dragStartValues.sizePercentA === 0) { + areaB.size = this.dragStartValues.sizePercentB / this.dragStartValues.sizePixelB * newSizePixelB; + areaA.size = this.dragStartValues.sizePercentB - areaB.size; + } else if (this.dragStartValues.sizePercentB === 0) { + areaA.size = this.dragStartValues.sizePercentA / this.dragStartValues.sizePixelA * newSizePixelA; + areaB.size = this.dragStartValues.sizePercentA - areaA.size; + } else { + areaA.size = this.dragStartValues.sizePercentA / this.dragStartValues.sizePixelA * newSizePixelA; + areaB.size = (this.dragStartValues.sizePercentA + this.dragStartValues.sizePercentB) - areaA.size; + } + } + + this.refreshStyleSizes(); + this.notify('progress'); + } + + private stopDragging(): void { + if (this.isDragging === false && this.draggingWithoutMove === false) { + return; + } + + this.displayedAreas.forEach(area => { + area.comp.unlockEvents(); + }); + + while (this.dragListeners.length > 0) { + const fct = this.dragListeners.pop(); + if (fct) { + fct(); + } + } + + if (this.draggingWithoutMove === true) { + this.notify('click'); + } else { + this.notify('end'); + } + + this.isDragging = false; + this.draggingWithoutMove = false; + } + + + public notify(type: 'start' | 'progress' | 'end' | 'click' | 'transitionEnd'): void { + const areasSize: Array = this.displayedAreas.map(a => a.size * 100); + + switch (type) { + case 'start': + return this.dragStart.emit({gutterNum: this.currentGutterNum, sizes: areasSize}); + + case 'progress': + return this.dragProgress.emit({gutterNum: this.currentGutterNum, sizes: areasSize}); + + case 'end': + return this.dragEnd.emit({gutterNum: this.currentGutterNum, sizes: areasSize}); + + case 'click': + return this.gutterClick.emit({gutterNum: this.currentGutterNum, sizes: areasSize}); + + case 'transitionEnd': + return this.transitionEndInternal.next(areasSize); + } + } + + public ngOnDestroy(): void { + this.stopDragging(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/angular-split/components/splitArea.directive.ts b/web/src/main/webapp/v2/src/app/core/components/angular-split/components/splitArea.directive.ts new file mode 100644 index 000000000000..ad3b9ed8408b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/angular-split/components/splitArea.directive.ts @@ -0,0 +1,176 @@ +import { Directive, Input, ElementRef, Renderer2, OnInit, OnDestroy, NgZone } from '@angular/core'; + +import { SplitComponent } from './split.component'; + +@Directive({ + selector: 'split-area' +}) +export class SplitAreaDirective implements OnInit, OnDestroy { + + private _order: number | null = null; + + @Input() set order(v: number | null) { + v = Number(v); + this._order = !isNaN(v) ? v : null; + + this.split.updateArea(this, true, false); + } + + get order(): number | null { + return this._order; + } + + //// + private _size: number | null = null; + + @Input() set size(v: number | null) { + v = Number(v); + this._size = (!isNaN(v) && v >= 0 && v <= 100) ? (v/100) : null; + + this.split.updateArea(this, false, true); + } + + get size(): number | null { + return this._size; + } + + //// + private _minSize: number = 0; + + @Input() set minSize(v: number) { + v = Number(v); + this._minSize = (!isNaN(v) && v > 0 && v < 100) ? v/100 : 0; + + this.split.updateArea(this, false, true); + } + + get minSize(): number { + return this._minSize; + } + + //// + private _visible: boolean = true; + + @Input() set visible(v: boolean) { + v = (typeof(v) === 'boolean') ? v : (v === 'false' ? false : true); + this._visible = v; + + if(this.visible) { + this.split.showArea(this); + } + else { + this.split.hideArea(this); + } + } + + get visible(): boolean { + return this._visible; + } + + //// + private transitionListener: Function; + private readonly lockListeners: Array = []; + + constructor(private ngZone: NgZone, + private elRef: ElementRef, + private renderer: Renderer2, + private split: SplitComponent) {} + + public ngOnInit(): void { + this.split.addArea(this); + + this.renderer.setStyle(this.elRef.nativeElement, 'flex-grow', '0'); + this.renderer.setStyle(this.elRef.nativeElement, 'flex-shrink', '0'); + + this.ngZone.runOutsideAngular(() => { + this.transitionListener = this.renderer.listen(this.elRef.nativeElement, 'transitionend', (e: TransitionEvent) => this.onTransitionEnd(e)); + }); + } + + public getSizePixel(prop: 'offsetWidth' | 'offsetHeight'): number { + return this.elRef.nativeElement[prop]; + } + + public setStyleVisibleAndDir(isVisible: boolean, isDragging: boolean, direction: 'horizontal' | 'vertical'): void { + if(isVisible === false) { + this.setStyleFlexbasis('0', isDragging); + this.renderer.setStyle(this.elRef.nativeElement, 'overflow-x', 'hidden'); + this.renderer.setStyle(this.elRef.nativeElement, 'overflow-y', 'hidden'); + + if(direction === 'vertical') { + this.renderer.setStyle(this.elRef.nativeElement, 'max-width', '0'); + } + } + else { + this.renderer.setStyle(this.elRef.nativeElement, 'overflow-x', 'hidden'); + this.renderer.setStyle(this.elRef.nativeElement, 'overflow-y', 'auto'); + this.renderer.removeStyle(this.elRef.nativeElement, 'max-width'); + } + + if(direction === 'horizontal') { + this.renderer.setStyle(this.elRef.nativeElement, 'height', '100%'); + this.renderer.removeStyle(this.elRef.nativeElement, 'width'); + } + else { + this.renderer.setStyle(this.elRef.nativeElement, 'width', '100%'); + this.renderer.removeStyle(this.elRef.nativeElement, 'height'); + } + } + + public setStyleOrder(value: number): void { + this.renderer.setStyle(this.elRef.nativeElement, 'order', value); + } + + public setStyleFlexbasis(value: string, isDragging: boolean): void { + // If component not yet initialized or gutter being dragged, disable transition + if(this.split.isViewInitialized === false || isDragging === true) { + this.setStyleTransition(false); + } + // Or use 'useTransition' to know if transition. + else { + this.setStyleTransition(this.split.useTransition); + } + + this.renderer.setStyle(this.elRef.nativeElement, 'flex-basis', value); + } + + private setStyleTransition(useTransition: boolean): void { + if (useTransition) { + this.renderer.setStyle(this.elRef.nativeElement, 'transition', `flex-basis 0.3s`); + } else { + this.renderer.removeStyle(this.elRef.nativeElement, 'transition'); + } + } + private onTransitionEnd(event: TransitionEvent): void { + // Limit only flex-basis transition to trigger the event + if (event.propertyName === 'flex-basis') { + this.split.notify('transitionEnd'); + } + } + + public lockEvents(): void { + this.ngZone.runOutsideAngular(() => { + this.lockListeners.push( this.renderer.listen(this.elRef.nativeElement, 'selectstart', (e: Event) => false) ); + this.lockListeners.push( this.renderer.listen(this.elRef.nativeElement, 'dragstart', (e: Event) => false) ); + }); + } + + public unlockEvents(): void { + while (this.lockListeners.length > 0) { + const fct = this.lockListeners.pop(); + if (fct) { + fct(); + } + } + } + + public ngOnDestroy(): void { + this.unlockEvents(); + + if (this.transitionListener) { + this.transitionListener(); + } + + this.split.removeArea(this); + } +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/angular-split/components/splitGutter.directive.ts b/web/src/main/webapp/v2/src/app/core/components/angular-split/components/splitGutter.directive.ts new file mode 100644 index 000000000000..3cb0aee974e4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/angular-split/components/splitGutter.directive.ts @@ -0,0 +1,139 @@ +import { Directive, Input, ElementRef, Renderer2 } from '@angular/core'; + +@Directive({ + selector: 'split-gutter' +}) +export class SplitGutterDirective { + + @Input() set order(v: number) { + this.renderer.setStyle(this.elRef.nativeElement, 'order', v); + } + + //// + private _direction: 'vertical' | 'horizontal'; + + @Input() set direction(v: 'vertical' | 'horizontal') { + this._direction = v; + this.refreshStyle(); + } + + get direction(): 'vertical' | 'horizontal' { + return this._direction; + } + + //// + @Input() set useTransition(v: boolean) { + if (v) { + this.renderer.setStyle(this.elRef.nativeElement, 'transition', `flex-basis 0.3s`); + } else { + this.renderer.removeStyle(this.elRef.nativeElement, 'transition'); + } + } + + //// + private _size: number; + + @Input() set size(v: number) { + this._size = v; + this.refreshStyle(); + } + + get size(): number { + return this._size; + } + + //// + private _color: string; + + @Input() set color(v: string) { + this._color = v; + this.refreshStyle(); + } + + get color(): string { + return this._color; + } + + //// + private _imageH: string; + + @Input() set imageH(v: string) { + this._imageH = v; + this.refreshStyle(); + } + + get imageH(): string { + return this._imageH; + } + + //// + private _imageV: string; + + @Input() set imageV(v: string) { + this._imageV = v; + this.refreshStyle(); + } + + get imageV(): string { + return this._imageV; + } + + //// + private _disabled: boolean = false; + + @Input() set disabled(v: boolean) { + this._disabled = v; + this.refreshStyle(); + } + + get disabled(): boolean { + return this._disabled; + } + + //// + constructor(private elRef: ElementRef, + private renderer: Renderer2) {} + + private refreshStyle(): void { + this.renderer.setStyle(this.elRef.nativeElement, 'flex-basis', `${ this.size }px`); + + // fix safari bug about gutter height when direction is horizontal + this.renderer.setStyle(this.elRef.nativeElement, 'height', (this.direction === 'vertical') ? `${ this.size }px` : `100%`); + + this.renderer.setStyle(this.elRef.nativeElement, 'background-color', (this.color !== '') ? this.color : `#eeeeee`); + + const state: 'disabled' | 'vertical' | 'horizontal' = (this.disabled === true) ? 'disabled' : this.direction; + this.renderer.setStyle(this.elRef.nativeElement, 'background-image', this.getImage(state)); + this.renderer.setStyle(this.elRef.nativeElement, 'cursor', this.getCursor(state)); + } + + private getCursor(state: 'disabled' | 'vertical' | 'horizontal'): string { + switch (state) { + case 'horizontal': + return 'col-resize'; + + case 'vertical': + return 'row-resize'; + + case 'disabled': + return 'default'; + } + } + + private getImage(state: 'disabled' | 'vertical' | 'horizontal'): string { + switch (state) { + case 'horizontal': + return (this.imageH !== '') ? this.imageH : defaultImageH; + + case 'vertical': + return (this.imageV !== '') ? this.imageV : defaultImageV; + + case 'disabled': + return ''; + } + } +} + + +const defaultImageH = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==")'; +const defaultImageV = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC")'; diff --git a/web/src/main/webapp/v2/src/app/core/components/angular-split/interface/IArea.ts b/web/src/main/webapp/v2/src/app/core/components/angular-split/interface/IArea.ts new file mode 100644 index 000000000000..20b02201782a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/angular-split/interface/IArea.ts @@ -0,0 +1,7 @@ +import { SplitAreaDirective } from '../components/splitArea.directive'; + +export interface IArea { + comp: SplitAreaDirective; + size: number; + order: number; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/angular-split/interface/IPoint.ts b/web/src/main/webapp/v2/src/app/core/components/angular-split/interface/IPoint.ts new file mode 100644 index 000000000000..a22340cd5ea1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/angular-split/interface/IPoint.ts @@ -0,0 +1,4 @@ +export interface IPoint { + x: number; + y: number; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/angular-split/modules/angularSplit.module.ts b/web/src/main/webapp/v2/src/app/core/components/angular-split/modules/angularSplit.module.ts new file mode 100644 index 000000000000..3f004786409e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/angular-split/modules/angularSplit.module.ts @@ -0,0 +1,38 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { SplitComponent } from '../components/split.component'; +import { SplitAreaDirective } from '../components/splitArea.directive'; +import { SplitGutterDirective } from '../components/splitGutter.directive'; + +@NgModule({ + imports: [ + CommonModule + ], + declarations: [ + SplitComponent, + SplitAreaDirective, + SplitGutterDirective, + ], + exports: [ + SplitComponent, + SplitAreaDirective, + ] +}) +export class AngularSplitModule { + + public static forRoot(): ModuleWithProviders { + return { + ngModule: AngularSplitModule, + providers: [] + }; + } + + public static forChild(): ModuleWithProviders { + return { + ngModule: AngularSplitModule, + providers: [] + }; + } + +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/application-inspector-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/application-inspector-contents-container.component.css new file mode 100644 index 000000000000..ae10a3ace93a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/application-inspector-contents-container.component.css @@ -0,0 +1,24 @@ +:host { + display: flex; + flex-flow: column nowrap; + height: 100%; +} +.l-date-range { + border-bottom: 1px solid #e5e8f0; + background-color: #FFF; + min-height: 161px; + max-height: 1000px +} +.l-main-contents { + height: 100%; + position: relative; + overflow: auto; +} +.l-chart-group-wrap { + display: flex; + flex-flow: row wrap; +} +.l-empty-content { + width: calc(50% - 20px); + margin: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/application-inspector-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/application-inspector-contents-container.component.html new file mode 100644 index 000000000000..1efd551ded39 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/application-inspector-contents-container.component.html @@ -0,0 +1,27 @@ + +
+ + +
+
+
+ + + + + + + + + +
+ + + + +
+
+
+ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/application-inspector-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/application-inspector-contents-container.component.ts new file mode 100644 index 000000000000..db5e46fe17cf --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/application-inspector-contents-container.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; + +import { WebAppSettingDataService } from 'app/shared/services'; + +@Component({ + selector: 'pp-application-inspector-contents-container', + templateUrl: './application-inspector-contents-container.component.html', + styleUrls: ['./application-inspector-contents-container.component.css'] +}) +export class ApplicationInspectorContentsContainerComponent implements OnInit { + isApplicationInspectorActivated$: Observable; + + constructor( + private webAppSettingDataService: WebAppSettingDataService + ) {} + + ngOnInit() { + this.isApplicationInspectorActivated$ = this.webAppSettingDataService.isApplicationInspectorActivated(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/index.ts new file mode 100644 index 000000000000..59ae264062ef --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-contents/index.ts @@ -0,0 +1,28 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { ApplicationInspectorUsageGuideModule } from 'app/core/components/application-inspector-usage-guide'; +import { ApplicationInspectorContentsContainerComponent } from './application-inspector-contents-container.component'; +import { TimelineCommandGroupModule } from 'app/core/components/timeline-command-group'; +import { AgentEventViewModule } from 'app/core/components/agent-event-view'; +import { TimelineModule } from 'app/core/components/timeline'; +import { InspectorChartModule } from 'app/core/components/inspector-chart'; + +@NgModule({ + declarations: [ + ApplicationInspectorContentsContainerComponent + ], +imports: [ + SharedModule, + TimelineCommandGroupModule, + AgentEventViewModule, + TimelineModule, + InspectorChartModule, + ApplicationInspectorUsageGuideModule + ], + exports: [ + ApplicationInspectorContentsContainerComponent + ], + providers: [] +}) +export class ApplicationInspectorContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/application-inspector-title-container.component.css b/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/application-inspector-title-container.component.css new file mode 100644 index 000000000000..27429901956c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/application-inspector-title-container.component.css @@ -0,0 +1,25 @@ +.l-wrapper { + cursor: pointer; + display: block; + padding: 8px 6px 6px 8px; + overflow: hidden; + font-size: 12px; + white-space: nowrap; + text-overflow: ellipsis; +} +.l-wrapper img { + float: left; + width: 22px; + height: 18px; + margin-right: 10px; +} +.l-wrapper.active { + color: #418CD3; + font-weight: 600; + box-shadow: 2px 2px 5px 0px rgba(53,61,117,1); +} +.l-wrapper:hover { + color: #FFF; + cursor: pointer; + background-color: #418CD3; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/application-inspector-title-container.component.html b/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/application-inspector-title-container.component.html new file mode 100644 index 000000000000..37c4b7cab06f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/application-inspector-title-container.component.html @@ -0,0 +1,3 @@ +
+ {{applicationName}} +
diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/application-inspector-title-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/application-inspector-title-container.component.ts new file mode 100644 index 000000000000..2d6ed9abaa3c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/application-inspector-title-container.component.ts @@ -0,0 +1,74 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { + UrlRouteManagerService, + WebAppSettingDataService, + NewUrlStateNotificationService, + AnalyticsService, + TRACKED_EVENT_LIST +} from 'app/shared/services'; + +@Component({ + selector: 'pp-application-inspector-title-container', + templateUrl: './application-inspector-title-container.component.html', + styleUrls: ['./application-inspector-title-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationInspectorTitleContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + agentId: string; + funcImagePath: Function; + applicationServiceType: string; + applicationName: string; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private analyticsService: AnalyticsService, + ) {} + + ngOnInit() { + this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.APPLICATION)) { + this.applicationName = urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName(); + this.applicationServiceType = urlService.getPathValue(UrlPathId.APPLICATION).getServiceType(); + } + if (urlService.hasValue(UrlPathId.AGENT_ID)) { + this.agentId = urlService.getPathValue(UrlPathId.AGENT_ID); + } else { + this.agentId = ''; + } + this.changeDetectorRef.detectChanges(); + }); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + isEmptyAgentId(): boolean { + return this.agentId === ''; + } + getApplicationIcon(): string { + return this.funcImagePath(this.applicationServiceType); + } + onSelectApplication() { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.GO_TO_APPLICATION_INSPECTOR); + this.urlRouteManagerService.moveOnPage({ + url: [ + UrlPath.INSPECTOR, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ] + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/index.ts b/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/index.ts new file mode 100644 index 000000000000..87c5d4d0f1f3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-title/index.ts @@ -0,0 +1,20 @@ + +import { NgModule } from '@angular/core'; +import { MatTooltipModule } from '@angular/material'; +import { SharedModule } from 'app/shared'; +import { ApplicationInspectorTitleContainerComponent } from './application-inspector-title-container.component'; + +@NgModule({ + declarations: [ + ApplicationInspectorTitleContainerComponent + ], + imports: [ + MatTooltipModule, + SharedModule + ], + exports: [ + ApplicationInspectorTitleContainerComponent + ], + providers: [] +}) +export class ApplicationInspectorTitleModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/application-inspector-usage-guide-container.component.css b/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/application-inspector-usage-guide-container.component.css new file mode 100644 index 000000000000..41eb703a5156 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/application-inspector-usage-guide-container.component.css @@ -0,0 +1,31 @@ +:host { + display: block; + height: 100%; +} + +.l-guide-wrapper { + width: 480px; + height: 200px; + margin: auto; + position: relative; + top: 35%; + background-color: #fff; + padding: 15px 20px; + border-radius: 3px; + line-height: 200%; +} + +.l-guide-title { + text-align: center; + font-size: 30px; + padding: 25px 0; +} + +.l-guide-title > .fas { + font-size: 30px; + margin-right: 5px; +} + +.l-guide-text { + text-align: center; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/application-inspector-usage-guide-container.component.html b/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/application-inspector-usage-guide-container.component.html new file mode 100644 index 000000000000..42e306ffcec9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/application-inspector-usage-guide-container.component.html @@ -0,0 +1,4 @@ +
+

Warning

+

+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/application-inspector-usage-guide-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/application-inspector-usage-guide-container.component.ts new file mode 100644 index 000000000000..5584c5eb0130 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/application-inspector-usage-guide-container.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'pp-application-inspector-usage-guide-container', + templateUrl: './application-inspector-usage-guide-container.component.html', + styleUrls: ['./application-inspector-usage-guide-container.component.css'] +}) +export class ApplicationInspectorUsageGuideContainerComponent implements OnInit { + guideMessage$: Observable; + + constructor( + private translateService: TranslateService + ) {} + + ngOnInit() { + this.guideMessage$ = this.translateService.get('INSPECTOR.APPLICATION_INSPECTOR_USAGE_GUIDE_MESSAGE'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/index.ts b/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/index.ts new file mode 100644 index 000000000000..097bb5fcb17e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-inspector-usage-guide/index.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; + +import { ApplicationInspectorUsageGuideContainerComponent } from './application-inspector-usage-guide-container.component'; + +@NgModule({ + declarations: [ + ApplicationInspectorUsageGuideContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + ApplicationInspectorUsageGuideContainerComponent + ], + providers: [], +}) +export class ApplicationInspectorUsageGuideModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-data.service.ts new file mode 100644 index 000000000000..67197ef3fa99 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-data.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { StoreHelperService } from 'app/shared/services/store-helper.service'; +import { Actions } from 'app/shared/store'; +import { Application } from 'app/core/models/application'; + +@Injectable() +export class ApplicationListDataService { + constructor( + private http: HttpClient, + private storeHelperService: StoreHelperService + ) { } + getApplicationList(): Observable { + return this.http.get('applications.pinpoint').pipe( + map(res => { + const body = res || []; + if (body) { + const convertData = body.map(app => new Application(app.applicationName, app.serviceType, app.code)); + this.storeHelperService.dispatch(new Actions.UpdateApplicationList(convertData)); + return convertData; + } else { + return []; + } + }) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-alarm-container.component.css b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-alarm-container.component.css new file mode 100644 index 000000000000..c18ad00430b9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-alarm-container.component.css @@ -0,0 +1,32 @@ +:host { + position: relative; +} +.l-popup-table th { + color:#333; +} +.l-popup-table td { + padding:10px 15px; + height:auto; + border-top:1px solid #e5e8f0; +} +.l-widget-group { + flex:1; + padding: 0 15px; + height: 100%; + align-items: center; +} +.l-search-input { + border:1px solid #469ae4; + font-size:13px; + color:#b3b3b4; + padding:6px 11px; + width:100%; +} +.l-application-list { + padding: 0 !important; + vertical-align: top; +} +.l-application-list > div { + overflow-y: auto; + height: 428px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-alarm-container.component.html b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-alarm-container.component.html new file mode 100644 index 000000000000..01d05ca656e2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-alarm-container.component.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + +
Application
+
+ +
+
+
+ +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-alarm-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-alarm-container.component.ts new file mode 100644 index 000000000000..27b8aa5b8720 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-alarm-container.component.ts @@ -0,0 +1,164 @@ +import { Component, OnInit, AfterViewInit, ViewChild, ElementRef, ChangeDetectorRef, ChangeDetectionStrategy, OnDestroy, Renderer2 } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Subject, combineLatest, fromEvent } from 'rxjs'; +import { debounceTime, distinctUntilChanged, filter, pluck } from 'rxjs/operators'; + +import { WebAppSettingDataService, StoreHelperService } from 'app/shared/services'; +import { ApplicationListInteractionForConfigurationService } from './application-list-interaction-for-configuration.service'; +import { FOCUS_TYPE } from './application-list-for-header.component'; + +@Component({ + selector: 'pp-application-list-for-configuration-alarm-container', + templateUrl: './application-list-for-configuration-alarm-container.component.html', + styleUrls: ['./application-list-for-configuration-alarm-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationListForConfigurationAlarmContainerComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('inputQuery') inputQuery: ElementRef; + i18nText: { [key: string]: string } = { + INPUT_APPLICATION_NAME: '', + SELECTED_APPLICATION_NAME: '', + EMPTY_LIST: '' + }; + private unsubscribe: Subject = new Subject(); + private minLength = 3; + private filterStr = ''; + private applicationList: IApplication[]; + filteredApplicationList: IApplication[]; + selectedApplication: IApplication; + showTitle = false; + focusType: FOCUS_TYPE = FOCUS_TYPE.KEYBOARD; + restCount = 0; + focusIndex = -1; + funcImagePath: Function; + + constructor( + private changeDetector: ChangeDetectorRef, + private renderer: Renderer2, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private translateService: TranslateService, + private applicationListInteractionForConfigurationService: ApplicationListInteractionForConfigurationService + ) {} + ngOnInit() { + this.initI18nText(); + this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc(); + // this.initIconData(); + + this.storeHelperService.getApplicationList(this.unsubscribe).subscribe((applicationList: IApplication[]) => { + this.applicationList = applicationList; + this.filteredApplicationList = this.filterList(this.applicationList); + this.changeDetector.detectChanges(); + }); + } + ngAfterViewInit() { + this.bindUserInputEvent(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private bindUserInputEvent(): void { + fromEvent(this.inputQuery.nativeElement, 'keyup').pipe( + debounceTime(300), + filter((event: KeyboardEvent) => { + return !this.isArrowKey(event.keyCode); + }), + pluck('target', 'value'), + filter((value: string) => { + return this.isLengthValid(value.trim().length); + }), + distinctUntilChanged() + ).subscribe((value: string) => { + this.applyQuery(value); + }); + } + private initI18nText(): void { + combineLatest( + this.translateService.get('MAIN.INPUT_APP_NAME_PLACE_HOLDER'), + this.translateService.get('MAIN.APP_LIST'), + this.translateService.get('CONFIGURATION.GENERAL.EMPTY') + ).subscribe((i18n: string[]) => { + this.i18nText.INPUT_APPLICATION_NAME = i18n[0]; + this.i18nText.APPLICATION_LIST_TITLE = i18n[1]; + this.i18nText.EMPTY_LIST = i18n[2]; + }); + } + private selectApplication(application: IApplication): void { + if (application) { + this.selectedApplication = application; + this.changeDetector.detectChanges(); + } + } + private filterList(appList: IApplication[]): IApplication[] { + if (this.filterStr === '') { + return appList; + } else { + return appList.filter((application: IApplication) => { + return new RegExp(this.filterStr, 'i').test(application.getApplicationName()); + }); + } + } + private applyQuery(query: string): void { + this.filterStr = query; + this.filteredApplicationList = this.filterList(this.applicationList); + this.focusIndex = -1; + this.changeDetector.detectChanges(); + } + + getSelectedApplicationIcon(): string { + return this.funcImagePath(this.selectedApplication.getServiceType()); + } + + getSelectedApplicationName(): string { + if (this.selectedApplication) { + return this.selectedApplication.getApplicationName(); + } else { + return this.i18nText.SELECTED_APPLICATION_NAME; + } + } + onSelectApplication(selectedApplication: IApplication): void { + this.selectApplication(selectedApplication); + this.applicationListInteractionForConfigurationService.setSelectedApplication(selectedApplication); + } + onFocused(index: number): void { + this.focusIndex = index; + this.focusType = FOCUS_TYPE.MOUSE; + this.changeDetector.detectChanges(); + } + onKeyDown(keyCode: number): void { + switch (keyCode) { + case 27: // ESC + this.renderer.setProperty(this.inputQuery.nativeElement, 'value', ''); + this.applyQuery(''); + this.changeDetector.detectChanges(); + break; + // case 13: // Enter + // if (this.focusIndex !== -1) { + // this.onSelectApplication(this.filteredApplicationList[this.focusIndex]); + // this.changeDetector.detectChanges(); + // } + // break; + // case 38: // ArrowUp + // if (this.focusIndex - 1 >= 0) { + // this.focusIndex -= 1; + // this.focusType = FOCUS_TYPE.KEYBOARD; + // this.changeDetector.detectChanges(); + // } + // break; + // case 40: // ArrowDown + // if (this.focusIndex + 1 < this.filteredApplicationList.length) { + // this.focusIndex += 1; + // this.focusType = FOCUS_TYPE.KEYBOARD; + // this.changeDetector.detectChanges(); + // } + // break; + } + } + private isArrowKey(key: number): boolean { + return key >= 37 && key <= 40; + } + private isLengthValid(length: number): boolean { + return length === 0 || length >= this.minLength; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-container.component.css b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-container.component.css new file mode 100644 index 000000000000..7f26ddcb5503 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-container.component.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-container.component.html b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-container.component.html new file mode 100644 index 000000000000..2b5c6ed3f709 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-container.component.html @@ -0,0 +1,7 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-container.component.ts new file mode 100644 index 000000000000..265210ef022c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration-container.component.ts @@ -0,0 +1,60 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, combineLatest, Subject } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { StoreHelperService, WebAppSettingDataService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +@Component({ + selector: 'pp-application-list-for-configuration-container', + templateUrl: './application-list-for-configuration-container.component.html', + styleUrls: ['./application-list-for-configuration-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationListForConfigurationContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + applicationList$: Observable; + funcImagePath: Function; + emptyText$: Observable; + iconBtnClassName = 'fas fa-arrow-right'; + + constructor( + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private translateService: TranslateService, + private analyticsService: AnalyticsService, + ) {} + + ngOnInit() { + this.initList(); + this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc(); + this.initEmptyText(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + private initList(): void { + this.applicationList$ = combineLatest( + this.storeHelperService.getApplicationList(this.unsubscribe), + this.storeHelperService.getFavoriteApplicationList(this.unsubscribe), + ).pipe( + map(([appList, favAppList]: IApplication[][]) => { + return appList.filter((app: IApplication) => { + return favAppList.findIndex((favApp: IApplication) => { + return favApp.equals(app); + }) === -1; + }); + }) + ); + } + private initEmptyText(): void { + this.emptyText$ = this.translateService.get('CONFIGURATION.GENERAL.EMPTY'); + } + + onSelectApp(app: IApplication): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SET_FAVORITE_APPLICATION_IN_CONFIGURATION); + this.webAppSettingDataService.addFavoriteApplication(app); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration.component.css b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration.component.css new file mode 100644 index 000000000000..d093c6fef980 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration.component.css @@ -0,0 +1,46 @@ +:host { + display: block; + max-height: 240px; + overflow-y: auto; +} + +.l-application-list { + font-size: 13px; +} + +.l-application-list-item { + padding: 12px 12px 12px 18px; + color: #666 !important; + font-weight: 400 !important; + display: flex; + align-items: center; + justify-content: flex-start; +} + +.l-application-list-item:hover { + background-color: #e4f3eb; +} + +.l-application-name-wrapper { + flex: auto; + margin-right: 5px; +} + +.l-icon-img { + margin-right: 5px; +} + +.l-select-button > .fas, +.l-select-button > .far { + font-size: 18px; +} + +.l-select-button > .fa-arrow-right { + color: #4a8fd2; +} + +.l-empty-text { + text-align: center; + color: #333; + font-size: 16px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration.component.html b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration.component.html new file mode 100644 index 000000000000..19021e5f6f3a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration.component.html @@ -0,0 +1,8 @@ +
    +
  • + + {{app.getApplicationName()}} + +
  • +
+

{{emptyText}}

diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration.component.ts new file mode 100644 index 000000000000..79cbb6ba987b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-configuration.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-application-list-for-configuration', + templateUrl: './application-list-for-configuration.component.html', + styleUrls: ['./application-list-for-configuration.component.css'] +}) +export class ApplicationListForConfigurationComponent implements OnInit { + @Input() applicationList: IApplication[]; + @Input() emptyText: string; + @Input() funcImagePath: Function; + @Input() iconBtnClassName: string; + @Output() outSelectApp = new EventEmitter(); + + constructor() {} + ngOnInit() {} + + getIconPath(serviceType: string): string { + return this.funcImagePath(serviceType); + } + + onSelectApp(app: IApplication): void { + this.outSelectApp.emit(app); + } + + isListEmpty(): boolean { + return this.applicationList.length === 0; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header-container.component.css b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header-container.component.css new file mode 100644 index 000000000000..ba9d160c721b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header-container.component.css @@ -0,0 +1,87 @@ +:host { + display: block; + position: relative; +} +.l-wrapper { + display: flex; + flex-flow: row wrap; + margin-right: 10px; + width: 310px; + position: relative; +} +.l-app-select { + display: flex; + flex-flow: row nowrap; + width: 100%; + align-items: center; + justify-content: space-between; + cursor: pointer; + height: 32px; + padding: 0 9px; + border: 1px solid #4488CB; + background: #F6FAFE; + font-size: 13px; + border-radius: 0px; + color: #666; +} +.l-app-select img { + margin-right: 6px; +} +.l-app-select > div { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.l-app-select > div img { + float: left; +} +.l-app-select > div span { + line-height: 1.4; +} +.l-input-layer { + position: absolute; + top: 32px; + width: 100%; + border: 1px solid #4488CB; + background: #FFF; + z-index: 10; +} +.l-search-group-wrap { + display: flex; + flex-flow: row wrap; + padding: 11px; + font-size: 13px; + justify-content: flex-end; + position: relative; +} +.l-search-group { + width: 100%; + background: #FFF; + height: 32px; + color: #B3B3B4; + position: relative; +} +.l-search-group input { + width: 100%; + height: 100%; + border: 1px solid #D7DDE4; + padding: 0 10px; +} +.l-app-list { + display: flex; + flex-flow: column nowrap; + max-height: 418px; + overflow-y: auto; + height: 100%; +} +.l-bottom { + display: flex; + flex-flow: row wrap; + padding: 4px; + border-top: 1px solid #d7dde4; +} +.l-bottom button { + font-weight: 100; + flex: 1; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header-container.component.html b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header-container.component.html new file mode 100644 index 000000000000..cbd3e620e214 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header-container.component.html @@ -0,0 +1,46 @@ +
+
+
+ + {{getSelectedApplicationName()}} +
+ +
+
+
+
+ +
+
+
+ + +
+
+ +
+
+ +
diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header-container.component.ts new file mode 100644 index 000000000000..c50a0b3abb5a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header-container.component.ts @@ -0,0 +1,267 @@ +import { Component, OnInit, AfterViewInit, ViewChild, ElementRef, ChangeDetectorRef, ChangeDetectionStrategy, OnDestroy, Renderer2 } from '@angular/core'; +import { Subject, combineLatest, fromEvent, of } from 'rxjs'; +import { debounceTime, distinctUntilChanged, filter, takeUntil, pluck, delay } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { + StoreHelperService, + WebAppSettingDataService, + UrlRouteManagerService, + NewUrlStateNotificationService, + AnalyticsService, + TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { ApplicationListDataService } from './application-list-data.service'; +import { FOCUS_TYPE } from './application-list-for-header.component'; + +@Component({ + selector: 'pp-application-list-for-header-container', + templateUrl: './application-list-for-header-container.component.html', + styleUrls: ['./application-list-for-header-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationListForHeaderContainerComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('inputQuery') inputQuery: ElementRef; + private unsubscribe: Subject = new Subject(); + private maxIndex: number; + private minLength = 3; + private filterStr = ''; + private initApplication: IApplication; + private applicationList: IApplication[]; + private favoriteApplicationList: IApplication[]; + i18nText: { [key: string]: string } = { + FAVORITE_LIST_TITLE: '', + APPLICATION_LIST_TITLE: '', + INPUT_APPLICATION_NAME: '', + SELECTED_APPLICATION_NAME: '', + EMPTY_LIST: '' + }; + showTitle = true; + selectedApplication: IApplication; + focusType: FOCUS_TYPE = FOCUS_TYPE.KEYBOARD; + focusIndex = -1; + hiddenComponent = true; + filteredApplicationList: IApplication[] = []; + filteredFavoriteApplicationList: IApplication[] = []; + funcImagePath: Function; + showLoading = false; + + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private applicationListDataService: ApplicationListDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private translateService: TranslateService, + private analyticsService: AnalyticsService, + private renderer: Renderer2 + ) {} + + ngOnInit() { + this.initI18nText(); + this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc(); + + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.APPLICATION)) { + this.initApplication = urlService.getPathValue(UrlPathId.APPLICATION); + this.selectApplication(this.initApplication); + this.hiddenComponent = true; + } else { + this.hiddenComponent = false; + this.selectedApplication = null; + this.changeDetector.detectChanges(); + } + }); + this.connectStore(); + } + + ngAfterViewInit() { + this.setFocusToInput(); + this.bindUserInputEvent(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + combineLatest( + this.storeHelperService.getApplicationList(this.unsubscribe), + this.storeHelperService.getFavoriteApplicationList(this.unsubscribe) + ).pipe( + takeUntil(this.unsubscribe) + ).subscribe((responseData: any[]) => { + this.refreshList(responseData[0], responseData[1]); + this.showLoading = true; + this.changeDetector.detectChanges(); + }); + } + private bindUserInputEvent(): void { + fromEvent(this.inputQuery.nativeElement, 'keyup').pipe( + debounceTime(300), + distinctUntilChanged(), + filter((event: KeyboardEvent) => { + return !this.isArrowKey(event.keyCode); + }), + pluck('target', 'value'), + filter((value: string) => { + return this.isLengthValid(value.trim().length); + }) + ).subscribe((value: string) => { + this.applyQuery(value); + }); + } + private initI18nText(): void { + combineLatest( + this.translateService.get('MAIN.INPUT_APP_NAME_PLACE_HOLDER'), + this.translateService.get('MAIN.APP_LIST'), + this.translateService.get('MAIN.FAVORITE_APP_LIST'), + this.translateService.get('MAIN.SELECT_YOUR_APP'), + this.translateService.get('CONFIGURATION.GENERAL.EMPTY') + ).subscribe((i18n: string[]) => { + this.i18nText.INPUT_APPLICATION_NAME = i18n[0]; + this.i18nText.APPLICATION_LIST_TITLE = i18n[1]; + this.i18nText.FAVORITE_LIST_TITLE = i18n[2]; + this.i18nText.SELECTED_APPLICATION_NAME = i18n[3]; + this.i18nText.EMPTY_LIST = i18n[4]; + }); + } + + private refreshList(applicationList: IApplication[], favoriteList: IApplication[]): void { + this.applicationList = applicationList; + this.favoriteApplicationList = favoriteList; + this.filteredApplicationList = this.filterList(this.applicationList); + this.filteredFavoriteApplicationList = this.filterList(this.favoriteApplicationList); + this.maxIndex = this.filteredApplicationList.length + this.filteredFavoriteApplicationList.length; + } + + private selectApplication(application: IApplication): void { + if (application) { + this.selectedApplication = application; + this.changeDetector.detectChanges(); + } + } + + private filterList(appList: IApplication[]): IApplication[] { + if (this.filterStr === '') { + return appList; + } else { + return appList.filter((application: IApplication) => { + return new RegExp(this.filterStr, 'i').test(application.getApplicationName()); + }); + } + } + + private setFocusToInput(): void { + of(1).pipe(delay(0)).subscribe((v: number) => { + this.inputQuery.nativeElement.select(); + }); + } + + private applyQuery(query: string): void { + this.filterStr = query; + this.filteredFavoriteApplicationList = this.filterList(this.favoriteApplicationList); + this.filteredApplicationList = this.filterList(this.applicationList); + this.maxIndex = this.filteredApplicationList.length + this.filteredFavoriteApplicationList.length; + this.focusIndex = -1; + this.changeDetector.detectChanges(); + } + + getSelectedApplicationIcon(): string { + return this.funcImagePath(this.selectedApplication.getServiceType()); + } + + getSelectedApplicationName(): string { + if (this.selectedApplication) { + return this.selectedApplication.getApplicationName(); + } else { + return this.i18nText.SELECTED_APPLICATION_NAME; + } + } + + toggleApplicationList(): void { + this.hiddenComponent = !this.hiddenComponent; + if (this.hiddenComponent === false) { + this.setFocusToInput(); + } + } + + onClose(): void { + this.hiddenComponent = true; + } + + onSelectApplication(selectedApplication: IApplication): void { + this.hiddenComponent = true; + if (!selectedApplication.equals(this.selectedApplication)) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_APPLICATION); + this.urlRouteManagerService.changeApplication(selectedApplication.getUrlStr()); + this.selectApplication(selectedApplication); + } + } + + onFocused(index: number): void { + this.focusIndex = index; + this.focusType = FOCUS_TYPE.MOUSE; + this.changeDetector.detectChanges(); + } + + onKeyDown(keyCode: number): void { + if (!this.hiddenComponent) { + switch (keyCode) { + case 27: // ESC + this.renderer.setProperty(this.inputQuery.nativeElement, 'value', ''); + this.applyQuery(''); + this.hiddenComponent = true; + this.changeDetector.detectChanges(); + break; + case 13: // Enter + if (this.focusIndex !== -1) { + const favoriteLen = this.filteredFavoriteApplicationList.length; + if (favoriteLen === 0 || this.focusIndex > favoriteLen) { + this.onSelectApplication(this.filteredApplicationList[this.focusIndex - favoriteLen]); + } else { + this.onSelectApplication(this.filteredFavoriteApplicationList[this.focusIndex]); + } + this.changeDetector.detectChanges(); + } + break; + case 38: // ArrowUp + if (this.focusIndex - 1 >= 0) { + this.focusIndex -= 1; + this.focusType = FOCUS_TYPE.KEYBOARD; + this.changeDetector.detectChanges(); + } + break; + case 40: // ArrowDown + if (this.focusIndex + 1 < this.maxIndex) { + this.focusIndex += 1; + this.focusType = FOCUS_TYPE.KEYBOARD; + this.changeDetector.detectChanges(); + } + break; + } + } + } + + onReload(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_RELOAD_APPLICATION_LIST_BUTTON); + this.showLoading = false; + this.refreshList([], []); + this.applicationListDataService.getApplicationList().subscribe((applicationList: IApplication[]) => { + this.showLoading = true; + this.changeDetector.detectChanges(); + }); + } + + private isArrowKey(key: number): boolean { + return key >= 37 && key <= 40; + } + + private isLengthValid(length: number): boolean { + return length === 0 || length >= this.minLength; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header.component.css b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header.component.css new file mode 100644 index 000000000000..60d2071c97ba --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header.component.css @@ -0,0 +1,39 @@ +:host { + display: block; +} + +.l-application-list { + font-size: 13px; +} + +.l-application-list > dt { + padding: 12px; + background-color: #edf2f8; + color: #333; + font-weight: 600; +} +.l-item { + padding: 12px; + color: #666; + display: flex; + align-items: center; + cursor: pointer; +} +.l-item.active { + color: #FFF !important; + background-color: #4b99e3; +} +.l-item.active.focus { + background-color: #4b99e3; +} +.l-item.focus { + background-color: #e4f3eb; +} +.l-icon-img { + margin: 0 10px 0 0; +} +.l-text-ellipsis { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header.component.html b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header.component.html new file mode 100644 index 000000000000..3a7a5b9ec4dd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header.component.html @@ -0,0 +1,12 @@ +
+
{{title}}
+
+ + {{app.getApplicationName()}} +
+
{{emptyText}}
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header.component.ts new file mode 100644 index 000000000000..55ab8081f3b2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-for-header.component.ts @@ -0,0 +1,73 @@ +import { Component, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core'; + +export enum FOCUS_TYPE { + KEYBOARD, + MOUSE +} + +@Component({ + selector: 'pp-application-list-for-header', + templateUrl: './application-list-for-header.component.html', + styleUrls: ['./application-list-for-header.component.css'] +}) +export class ApplicationListForHeaderComponent implements OnInit, OnChanges { + @ViewChild('appList') ele: ElementRef; + @Input() showTitle: boolean; + @Input() title: string; + @Input() restCount: number; + @Input() focusIndex: number; + @Input() focusType: FOCUS_TYPE; + @Input() applicationList: IApplication[]; + @Input() selectedApplication: IApplication; + @Input() emptyText: string; + @Input() funcImagePath: Function; + @Output() outSelected: EventEmitter = new EventEmitter(); + @Output() outFocused: EventEmitter = new EventEmitter(); + private previousFocusIndex = -1; + + constructor() {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if (changes['focusIndex']) { + const eleIndex = this.focusIndex - this.restCount; + if (eleIndex >= 0 && eleIndex < this.applicationList.length && this.focusType === FOCUS_TYPE.KEYBOARD) { + this.ele.nativeElement.querySelectorAll('dd')[eleIndex].scrollIntoView({ + block: 'nearest', + inline: 'nearest', + behavior: 'instant' + }); + } + } + } + + private isSelectedApplication(app: IApplication): boolean { + return this.selectedApplication && this.selectedApplication.equals(app) ? true : false; + } + + getIconPath(serviceType: string): string { + return this.funcImagePath(serviceType); + } + + makeClass(index: number): { [key: string]: boolean } { + const app = this.applicationList[index - this.restCount]; + return { + active: this.isSelectedApplication(app), + focus: this.focusIndex === index + }; + } + + onFocus(index: number): void { + if (this.previousFocusIndex !== index) { + this.outFocused.emit(index); + this.previousFocusIndex = index; + } + } + + onSelectApplication(app: IApplication): void { + this.outSelected.emit(app); + } + + isListEmpty(): boolean { + return this.applicationList.length === 0; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-interaction-for-configuration.service.ts b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-interaction-for-configuration.service.ts new file mode 100644 index 000000000000..fb98c67b27db --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/application-list-interaction-for-configuration.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; + +@Injectable() +export class ApplicationListInteractionForConfigurationService { + private outSelectApplication = new Subject(); + onSelectApplication$: Observable; + + constructor() { + this.onSelectApplication$ = this.outSelectApplication.asObservable(); + } + setSelectedApplication(application: IApplication): void { + this.outSelectApplication.next(application); + } +} + diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/favorite-application-list-for-configuration-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-list/favorite-application-list-for-configuration-container.component.ts new file mode 100644 index 000000000000..0fd57f0f74c7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/favorite-application-list-for-configuration-container.component.ts @@ -0,0 +1,46 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, Subject } from 'rxjs'; + +import { WebAppSettingDataService, StoreHelperService } from 'app/shared/services'; + +@Component({ + selector: 'pp-favorite-application-list-for-configuration-container', + templateUrl: './application-list-for-configuration-container.component.html', + styleUrls: ['./application-list-for-configuration-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FavoriteApplicationListForConfigurationContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + applicationList$: Observable; + emptyText$: Observable; + funcImagePath: Function; + iconBtnClassName = 'far fa-trash-alt'; + + constructor( + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private webAppSettingDataService: WebAppSettingDataService, + ) {} + + ngOnInit() { + this.initList(); + this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc(); + this.initEmptyText(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + private initList(): void { + this.applicationList$ = this.storeHelperService.getFavoriteApplicationList(this.unsubscribe); + } + private initEmptyText(): void { + this.emptyText$ = this.translateService.get('CONFIGURATION.GENERAL.EMPTY'); + } + + onSelectApp(app: IApplication): void { + this.webAppSettingDataService.removeFavoriteApplication(app); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-list/index.ts b/web/src/main/webapp/v2/src/app/core/components/application-list/index.ts new file mode 100644 index 000000000000..c65cafbee2af --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-list/index.ts @@ -0,0 +1,39 @@ + +import { NgModule } from '@angular/core'; +import { MatTooltipModule } from '@angular/material'; +import { SharedModule } from 'app/shared'; + +import { ApplicationListForHeaderContainerComponent } from './application-list-for-header-container.component'; +import { ApplicationListForHeaderComponent } from './application-list-for-header.component'; +import { ApplicationListForConfigurationContainerComponent } from './application-list-for-configuration-container.component'; +import { ApplicationListForConfigurationComponent } from './application-list-for-configuration.component'; +import { FavoriteApplicationListForConfigurationContainerComponent } from './favorite-application-list-for-configuration-container.component'; +import { ApplicationListForConfigurationAlarmContainerComponent } from './application-list-for-configuration-alarm-container.component'; +import { ApplicationListInteractionForConfigurationService } from './application-list-interaction-for-configuration.service'; +import { ApplicationListDataService } from './application-list-data.service'; + +@NgModule({ + declarations: [ + ApplicationListForHeaderContainerComponent, + ApplicationListForHeaderComponent, + ApplicationListForConfigurationContainerComponent, + ApplicationListForConfigurationComponent, + FavoriteApplicationListForConfigurationContainerComponent, + ApplicationListForConfigurationAlarmContainerComponent + ], + imports: [ + MatTooltipModule, + SharedModule + ], + exports: [ + ApplicationListForHeaderContainerComponent, + ApplicationListForConfigurationContainerComponent, + FavoriteApplicationListForConfigurationContainerComponent, + ApplicationListForConfigurationAlarmContainerComponent + ], + providers: [ + ApplicationListInteractionForConfigurationService, + ApplicationListDataService + ] +}) +export class ApplicationListModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup-container.component.css b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup-container.component.css new file mode 100644 index 000000000000..838ad80020e3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup-container.component.css @@ -0,0 +1,5 @@ +:host { + display: block; + width: 300px; + border-radius: 3px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup-container.component.html b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup-container.component.html new file mode 100644 index 000000000000..d74bbf542726 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup-container.component.html @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup-container.component.ts new file mode 100644 index 000000000000..1ec62a8d53dd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup-container.component.ts @@ -0,0 +1,83 @@ +import { Component, OnInit, ElementRef, AfterViewInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, Subject } from 'rxjs'; +import { withLatestFrom, map, takeUntil } from 'rxjs/operators'; + +import { POPUP_CONSTANT } from 'app/core/components/application-name-issue-popup/application-name-issue-popup.component'; +import { DynamicPopup, TranslateReplaceService, NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; + +@Component({ + selector: 'pp-application-name-issue-popup-container', + templateUrl: './application-name-issue-popup-container.component.html', + styleUrls: ['./application-name-issue-popup-container.component.css'] +}) +export class ApplicationNameIssuePopupContainerComponent implements OnInit, OnDestroy, AfterViewInit, DynamicPopup { + @Input() data: {[key: string]: string}; + @Input() coord: ICoordinate; + @Output() outCreated = new EventEmitter(); + @Output() outClose = new EventEmitter(); + + private unsubscribe = new Subject(); + + data$: Observable<{[key: string]: any}>; + + constructor( + private elementRef: ElementRef, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + ) {} + + ngOnInit() { + const urlApplicationName$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + map((urlService: NewUrlStateNotificationService) => { + return urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName(); + }) + ); + + this.data$ = this.translateService.get('INSPECTOR.APPLICAITION_NAME_ISSUE').pipe( + withLatestFrom(urlApplicationName$), + map(([message, urlAppName]: [{[key: string]: any}, string]) => { + const { agentId, applicationName } = this.data; + + return { + prevAppName: urlAppName, + currAppName: applicationName, + message: { + ISSUE_MESSAGE: this.translateReplaceService.replace(message['ISSUE_MESSAGE'], urlAppName, applicationName), + ISSUE_CAUSES: [ + this.translateReplaceService.replace(message['ISSUE_CAUSES'][0], urlAppName, applicationName), + this.translateReplaceService.replace(message['ISSUE_CAUSES'][1], agentId, applicationName) + ], + ISSUE_SOLUTIONS: [ + this.translateReplaceService.replace(message['ISSUE_SOLUTIONS'][0], urlAppName, agentId), + message['ISSUE_SOLUTIONS'][1] + ] + } + }; + }) + ); + } + + ngAfterViewInit() { + this.outCreated.emit({ + coordX: this.coord.coordX - (this.elementRef.nativeElement.offsetWidth / 2), + coordY: this.coord.coordY + POPUP_CONSTANT.SPACE_FROM_BUTTON + POPUP_CONSTANT.TOOLTIP_TRIANGLE_HEIGHT + }); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + onInputChange(): void { + this.outClose.emit(); + } + + onClickOutside(): void { + this.outClose.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup.component.css b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup.component.css new file mode 100644 index 000000000000..a85534a09ce7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup.component.css @@ -0,0 +1,51 @@ +:host { + display: block; + width: 100%; +} + +:host::after { + content: ''; + width: 0; + height: 0; + position: absolute; + border-bottom: 7px solid #f6f8fb; + border-right: 7px solid transparent; + border-left: 7px solid transparent; + bottom: 100%; + left: 0; + right: 0; + margin: auto; +} + +.l-popup-title { + background-color: #f6f8fb; + border-bottom: 1px solid #e5e8f0; + padding:10px; + display: flex; + justify-content: space-evenly; + font-weight: 400; +} + +.l-popup-title span { + display: inline-block; +} + +.l-issue-message { + padding: 12px 15px 8px 15px; + font-size: 13px; +} + +.l-cause-list { + padding: 0 12px 12px; + font-size: 13px; + border-bottom: 1px solid #e5e8f0; +} + +.l-cause-list-item, .l-solution-list-item { + padding: 3px; +} + +.l-solution-list { + padding: 12px; + font-size: 13px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup.component.html b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup.component.html new file mode 100644 index 000000000000..b6fdf2692baf --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup.component.html @@ -0,0 +1,12 @@ +

+ {{data.prevAppName}} + + {{data.currAppName}} +

+

+
    +
  • {{cause}}
  • +
+
    +
  • {{solution}}
  • +
diff --git a/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup.component.ts b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup.component.ts new file mode 100644 index 000000000000..3a4bcebb8c13 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/application-name-issue-popup.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit, Input } from '@angular/core'; + +export const enum POPUP_CONSTANT { + TOOLTIP_TRIANGLE_HEIGHT = 7, // 툴팁 삼각형 높이 + SPACE_FROM_BUTTON = 10 // 클릭한 버튼에서 살짝 떨어뜨려줄 길이 +} + +@Component({ + selector: 'pp-application-name-issue-popup', + templateUrl: './application-name-issue-popup.component.html', + styleUrls: ['./application-name-issue-popup.component.css'] +}) +export class ApplicationNameIssuePopupComponent implements OnInit { + @Input() data: {[key: string]: any}; + + constructor() {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/index.ts b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/index.ts new file mode 100644 index 000000000000..c0269dd85168 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/application-name-issue-popup/index.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { ApplicationNameIssuePopupContainerComponent } from 'app/core/components/application-name-issue-popup/application-name-issue-popup-container.component'; +import { ApplicationNameIssuePopupComponent } from 'app/core/components/application-name-issue-popup/application-name-issue-popup.component'; + +@NgModule({ + declarations: [ + ApplicationNameIssuePopupContainerComponent, + ApplicationNameIssuePopupComponent + ], + imports: [ + SharedModule + ], + exports: [], + entryComponents: [ + ApplicationNameIssuePopupContainerComponent, + ], + providers: [], +}) +export class ApplicationNameIssuePopupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list-container.component.css new file mode 100644 index 000000000000..9fcef31ebc91 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list-container.component.css @@ -0,0 +1,25 @@ +.authentication-rules-table th .flex-container { + height: 47px; + justify-content: space-between; + display: flex; + align-items: center; +} +.authentication-rules-tr-wrap { + height: 170px; + overflow-y: scroll; + position: relative; +} +.authentication-rules-tr { + display: flex; + height: 30px; + align-items: center; + padding: 0 15px; + position: relative; +} +.authentication-rules-tr-th { + color: #777879; + font-weight: 600; + border-bottom: 1px solid #e6e8ec; + background: #fff; + margin: 0 -21px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list-container.component.html new file mode 100644 index 000000000000..dac0d39174f7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list-container.component.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list-container.component.ts new file mode 100644 index 000000000000..08c96197b2dd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list-container.component.ts @@ -0,0 +1,11 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'pp-authentication-list-container', + templateUrl: './authentication-list-container.component.html', + styleUrls: ['./authentication-list-container.component.css'] +}) +export class AuthenticationListContainerComponent implements OnInit { + constructor() { } + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list.component.css b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list.component.css new file mode 100644 index 000000000000..f2598cf57fe0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list.component.css @@ -0,0 +1,22 @@ +.authentication-rules-tr { + display: flex; + height: 30px; + align-items: center; + padding: 0 15px; + position:relative; +} +.authentication-rules-btn-group { + color: #b3b6bf; + font-size: 13px; + align-items: center; + justify-content: flex-end; +} +.authentication-rules-btn-group button { + margin-left: 11px; +} +.authentication-rules-btn-group button.img-icon { + line-height: 0; +} +.authentication-rules-btn-group button:first-child { + margin-left: 0; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list.component.html b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list.component.html new file mode 100644 index 000000000000..5acb9dbd88fa --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list.component.html @@ -0,0 +1,15 @@ +
+
GUEST
+
GUEST
+
+ + + +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list.component.ts b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list.component.ts new file mode 100644 index 000000000000..c730e4f6962c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/authentication-list/authentication-list.component.ts @@ -0,0 +1,11 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'pp-authentication-list', + templateUrl: './authentication-list.component.html', + styleUrls: ['./authentication-list.component.css'] +}) +export class AuthenticationListComponent implements OnInit { + constructor() { } + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/authentication-list/index.ts b/web/src/main/webapp/v2/src/app/core/components/authentication-list/index.ts new file mode 100644 index 000000000000..7e2bfdbe5b01 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/authentication-list/index.ts @@ -0,0 +1,21 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { AuthenticationListComponent } from './authentication-list.component'; +import { AuthenticationListContainerComponent } from './authentication-list-container.component'; + + +@NgModule({ + declarations: [ + AuthenticationListComponent, + AuthenticationListContainerComponent, + ], + imports: [ + SharedModule + ], + exports: [ + AuthenticationListContainerComponent, + ], + providers: [] +}) +export class AuthenticationListModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree-container.component.css b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree-container.component.html b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree-container.component.html new file mode 100644 index 000000000000..0df383565773 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree-container.component.html @@ -0,0 +1,11 @@ + diff --git a/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree-container.component.ts new file mode 100644 index 000000000000..a5078e9a0bad --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree-container.component.ts @@ -0,0 +1,194 @@ +import { Component, Input, OnInit, AfterViewInit, OnDestroy, ViewChild, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { Actions } from 'app/shared/store'; +import { + StoreHelperService, + NewUrlStateNotificationService, + TransactionViewTypeService, + VIEW_TYPE, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { TransactionSearchInteractionService, ISearchParam } from 'app/core/components/transaction-search/transaction-search-interaction.service'; +import { IGridData } from './call-tree.component'; +import { CallTreeComponent } from './call-tree.component'; +import { MessagePopupContainerComponent } from 'app/core/components/message-popup/message-popup-container.component'; +import { SyntaxHighlightPopupContainerComponent } from 'app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component'; + +@Component({ + selector: 'pp-call-tree-container', + templateUrl: './call-tree-container.component.html', + styleUrls: ['./call-tree-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CallTreeContainerComponent implements OnInit, OnDestroy, AfterViewInit { + @ViewChild(CallTreeComponent) private callTreeComponent: CallTreeComponent; + @Input() canSelectRow = false; + @Input() rowSelection = 'multiple'; + private unsubscribe: Subject = new Subject(); + timezone$: Observable; + dateFormat$: Observable; + ratio: number; + searchSelfTime: number; + hiddenComponent = true; + transactionInfo: ITransactionMetaData; + callTreeOriginalData: ITransactionDetailData; + callTreeData: IGridData[]; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private transactionSearchInteractionService: TransactionSearchInteractionService, + private transactionViewTypeService: TransactionViewTypeService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.transactionViewTypeService.onChangeViewType$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((viewType: string) => { + if ( viewType === VIEW_TYPE.CALL_TREE ) { + this.hiddenComponent = false; + } else { + this.hiddenComponent = true; + } + this.changeDetectorRef.detectChanges(); + }); + this.connectStore(); + this.transactionSearchInteractionService.onSearch$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((params: ISearchParam) => { + if (this.hiddenComponent === true) { + return; + } + this.transactionSearchInteractionService.setSearchResult({ + type: params.type, + query: params.query, + result: this.callTreeComponent.searchRow(params) + }); + }); + } + ngAfterViewInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.SEARCH_ID)) { + const searchId = urlService.getPathValue(UrlPathId.SEARCH_ID); + if (searchId !== '' && this.hiddenComponent === false) { + this.callTreeComponent.moveRow(searchId); + } + } + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(); + this.dateFormat$ = this.storeHelperService.getDateFormat(this.unsubscribe, 2); + this.storeHelperService.getTransactionDetailData(this.unsubscribe).pipe( + filter((transactionDetailInfo: ITransactionDetailData) => { + return transactionDetailInfo && transactionDetailInfo.transactionId ? true : false; + }) + ).subscribe((transactionDetailInfo: ITransactionDetailData) => { + this.ratio = this.calcTimeRatio(transactionDetailInfo.callStack[0][transactionDetailInfo.callStackIndex.begin], transactionDetailInfo.callStack[0][transactionDetailInfo.callStackIndex.end]); + this.callTreeOriginalData = transactionDetailInfo; + this.callTreeData = this.makeGridData(transactionDetailInfo.callStack, transactionDetailInfo.callStackIndex); + this.changeDetectorRef.detectChanges(); + }); + } + private calcTimeRatio(begin: number, end: number): number { + return 100 / (end - begin); + } + private makeGridData(callTreeData: any, oIndex: any): IGridData[] { + const newData = []; + const parentRef = {}; + for ( let i = 0 ; i < callTreeData.length ; i++ ) { + const callTree = callTreeData[i]; + const oRow = {}; + parentRef[callTree[oIndex.id]] = oRow; + this.makeRow(callTree, oIndex, oRow, i); + if ( callTree[oIndex.parentId] ) { + const oParentRow = parentRef[callTree[oIndex.parentId]]; + if ( oParentRow.children instanceof Array === false ) { + oParentRow['folder'] = true; + oParentRow['open'] = true; + oParentRow['children'] = []; + } + oParentRow.children.push(oRow); + } else { + newData.push(oRow); + } + } + return newData; + } + private makeRow(callTree: any, oIndex: any, oRow: IGridData, index: number): void { + oRow['index'] = index; + oRow['id'] = callTree[oIndex.id]; + oRow['method'] = callTree[oIndex.title]; + oRow['argument'] = callTree[oIndex.arguments]; + oRow['startTime'] = callTree[oIndex.begin]; + oRow['gap'] = callTree[oIndex.gap]; + oRow['exec'] = callTree[oIndex.elapsedTime]; + oRow['execPer'] = callTree[oIndex.elapsedTime] ? Math.ceil((callTree[oIndex.end] - callTree[oIndex.begin]) * this.ratio) : ''; + oRow['selp'] = callTree[oIndex.executionMilliseconds]; + oRow['selpPer'] = callTree[oIndex.elapsedTime] && callTree[oIndex.executionMilliseconds] ? + ( Math.floor( callTree[oIndex.executionMilliseconds].replace(/,/gi, '') ) / Math.floor( callTree[oIndex.elapsedTime].replace(/,/gi, '') ) ) * 100 + : 0; + oRow['clazz'] = callTree[oIndex.simpleClassName]; + oRow['api'] = callTree[oIndex.apiType]; + oRow['agent'] = callTree[oIndex.agent]; + oRow['application'] = callTree[oIndex.applicationName]; + oRow['isMethod'] = callTree[oIndex.isMethod]; + oRow['methodType'] = callTree[oIndex.methodType]; + oRow['hasException'] = callTree[oIndex.hasException]; + oRow['isAuthorized'] = callTree[oIndex.isAuthorized]; + oRow['isFocused'] = callTree[oIndex.isFocused]; + if ( callTree[oIndex.hasChild] === true ) { + oRow['folder'] = true; + oRow['open'] = true; + oRow['children'] = []; + } + } + outSelectFormatting(info: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_SQL); + const nextRowData = this.callTreeOriginalData.callStack[info.index + 1]; + const nextValue = nextRowData[this.callTreeOriginalData.callStackIndex.title]; + let bindValue; + + if (nextRowData && (nextValue === 'SQL-BindValue' || nextValue === 'JSON-BindValue')) { + bindValue = nextRowData[this.callTreeOriginalData.callStackIndex.arguments]; + } + + this.dynamicPopupService.openPopup({ + data: { + type: info.type, + originalContents: info.formatText, + bindValue + }, + component: SyntaxHighlightPopupContainerComponent + }); + } + onRowSelected(rowData: IGridData): void { + if (rowData.startTime !== 0) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: -1, + time: rowData.startTime + })); + } + } + onCellDoubleClicked(contents: string): void { + this.dynamicPopupService.openPopup({ + data: { + title: 'Contents', + contents + }, + component: MessagePopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree.component.css b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree.component.css new file mode 100644 index 000000000000..97545b71bb14 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree.component.css @@ -0,0 +1,97 @@ +#callTree .ag-root { + border: none; + font-size: 12px; + font-family: 'Open Sans', sans-serif; +} +#callTree .ag-cell { + padding: 4px; + border-right: 1px solid #e6e8ec; + font-family: 'Open Sans', sans-serif; +} + +#callTree .ag-column-moving .ag-cell { + transition: left 0.2s; +} +#callTree .ag-header-cell-moving .ag-header-cell-label { + opacity: 0; + filter: alpha(opacity=0); +} +#callTree .ag-header-cell-moving { + background-color: #bebebe; +} +#callTree .ag-header-cell-moving-clone { + border-right: 1px solid #808080; + border-left: 1px solid #808080; + background-color: rgba(220,220,220,0.8); +} +#callTree .ag-header { + background: #f6f8fb; + border-bottom: 1px solid #e6e8ec; + line-height: 2; +} +#callTree .ag-header-cell { + font-size: 12px; + font-weight: 600; + font-family: 'Open Sans', sans-serif; + border-right: 1px solid #e6e8ec; + padding-left: 2px; + padding-right: 2px; +} +#callTree .ag-header-cell:first-child { + border-right: none; +} +#callTree .ag-header-cell-resize:after { + border-right: none; +} +#callTree .ag-header-cell-label { + padding: 4px; +} +#callTree .ag-group-expanded span { + margin-right: 4px; +} +#callTree .ag-row { + line-height: 2; +} +#callTree .ag-row-exception { + background-color: #fff1f1; +} +#callTree .ag-row-focused { + background-color: #e4f5e3; +} +#callTree .ag-body { + background-color: #ffffff; +} +#callTree .ag-body-viewport { + background-color: #ffffff; +} +#callTree .ag-menu { + background-color: #ffffff; + border: 1px solid grey; +} +#callTree .fa { + font-size: 14px; +} +#callTree .ag-row-focused { + background-color: #e4f5e3; +} +#callTree .ag-row-selected { + color: #4a8fd2; + background-color: #e7f5f9; +} +#callTree .div-percent-bar { + display: inline-block; + height: 30%; + position: absolute; +} +#callTree .div-percent-value { + position: absolute; + padding-left: 4px; + font-weight: bold; + font-size: 13px; +} +#callTree .div-outer-div { + display: inline-block; + margin-right: 20px; + height: 30%; + width: 92%; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree.component.html b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree.component.html new file mode 100644 index 000000000000..dd066e77dc3c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree.component.html @@ -0,0 +1,8 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree.component.ts b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree.component.ts new file mode 100644 index 000000000000..6c940ab49d8b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/call-tree/call-tree.component.ts @@ -0,0 +1,344 @@ +import { Component, Input, Output, ViewEncapsulation, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import * as moment from 'moment-timezone'; +import { GridOptions, RowNode } from 'ag-grid'; +import { WindowRefService } from 'app/shared/services'; + +export interface IGridData { + index: number; + method: string; + argument: string; + startTime: number; + gap: number; + exec: number; + execPer: number | string; + selp: number; + selpPer: number; + clazz: string; + api: string; + agent: string; + application: string; + isMethod: boolean; + methodType: string; + hasException: boolean; + isAuthorized: boolean; + isFocused: boolean; + folder?: boolean; + open?: boolean; + children?: any[]; +} + +@Component({ + selector: 'pp-call-tree', + templateUrl: './call-tree.component.html', + styleUrls: ['./call-tree.component.css'], + encapsulation: ViewEncapsulation.None +}) +export class CallTreeComponent implements OnInit, OnChanges { + gridOptions: GridOptions; + previousColor: string; + @Input() canSelectRow: boolean; + @Input() rowSelection: string; + @Input() rowData: IGridData[]; + @Input() timezone: string; + @Input() dateFormat: string; + @Output() outSelectFormatting: EventEmitter = new EventEmitter(); + @Output() outRowSelected: EventEmitter = new EventEmitter(); + @Output() outCellDoubleClicked: EventEmitter = new EventEmitter(); + + constructor(private windowRefService: WindowRefService) {} + ngOnInit() { + this.initGridOptions(); + } + ngOnChanges(changes: SimpleChanges) { + if (changes['timezone'] && changes['timezone'].firstChange === false) { + this.gridOptions.api.refreshCells({ + columns: ['startTime'], + force: true + }); + } + if (changes['dateFormat'] && changes['dateFormat'].firstChange === false) { + this.gridOptions.api.refreshCells({ + columns: ['startTime'], + force: true + }); + } + } + private initGridOptions() { + this.gridOptions = { + columnDefs : this.makeColumnDefs(), + headerHeight: 34, + enableColResize: true, + enableSorting: false, + animateRows: true, + rowHeight: 30, + getRowClass: (params: any) => { + if ( params.data.isFocused ) { + return 'ag-row-focused'; + } else if ( params.data.hasException ) { + return 'ag-row-exception'; + } else { + return ''; + } + }, + getNodeChildDetails: (file) => { + if (file.folder) { + return { + group: true, + children: file.children, + expanded: file.open + }; + } else { + return null; + } + }, + onRowClicked: (params: any) => { + if (this.canSelectRow) { + params.node.setSelected(true); + this.outRowSelected.emit(params.data); + } + }, + suppressRowClickSelection: !this.canSelectRow, + rowSelection: this.rowSelection + }; + } + private calcColor(str: string): string { + if ( str ) { + let hash = 0; + let colour = '#'; + for ( let i = 0 ; i < str.length ; i++ ) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + for ( let i = 0 ; i < 3 ; i++ ) { + colour += ('00' + ((hash >> i * 8) & 0xFF).toString(16)).slice(-2); + } + this.previousColor = colour; + } + return this.previousColor; + } + private makeColumnDefs(): any { + return [ + { + headerName: '', + field: 'agent', + width: 10, + minWidth: 10, + maxWidth: 10, + cellStyle: (params: any) => { + return { backgroundColor: this.calcColor(params.value) }; + }, + cellRenderer: (params: any) => { + return ''; + } + }, + { + headerName: 'Method', + field: 'method', + width: 350, + cellRenderer: 'group', + cellRendererParams: { + innerRenderer: this.innerCellRenderer, + suppressCount: true + }, + tooltipField: 'method' + }, + { + headerName: 'Argument', + field: 'argument', + width: 250, + cellStyle: this.argumentCellStyle, + tooltipField: 'argument' + }, + { + headerName: 'StartTime', + field: 'startTime', + width: 170, + valueFormatter: (params: any) => { + return params.value === 0 ? '' : moment(params.value).tz(this.timezone).format(this.dateFormat); + } + }, + { + headerName: 'Gap(ms)', + field: 'gap', + width: 75, + cellStyle: this.alignRightCellStyle, + valueFormatter: (params: any) => { + return params.value === '' ? '' : new Intl.NumberFormat().format(params.value); + } + }, + { + headerName: 'Exec(ms)', + field: 'exec', + width: 78, + cellStyle: this.alignRightCellStyle, + valueFormatter: (params: any) => { + return params.value === '' ? '' : new Intl.NumberFormat().format(params.value); + } + }, + { + headerName: 'Exec(%)', + field: 'execPer', + width: 100, + minWidth: 100, + maxWidth: 100, + cellRenderer: (params: any) => { + if ( params.value === '' ) { + return ''; + } + const adjustRatio = 0.92; + const value = params.value; + const eDivPercentBar = this.windowRefService.nativeWindow.document.createElement('div'); + eDivPercentBar.className = 'div-percent-bar'; + eDivPercentBar.style.width = (value * adjustRatio) + '%'; + eDivPercentBar.style.top = '10px'; + eDivPercentBar.style.backgroundColor = '#5bc0de'; + const eDivSelfBar = this.windowRefService.nativeWindow.document.createElement('div'); + eDivSelfBar.className = 'div-percent-bar'; + eDivSelfBar.style.height = '18%'; + eDivSelfBar.style.top = '12px'; + if ( params.data.selpPer ) { + eDivSelfBar.style.width = (((value * params.data.selpPer) / 100) * adjustRatio) + '%'; + } else { + eDivSelfBar.style.width = '0%'; + } + eDivSelfBar.style.backgroundColor = '#4343C8'; + const eOuterDiv = this.windowRefService.nativeWindow.document.createElement('div'); + eOuterDiv.className = 'div-outer-div'; + eOuterDiv.appendChild(eDivPercentBar); + eOuterDiv.appendChild(eDivSelfBar); + return eOuterDiv; + } + }, + { + headerName: 'Self(ms)', + field: 'selp', + width: 78, + cellStyle: this.alignRightCellStyle, + valueFormatter: function(params: any) { + return params.value === '' ? '' : new Intl.NumberFormat().format(params.value); + } + }, + { + headerName: 'Class', + field: 'clazz', + width: 150, + tooltipField: 'clazz' + }, + { + headerName: 'API', + field: 'api', + width: 150, + tooltipField: 'api' + }, + { + headerName: 'Agent', + field: 'agent', + width: 150, + tooltipField: 'agent' + }, + { + headerName: 'Application', + field: 'application', + width: 150, + tooltipField: 'application' + } + ]; + } + argumentCellStyle(): any { + return {'text-align': 'left'}; + } + alignRightCellStyle(): any { + return {'text-align': 'right'}; + } + timeFormatter(params: any): string { + return params.value === 0 ? '' : moment(params.value).tz(this.timezone).format(this.dateFormat); + } + numberFormatter(params: any): string { + return params.value === '' ? '' : new Intl.NumberFormat().format(params.value); + } + innerCellRenderer(params: any) { + let result = ''; + if ( params.data.hasException) { + result += ' '; + } else if (!params.data.isMethod) { + if ( params.data.method === 'SQL' || params.data.method === 'JSON' ) { + result += ' '; + return ' ' + result; + } else { + result += ' '; + } + } else { + const itemMethodType = +params.data.methodType; + switch ( itemMethodType ) { + case 100: + result += ' '; + break; + case 200: + result += ' '; + break; + case 900: + result += ' '; + break; + } + } + return ' ' + result + params.data.method; + } + onCellClick(params: any): void { + if ( params.colDef.field === 'method' && (params.value === 'SQL' || params.value === 'JSON') ) { + this.outSelectFormatting.next({ + type: params.value, + formatText: params.data.argument, + index: params.data.index + }); + } + } + onCellDoubleClicked(params: any): void { + this.outCellDoubleClicked.next(params.data[params.colDef.field]); + } + searchRow({type, query}: {type: string, query: string | number}): number { + let resultCount = 0; + let targetIndex = -1; + const fnCompare: { [key: string]: Function } = { + 'all': (data: any, value: string): boolean => { + return (data.method && data.method.indexOf(value) !== -1) || + (data.argument && data.argument.indexOf(value) !== -1) || + (data.clazz && data.clazz.indexOf(value) !== -1) || + (data.api && data.api.indexOf(value) !== -1) || + (data.agent && data.agent.indexOf(value) !== -1) || + (data.application && data.application.indexOf(value) !== -1); + }, + 'self': (data: any, value: number): boolean => { + return +data.selp >= +value; + }, + 'argument': (data: any, value: string): boolean => { + return data.argument.indexOf(value) !== -1; + } + }; + this.gridOptions.api.forEachNode((rowNode: RowNode) => { + if (fnCompare[type](rowNode.data, query)) { + if (resultCount === 0) { + targetIndex = rowNode.data.index; + } + resultCount++; + rowNode.setSelected(true); + } else { + rowNode.setSelected(false); + } + }); + if (resultCount > 0) { + this.gridOptions.api.ensureIndexVisible(targetIndex, 'top'); + } + return resultCount; + } + moveRow(id: string): void { + let targetIndex = -1; + this.gridOptions.api.forEachNode((rowNode: RowNode) => { + if (rowNode.data.id === id) { + targetIndex = rowNode.data.index; + rowNode.setSelected(true); + } else { + rowNode.setSelected(false); + } + }); + this.gridOptions.api.ensureIndexVisible(targetIndex, 'top'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/call-tree/index.ts b/web/src/main/webapp/v2/src/app/core/components/call-tree/index.ts new file mode 100644 index 000000000000..b4677910870f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/call-tree/index.ts @@ -0,0 +1,27 @@ + +import { NgModule } from '@angular/core'; +import { AgGridModule } from 'ag-grid-angular/main'; + +import { SharedModule } from 'app/shared'; +import { CallTreeComponent } from './call-tree.component'; +import { CallTreeContainerComponent } from './call-tree-container.component'; +import { MessagePopupModule } from 'app/core/components/message-popup'; +import { SyntaxHighlightPopupModule } from 'app/core/components/syntax-highlight-popup'; + +@NgModule({ + declarations: [ + CallTreeComponent, + CallTreeContainerComponent + ], + imports: [ + SharedModule, + AgGridModule.withComponents([]), + MessagePopupModule, + SyntaxHighlightPopupModule + ], + exports: [ + CallTreeContainerComponent + ], + providers: [] +}) +export class CallTreeModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/command-group/command-group-container.component.css b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group-container.component.css new file mode 100644 index 000000000000..e7a72018d679 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group-container.component.css @@ -0,0 +1,3 @@ +:host { + display: flex; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/command-group/command-group-container.component.html b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group-container.component.html new file mode 100644 index 000000000000..5594dd47ce9c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group-container.component.html @@ -0,0 +1,3 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/command-group/command-group-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group-container.component.ts new file mode 100644 index 000000000000..59e791b7a65e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group-container.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; + +import { AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { ConfigurationPopupContainerComponent } from 'app/core/components/configuration-popup/configuration-popup-container.component'; + +@Component({ + selector: 'pp-command-group-container', + templateUrl: './command-group-container.component.html', + styleUrls: ['./command-group-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CommandGroupContainerComponent implements OnInit { + constructor( + private dynamicPopupService: DynamicPopupService, + private analyticsService: AnalyticsService, + ) {} + + ngOnInit() {} + onOpenConfigurationPopup(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_CONFIGURATION_POPUP); + this.dynamicPopupService.openPopup({ + component: ConfigurationPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/command-group/command-group.component.css b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group.component.css new file mode 100644 index 000000000000..56e7b9de4b5e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group.component.css @@ -0,0 +1,21 @@ +:host { + display: flex; +} + +button { + outline: none; +} + +.fa-cog, .fa-github { + font-size: 18px; +} +.l-tool-group { + width:100px; + display: flex; + justify-content: center; + color:#fff; + font-size: 18px; +} +.l-tool-group button { + margin:0 8px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/command-group/command-group.component.html b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group.component.html new file mode 100644 index 000000000000..b8cfc00a61c9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group.component.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/command-group/command-group.component.ts b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group.component.ts new file mode 100644 index 000000000000..a30ec31b6b6a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/command-group/command-group.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +/** + * 도움말, 설정, repository 링크등을 제공하는 Component + */ +@Component({ + selector: 'pp-command-group', + templateUrl: './command-group.component.html', + styleUrls: ['./command-group.component.css'] +}) +export class CommandGroupComponent implements OnInit { + @Output() outOpenConfigurationPopup: EventEmitter = new EventEmitter(); + + constructor() {} + ngOnInit() {} + onOpenConfigurationPopup(): void { + this.outOpenConfigurationPopup.emit(); + } + onOpenRepository(): void { + window.open('http://github.com/naver/pinpoint'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/command-group/index.ts b/web/src/main/webapp/v2/src/app/core/components/command-group/index.ts new file mode 100644 index 000000000000..c52a61e01a26 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/command-group/index.ts @@ -0,0 +1,23 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { CommandGroupComponent } from './command-group.component'; +import { CommandGroupContainerComponent } from './command-group-container.component'; +import { ConfigurationPopupModule } from 'app/core/components/configuration-popup'; + +@NgModule({ + declarations: [ + CommandGroupComponent, + CommandGroupContainerComponent + ], + imports: [ + CommonModule, + ConfigurationPopupModule + ], + exports: [ + CommandGroupContainerComponent + ], + providers: [] +}) +export class CommandGroupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-alarm.component.css b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-alarm.component.css new file mode 100644 index 000000000000..64b7eff11466 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-alarm.component.css @@ -0,0 +1,11 @@ +.l-content-section { + display: flex; + padding: 30px 0 0; +} +.l-content-item { + flex:1; + margin-right: 20px; +} +.l-content-item2 { + flex:2; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-alarm.component.html b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-alarm.component.html new file mode 100644 index 000000000000..dec0eccb5d89 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-alarm.component.html @@ -0,0 +1,8 @@ +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-alarm.component.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-alarm.component.ts new file mode 100644 index 000000000000..098cd6729e51 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-alarm.component.ts @@ -0,0 +1,12 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'pp-configuration-popup-alarm', + templateUrl: './configuration-popup-alarm.component.html', + styleUrls: ['./configuration-popup-alarm.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ConfigurationPopupAlarmComponent implements OnInit { + constructor() {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-container.component.css b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-container.component.css new file mode 100644 index 000000000000..3c6a97654ec4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-container.component.css @@ -0,0 +1,18 @@ +:host { + display: block; + background-color: transparent; + width: 100%; + height: 100%; +} + +:host::before { + content: ''; + display: block; + height: 100%; + width: 100%; + background: #000; + opacity: 0.6; + position: absolute; + left: 0; + top: 0; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-container.component.html b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-container.component.html new file mode 100644 index 000000000000..b47b78a8f04d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-container.component.html @@ -0,0 +1 @@ + diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-container.component.ts new file mode 100644 index 000000000000..eeb9fc56e3e4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-container.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit, Output, EventEmitter, AfterViewInit } from '@angular/core'; + +import { WebAppSettingDataService, DynamicPopup } from 'app/shared/services'; + +@Component({ + selector: 'pp-configuration-popup-container', + templateUrl: './configuration-popup-container.component.html', + styleUrls: ['./configuration-popup-container.component.css'] +}) +export class ConfigurationPopupContainerComponent implements OnInit, AfterViewInit, DynamicPopup { + @Output() outClose = new EventEmitter(); + @Output() outCreated = new EventEmitter(); + + funcImagePath: Function; + + constructor( + private webAppSettingDataService: WebAppSettingDataService, + ) {} + + ngOnInit() { + this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc(); + } + + ngAfterViewInit() { + this.outCreated.emit({ coordX: 0, coordY: 0 }); + } + + onClosePopup(): void { + this.outClose.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-general-container.component.css b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-general-container.component.css new file mode 100644 index 000000000000..69c16a21ef1a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-general-container.component.css @@ -0,0 +1,78 @@ +:host { + display: block; + padding: 25px 10px 0; +} + +.l-desc-text-wrapper { + margin-bottom: 25px; +} + +.l-desc-text { + color: #999; + font-size: 14px; +} + +.l-popup-table { + border: 1px solid #e5e8f0; + width: 100%; + background-color: #fff; + font-family: 'Open Sans', sans-serif; +} + +.l-popup-table thead { + border-bottom: 1px solid #e5e8f0; +} + +.l-popup-table th { + font-size: 13px; + font-weight: 600; + color: #333; + background-color: #f6f8fb; + padding: 8px 20px; +} + +.l-popup-table td { + font-size: 13px; + font-weight: 400; + color: #999; + padding: 8px 20px; + border-left: 1px solid #e5e8f0; +} + +.l-favorite-application-table { + margin-top: 17px; + table-layout: fixed; +} + +.l-favorite-application-table td { + padding: 0; + vertical-align: baseline; +} + +.l-color-blue { + color: #4b99e3; +} + +.l-text-bold { + font-weight: 600; +} + +.l-servermap-span { + color: #666; +} +.l-servermap-span .fas { + color:#d0d0d0; +} + + +.fa-sign-in-alt, .fa-sign-out-alt { + color: #33b692; +} + +.l-timezone-dateformat-table { + margin-top: 17px; +} + +.l-timezone-dateformat-table th { + border-left: 1px solid #e5e8f0; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-general-container.component.html b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-general-container.component.html new file mode 100644 index 000000000000..35cb393d28e8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-general-container.component.html @@ -0,0 +1,68 @@ +

{{desc$ | async}}

+ + + + + + + + + + + + + + + + +
ServerMap
+ Search Depth + ( Inbound Outbound ) + + + Search Period + +
+ + + + + + + + + + + + + + + + +
Favorite Application
+ + + +
+ + + + + + + + + + + + + + + + + +
TimeZoneDate Format
+ + + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-general-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-general-container.component.ts new file mode 100644 index 000000000000..7776e2d6586c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-general-container.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'pp-configuration-popup-general-container', + templateUrl: './configuration-popup-general-container.component.html', + styleUrls: ['./configuration-popup-general-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ConfigurationPopupGeneralContainerComponent implements OnInit { + desc$: Observable; + + constructor( + private translateService: TranslateService, + ) {} + ngOnInit() { + this.initDescText(); + } + + private initDescText(): void { + this.desc$ = this.translateService.get('CONFIGURATION.GENERAL.DESC'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-help-container.component.css b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-help-container.component.css new file mode 100644 index 000000000000..53af6888bb2a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-help-container.component.css @@ -0,0 +1,88 @@ +:host { + display: block; + padding: 25px 10px 0; +} +.l-card-link-list { + display: flex; + width: 100%; + justify-content: space-between; + margin-bottom: 40px; +} +.l-card-link-list-item { + display: block; + width: 32%; + height: 120px; +} +.l-card-link { + display: flex; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; +} +.l-faq-link { + background-color: #4a8fd2; +} +.l-issue-link { + background-color: #4ab0d2; +} +.l-user-group-link { + background-color: #50cba4; +} +.l-card-text { + font-size: 18px; + font-weight: 400; + color: #fff; +} +.l-card-link .fas, .l-card-link .fab { + font-size: 42px; + margin-right: 7px; +} +.fa-comments { + color: #b4cce9; +} +.fa-github { + color: #b4dae9; +} +.fa-users { + color: #b5e6d5; +} + +h4 { + font-weight: 600; + padding-bottom: 15px; + border-bottom: 1px solid #e5e8f0; +} +.l-guide-contents-wrapper { + display: flex; + position: relative; +} +.l-guide-contents { + width: 50%; + padding-left: 30px; +} +.l-guide-contents:first-child { + border-right: 1px solid #e5e8f0; +} +.l-guide-language-text { + color: #666; + font-size: 22px; + font-weight: 600; + padding: 25px 0; + text-align: center; +} +.l-guide-contents-list li { + padding: 5px 0; +} +.l-guide-contents-list a > .fas { + color: #9ec2e4; + margin-right: 5px; +} + +.l-guide-contents-list a:link { + color: #428bca; +} + +.l-guide-contents-list a:visited { + color: #609; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-help-container.component.html b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-help-container.component.html new file mode 100644 index 000000000000..a18809764360 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-help-container.component.html @@ -0,0 +1,45 @@ + +

Document & Guide

+ diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-help-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-help-container.component.ts new file mode 100644 index 000000000000..d7741177f29f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-help-container.component.ts @@ -0,0 +1,11 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'pp-configuration-popup-help-container', + templateUrl: 'configuration-popup-help-container.component.html', + styleUrls: ['./configuration-popup-help-container.component.css'], +}) +export class ConfigurationPopupHelpContainerComponent implements OnInit { + constructor() { } + ngOnInit() { } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-container.component.css b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-container.component.css new file mode 100644 index 000000000000..d40cdcfe59d1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-container.component.css @@ -0,0 +1,14 @@ +:host { + display: block; + padding: 25px 10px 0; +} +.l-desc-text-wrapper { + margin-bottom: 25px; +} +.l-desc-text { + color: #999; + font-size: 14px; +} +.l-check-wrapper { + margin-bottom: 40px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-container.component.html b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-container.component.html new file mode 100644 index 000000000000..f9589a0fd5b0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-container.component.html @@ -0,0 +1,12 @@ +

{{desc$ | async}}

+
+ + +
+ + + + + diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-container.component.ts new file mode 100644 index 000000000000..edd493753d4b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-container.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ApplicationNameDuplicationCheckInteractionService } from 'app/core/components/duplication-check/application-name-duplication-check-interaction.service'; +import { AgentIdDuplicationCheckInteractionService } from 'app/core/components/duplication-check/agent-id-duplication-check-interaction.service'; +import { ConfigurationPopupInstallationDataService, IInstallationData } from './configuration-popup-installation-data.service'; + +import { Observable, combineLatest, of } from 'rxjs'; +import { filter, catchError, pluck } from 'rxjs/operators'; + +@Component({ + selector: 'pp-configuration-popup-installation-container', + templateUrl: './configuration-popup-installation-container.component.html', + styleUrls: ['./configuration-popup-installation-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ConfigurationPopupInstallationContainerComponent implements OnInit { + desc$: Observable; + installationInfo$: Observable; + jvmArgument$: Observable; + + constructor( + private translateService: TranslateService, + private configurationPopupInstallationDataService: ConfigurationPopupInstallationDataService, + private applicationNameDuplicationCheckInteractionService: ApplicationNameDuplicationCheckInteractionService, + private agentIdDuplicationCheckInteractionService: AgentIdDuplicationCheckInteractionService + ) {} + + ngOnInit() { + this.initDescText(); + this.initInstallationInfo(); + this.initJVMArgument(); + } + + private initDescText(): void { + this.desc$ = this.translateService.get('CONFIGURATION.INSTALLATION.DESC'); + } + + private initInstallationInfo(): void { + this.installationInfo$ = this.configurationPopupInstallationDataService.getData() + .pipe( + filter((data: IInstallationData) => { + return data.code === 0; + }), + pluck('message'), + catchError((err) => { + return this.onAjaxError(err); + }) + ); + } + + private initJVMArgument(): void { + this.jvmArgument$ = combineLatest( + this.applicationNameDuplicationCheckInteractionService.onCheckSuccess$, + this.agentIdDuplicationCheckInteractionService.onCheckSuccess$, + ); + } + + private onAjaxError(err: Error): Observable { + // TODO: Error발생시 띄워줄 팝업 컴포넌트 Call - issue#170 + return of({ + downloadUrl: '', + installationArgument: '' + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-data.service.ts new file mode 100644 index 000000000000..3f4477f05b57 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-data.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export interface IInstallationData { + code: number; + message: { + downloadUrl: string, + installationArgument: string, + version: string + }; +} + +@Injectable() +export class ConfigurationPopupInstallationDataService { + private dataRequestURL = 'getAgentInstallationInfo.pinpoint'; + + constructor( + private http: HttpClient, + ) {} + + getData(): Observable { + return this.http.get(this.dataRequestURL); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-download-link.component.css b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-download-link.component.css new file mode 100644 index 000000000000..f6a000629050 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-download-link.component.css @@ -0,0 +1,17 @@ +:host { + display: block; + border: 1px solid #e5e8f0; + border-left: 5px solid #4a8fd2; + margin-bottom: 15px; + padding: 25px 20px; +} + +.download-link { + font-size: 14px; + color: #428bca; +} + +h4 { + font-weight: 600; + margin-bottom: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-download-link.component.html b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-download-link.component.html new file mode 100644 index 000000000000..be494e0bf01a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-download-link.component.html @@ -0,0 +1,2 @@ +

Download Link

+{{downloadLink}} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-download-link.component.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-download-link.component.ts new file mode 100644 index 000000000000..2499727c3b8a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-download-link.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'pp-configuration-popup-installation-download-link', + templateUrl: './configuration-popup-installation-download-link.component.html', + styleUrls: ['./configuration-popup-installation-download-link.component.css'], +}) +export class ConfigurationPopupInstallationDownloadLinkComponent implements OnInit { + @Input() downloadLink: string; + + constructor() {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-jvm-argument-info.component.css b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-jvm-argument-info.component.css new file mode 100644 index 000000000000..a5d9867cf8ed --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-jvm-argument-info.component.css @@ -0,0 +1,61 @@ +:host { + display: block; + border: 1px solid #e5e8f0; + border-left: 5px solid #4a8fd2; + padding: 25px 20px; + position: relative; +} + +h4 { + font-weight: 600; + margin-bottom: 10px; +} + +.contents-wrapper { + display: flex; +} + +.textarea-wrapper { + position: relative; + width: 60%; +} + +.copied-noti-text-wrapper { + width: 40%; + position: relative; +} + +.jvm-argument-info { + border: 1px solid #e5e8f0; + font-size: 14px; + width: 100%; + height: 80px; + padding: 10px; + resize: none; +} + +.copy-button { + position: absolute; + right: 5px; + top: 5px; +} + +.copy-button:focus { + outline: 0; +} + +.fa-clone { + font-size: 20px; + color: #ccc; +} + +.fa-clone:hover { + color: #979797; +} + +.copied-noti-text { + position: absolute; + top: 25px; + left: 50%; + font-size: 15px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-jvm-argument-info.component.html b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-jvm-argument-info.component.html new file mode 100644 index 000000000000..dc673a663198 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-jvm-argument-info.component.html @@ -0,0 +1,8 @@ +

JVM Argument Info

+
+
+ + +
+

Copied!

+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-jvm-argument-info.component.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-jvm-argument-info.component.ts new file mode 100644 index 000000000000..162962efe5f5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-installation-jvm-argument-info.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'pp-configuration-popup-installation-jvm-argument-info', + templateUrl: './configuration-popup-installation-jvm-argument-info.component.html', + styleUrls: ['./configuration-popup-installation-jvm-argument-info.component.css'], +}) +export class ConfigurationPopupInstallationJVMArgumentInfoComponent implements OnInit { + @Input() installationArgument: string; + @Input() jvmArgument: string[]; + + isArgumentInfoCopied: boolean; + + constructor() {} + ngOnInit() {} + + getJVMArgumentInfoInView(): string { + const [applicationName, agentId] = this.jvmArgument; + + return `${this.installationArgument}\n-Dpinpoint.applicationName=${applicationName}\n-Dpinpoint.agentId=${agentId}`; + } + + onCopySuccess(): void { + this.updateCopiedStatus(true); + setTimeout(() => { + this.updateCopiedStatus(false); + }, 2000); + } + + private updateCopiedStatus(status: boolean): void { + this.isArgumentInfoCopied = status; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-usergroup.component.css b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-usergroup.component.css new file mode 100644 index 000000000000..993e89965d5b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-usergroup.component.css @@ -0,0 +1,11 @@ +.l-content-section { + display: flex; + padding: 30px 0 0; +} +.l-content-item { + flex:1; + margin-right: 20px; +} +.l-content-item:last-child { + margin-right: 0px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-usergroup.component.html b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-usergroup.component.html new file mode 100644 index 000000000000..9500e4681f91 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-usergroup.component.html @@ -0,0 +1,11 @@ +
+
+ +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-usergroup.component.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-usergroup.component.ts new file mode 100644 index 000000000000..3666c0bd15cf --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup-usergroup.component.ts @@ -0,0 +1,11 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'pp-configuration-popup-usergroup', + templateUrl: './configuration-popup-usergroup.component.html', + styleUrls: ['./configuration-popup-usergroup.component.css'] +}) +export class ConfigurationPopupUsergroupComponent implements OnInit { + constructor() {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup.component.css b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup.component.css new file mode 100644 index 000000000000..2b8d79f12617 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup.component.css @@ -0,0 +1,82 @@ +:host { + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 100%; + max-width: 1000px; + height: 710px; + background-color: #fff; + border: 1px solid #e5e8f0; + box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.75); + text-align: left; +} +.l-title-group { + padding:20px; + border-bottom:1px solid #e5e8f0; + position:relative; +} + +.l-title-group dt { + font-size:20px; + font-weight:normal; + color:#4a8fd2; +} + +.l-contents-group { + overflow: visible; + padding: 17px 18px; +} +.l-tab-menu { + position: relative; +} +.l-tab-menu ul { + border-bottom: 1px solid #e6e8ec; +} +.l-tab-menu ul:after { + content:""; + display:block; + width:100%; + height:0; + visibility:hidden; + clear:both; +} +.l-tab-menu li { + font-weight: 400 !important; + float: left; +} +.l-tab-menu li a { + cursor: pointer; + display:inline-block; + font-size: 13px; + color: #666; + padding: 14px 15px; + border: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + line-height: 1em; +} +.l-tab-menu li:hover a { + border-color: #e6e8ec #e6e8ec #fff; +} +.l-tab-menu li.active a { + color: #fff; + background: #4a8fd2; +} +.l-tab-menu li.active a:hover { + border-color: transparent; +} +.l-sql-popup-close { + position:absolute; + right:27px; + top:50%; + transform:translateY(-50%); + color:#4a8fd2; + font-size: 30px; + width:20px; + height:20px; + background:url(../../../../assets/img/icon-close.png) no-repeat 0 0; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup.component.html b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup.component.html new file mode 100644 index 000000000000..d646cc010c07 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup.component.html @@ -0,0 +1,14 @@ +
+
+
Pinpoint Configuration
+
+ +
+
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup.component.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup.component.ts new file mode 100644 index 000000000000..9943cff66cda --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/configuration-popup.component.ts @@ -0,0 +1,124 @@ +import { Component, OnInit, OnDestroy, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, Output, EventEmitter, HostBinding } from '@angular/core'; + +import { ConfigurationPopupGeneralContainerComponent } from './configuration-popup-general-container.component'; +import { ConfigurationPopupUsergroupComponent } from './configuration-popup-usergroup.component'; +import { ConfigurationPopupAlarmComponent } from './configuration-popup-alarm.component'; +import { ConfigurationPopupInstallationContainerComponent } from './configuration-popup-installation-container.component'; +import { ConfigurationPopupHelpContainerComponent } from './configuration-popup-help-container.component'; + +@Component({ + selector: 'pp-configuration-popup', + templateUrl: './configuration-popup.component.html', + styleUrls: ['./configuration-popup.component.css'] +}) +export class ConfigurationPopupComponent implements OnInit, OnDestroy { + @ViewChild('contentContainer', { read: ViewContainerRef }) contentContainer: ViewContainerRef; + @Input() funcImagePath: Function; + @Output() outClosePopup = new EventEmitter(); + @HostBinding('class.font-opensans') fontFamily = true; + + private componentMap = new Map(); + private componentList = [ + ConfigurationPopupGeneralContainerComponent, + ConfigurationPopupUsergroupComponent, + ConfigurationPopupAlarmComponent, + ConfigurationPopupInstallationContainerComponent, + ConfigurationPopupHelpContainerComponent + ]; + tabList: any[]; + + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + ) {} + + ngOnInit() { + this.initTabList(); + this.initComponentMap(); + this.loadComponent(this.tabList.find((tab) => tab.isActive).id); + } + + ngOnDestroy() { + this.componentMap.forEach((value) => { + if (value.componentRef) { + value.componentRef.destroy(); + } + }); + } + + onTabClick(tabName: string): void { + this.setActiveTab(tabName); + this.contentContainer.detach(0); + if (!this.isComponentLoaded(tabName)) { + this.loadComponent(tabName); + } else { + this.contentContainer.insert(this.componentMap.get(tabName).componentRef.hostView); + } + } + + private initTabList(): void { + this.tabList = [{ + id: 'general', + displayText: 'General', + isActive: true + }, + { + id: 'usergroup', + displayText: 'User Group', + isActive: false + }, + { + id: 'alarm', + displayText: 'Alarm', + isActive: false + }, + { + id: 'installation', + displayText: 'Installation', + isActive: false + }, + { + id: 'help', + displayText: 'Help', + isActive: false + }]; + } + + + private initComponentMap(): void { + this.tabList.forEach((value, i) => { + this.componentMap.set(value.id, { + component: this.componentList[i], + isLoaded: false, + componentRef: undefined + }); + }); + } + + private loadComponent(key: string): void { + const componentObj = this.componentMap.get(key); + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentObj.component); + + componentObj.componentRef = this.contentContainer.createComponent(componentFactory); + componentObj.isLoaded = true; + } + + private setActiveTab(tabName: string): void { + this.tabList.forEach((tab) => tab.isActive = tabName === tab.id); + } + + private isComponentLoaded(key: string): boolean { + return this.componentMap.get(key).isLoaded; + } + + onClickFilter(): void { + this.onClickClose(); + } + + onClickClose(): void { + this.outClosePopup.emit(); + } + + getIconFullPath(applicationName: string): string { + return this.funcImagePath(applicationName); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/configuration-popup/index.ts b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/index.ts new file mode 100644 index 000000000000..aafa17527080 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/configuration-popup/index.ts @@ -0,0 +1,68 @@ + +import { NgModule } from '@angular/core'; +import { ClipboardModule } from 'ngx-clipboard'; + +import { SharedModule } from 'app/shared'; +import { AlarmRuleListModule } from 'app/core/components/alarm-rule-list'; +import { ApplicationListModule } from 'app/core/components/application-list'; +import { UserGroupModule } from 'app/core/components/user-group'; +import { GroupMemberModule } from 'app/core/components/group-member'; +import { PinpointUserModule } from 'app/core/components/pinpoint-user'; +import { InboundOutboundRangeSelectorModule } from 'app/core/components/inbound-outbound-range-selector'; +import { SearchPeriodModule } from 'app/core/components/search-period'; +import { TimezoneModule } from 'app/core/components/timezone/index'; +import { DateFormatModule } from 'app/core/components/date-format'; +import { DuplicationCheckModule } from 'app/core/components/duplication-check'; +import { ConfigurationPopupComponent } from './configuration-popup.component'; +import { ConfigurationPopupContainerComponent } from './configuration-popup-container.component'; +import { ConfigurationPopupGeneralContainerComponent } from './configuration-popup-general-container.component'; +import { ConfigurationPopupUsergroupComponent } from './configuration-popup-usergroup.component'; +import { ConfigurationPopupAlarmComponent } from './configuration-popup-alarm.component'; +import { ConfigurationPopupHelpContainerComponent } from './configuration-popup-help-container.component'; +import { ConfigurationPopupInstallationContainerComponent } from './configuration-popup-installation-container.component'; +import { ConfigurationPopupInstallationDownloadLinkComponent } from './configuration-popup-installation-download-link.component'; +import { ConfigurationPopupInstallationJVMArgumentInfoComponent } from './configuration-popup-installation-jvm-argument-info.component'; + +import { ConfigurationPopupInstallationDataService } from './configuration-popup-installation-data.service'; + +@NgModule({ + declarations: [ + ConfigurationPopupComponent, + ConfigurationPopupContainerComponent, + ConfigurationPopupGeneralContainerComponent, + ConfigurationPopupUsergroupComponent, + ConfigurationPopupAlarmComponent, + ConfigurationPopupHelpContainerComponent, + ConfigurationPopupInstallationContainerComponent, + ConfigurationPopupInstallationDownloadLinkComponent, + ConfigurationPopupInstallationJVMArgumentInfoComponent + ], + imports: [ + SharedModule, + ClipboardModule, + AlarmRuleListModule, + ApplicationListModule, + UserGroupModule, + GroupMemberModule, + PinpointUserModule, + InboundOutboundRangeSelectorModule, + SearchPeriodModule, + ApplicationListModule, + TimezoneModule, + DateFormatModule, + DuplicationCheckModule, + ], + exports: [], + entryComponents: [ + ConfigurationPopupContainerComponent, + ConfigurationPopupGeneralContainerComponent, + ConfigurationPopupUsergroupComponent, + ConfigurationPopupAlarmComponent, + ConfigurationPopupInstallationContainerComponent, + ConfigurationPopupHelpContainerComponent + ], + providers: [ + ConfigurationPopupInstallationDataService + ] +}) +export class ConfigurationPopupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-filtered-map-container.component.css b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-filtered-map-container.component.css new file mode 100644 index 000000000000..7a73bb795ec8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-filtered-map-container.component.css @@ -0,0 +1,8 @@ +:host { + width: 100% +} +.l-wrapper { + display: flex; + flex-flow: row wrap; + padding: 10px 10px 0px 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-filtered-map-container.component.html b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-filtered-map-container.component.html new file mode 100644 index 000000000000..08b26408d737 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-filtered-map-container.component.html @@ -0,0 +1,10 @@ +
+ +
diff --git a/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-filtered-map-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-filtered-map-container.component.ts new file mode 100644 index 000000000000..2471ee3fee75 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-filtered-map-container.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService, StoreHelperService } from 'app/shared/services'; +import { ServerMapForFilteredMapDataService } from 'app/core/components/server-map/server-map-for-filtered-map-data.service'; + +@Component({ + selector: 'pp-data-load-indicator-for-filtered-map-container', + templateUrl: './data-load-indicator-for-filtered-map-container.component.html', + styleUrls: ['./data-load-indicator-for-filtered-map-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DataLoadIndicatorForFilteredMapContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + timezone$: Observable; + dateFormat$: Observable; + rangeValue: number[]; + selectedRangeValue: number[]; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private serverMapForFilteredMapDataService: ServerMapForFilteredMapDataService + ) {} + ngOnInit() { + this.connectStore(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.END_TIME, UrlPathId.PERIOD); + } + )).subscribe((urlService: NewUrlStateNotificationService) => { + const endTime = urlService.getEndTimeToNumber(); + this.rangeValue = [urlService.getStartTimeToNumber(), endTime]; + this.selectedRangeValue = [endTime, endTime]; + this.changeDetectorRef.detectChanges(); + }); + this.serverMapForFilteredMapDataService.onServerMapData$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((serverMapAndScatterData: any) => { + this.selectedRangeValue = [ + serverMapAndScatterData['lastFetchedTimestamp'], + serverMapAndScatterData['applicationMapData']['range']['to'] + ]; + this.changeDetectorRef.detectChanges(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.dateFormat$ = this.storeHelperService.getDateFormat(this.unsubscribe, 6); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-transaction-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-transaction-list-container.component.css new file mode 100644 index 000000000000..7a73bb795ec8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-transaction-list-container.component.css @@ -0,0 +1,8 @@ +:host { + width: 100% +} +.l-wrapper { + display: flex; + flex-flow: row wrap; + padding: 10px 10px 0px 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-transaction-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-transaction-list-container.component.html new file mode 100644 index 000000000000..08b26408d737 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-transaction-list-container.component.html @@ -0,0 +1,10 @@ +
+ +
diff --git a/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-transaction-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-transaction-list-container.component.ts new file mode 100644 index 000000000000..e417b200a6c4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/data-load-indicator-for-transaction-list-container.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { takeUntil, filter, take } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService, StoreHelperService } from 'app/shared/services'; +import { TransactionMetaDataService } from 'app/core/components/transaction-table-grid/transaction-meta-data.service'; + +@Component({ + selector: 'pp-data-load-indicator-for-transaction-list-container', + templateUrl: './data-load-indicator-for-transaction-list-container.component.html', + styleUrls: ['./data-load-indicator-for-transaction-list-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DataLoadIndicatorForTransactionListContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + timezone$: Observable; + dateFormat$: Observable; + rangeValue: number[]; + selectedRangeValue: number[]; + + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private transactionMetaDataService: TransactionMetaDataService + ) {} + ngOnInit() { + this.connectStore(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.END_TIME, UrlPathId.PERIOD); + }), + take(1) + ).subscribe((urlService: NewUrlStateNotificationService) => { + const endTime = urlService.getEndTimeToNumber(); + this.rangeValue = [urlService.getStartTimeToNumber(), endTime]; + this.selectedRangeValue = [endTime, endTime]; + this.connectMetaDataService(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.dateFormat$ = this.storeHelperService.getDateFormat(this.unsubscribe, 6); + } + private connectMetaDataService(): void { + this.transactionMetaDataService.onTransactionDataRange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((range: number[]) => { + this.selectedRangeValue = range; + this.changeDetectorRef.detectChanges(); + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/index.ts b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/index.ts new file mode 100644 index 000000000000..a5e0bc81fd3d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/data-load-indicator/index.ts @@ -0,0 +1,23 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { DataLoadIndicatorForFilteredMapContainerComponent } from './data-load-indicator-for-filtered-map-container.component'; +import { DataLoadIndicatorForTransactionListContainerComponent } from './data-load-indicator-for-transaction-list-container.component'; +import { TransactionTableGridModule } from 'app/core/components/transaction-table-grid'; + +@NgModule({ + declarations: [ + DataLoadIndicatorForFilteredMapContainerComponent, + DataLoadIndicatorForTransactionListContainerComponent + ], + imports: [ + SharedModule, + TransactionTableGridModule + ], + exports: [ + DataLoadIndicatorForFilteredMapContainerComponent, + DataLoadIndicatorForTransactionListContainerComponent + ], + providers: [] +}) +export class DataLoadIndicatorModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/date-format/date-format-container.component.css b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format-container.component.css new file mode 100644 index 000000000000..7f26ddcb5503 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format-container.component.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/date-format/date-format-container.component.html b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format-container.component.html new file mode 100644 index 000000000000..30f574a0530e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format-container.component.html @@ -0,0 +1,6 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/date-format/date-format-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format-container.component.ts new file mode 100644 index 000000000000..abc9511e9064 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format-container.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; + +import { Actions } from 'app/shared/store'; +import { StoreHelperService, WebAppSettingDataService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +@Component({ + selector: 'pp-date-format-container', + templateUrl: './date-format-container.component.html', + styleUrls: ['./date-format-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DateFormatContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + timezone$: Observable; + dateFormatList: string[][]; + currentDateFormatIndex$: Observable; + constructor( + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private analyticsService: AnalyticsService, + ) {} + + ngOnInit() { + this.dateFormatList = this.webAppSettingDataService.getDateFormatList(); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.currentDateFormatIndex$ = this.storeHelperService.getDateFormatIndex(this.unsubscribe); + } + onChangeDateFormat(dateFormatIndex: number): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SET_DATE_FORMAT_IN_CONFIGURATION, this.dateFormatList[dateFormatIndex][0]); + this.webAppSettingDataService.setDateFormat(dateFormatIndex); + this.storeHelperService.dispatch(new Actions.ChangeDateFormat(dateFormatIndex)); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/date-format/date-format.component.css b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format.component.css new file mode 100644 index 000000000000..b2b8789c1cdc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format.component.css @@ -0,0 +1,26 @@ +:host { + display: block; + position: relative; + width: 100%; +} + +.fa-angle-down { + position: absolute; + top: 8px; + right: 8px; + font-size: 15px; +} + +.l-app-select { + width: 100%; + cursor: pointer; + padding: 6px 12px; + background-color: #fff; + border: 1px solid #d7dde4 !important; + border-radius: 0px; + font-size: 13px; + color: #666; + appearance: none; + -webkit-appearance: none; + outline: 0; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/date-format/date-format.component.html b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format.component.html new file mode 100644 index 000000000000..c76968771ba7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format.component.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/date-format/date-format.component.ts b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format.component.ts new file mode 100644 index 000000000000..76c9a2c821ec --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/date-format/date-format.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import * as moment from 'moment-timezone'; + +@Component({ + selector: 'pp-date-format', + templateUrl: './date-format.component.html', + styleUrls: ['./date-format.component.css'], +}) +export class DateFormatComponent implements OnInit { + @Input() timezone: string; + @Input() dateFormatList: string[][]; + @Input() currentDateFormatIndex: number; + @Output() outChangeDateFormat = new EventEmitter(); + private exampleTime = Date.now(); + constructor() {} + ngOnInit() {} + onChangeDateFormat(value: number): void { + this.outChangeDateFormat.emit(value); + } + compareFn(o1: string, o2: string): boolean { + console.log( o1, o2, o1 === o2 ); + return o1 && o2 ? o1 === o2 : false; + } + formatExample(date: string): string { + return moment(this.exampleTime).tz(this.timezone).format(date); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/date-format/index.ts b/web/src/main/webapp/v2/src/app/core/components/date-format/index.ts new file mode 100644 index 000000000000..bc082b3d2695 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/date-format/index.ts @@ -0,0 +1,21 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { DateFormatContainerComponent } from './date-format-container.component'; +import { DateFormatComponent } from './date-format.component'; + +@NgModule({ + declarations: [ + DateFormatContainerComponent, + DateFormatComponent + ], + imports: [ + SharedModule + ], + exports: [ + DateFormatContainerComponent, + ], + providers: [ + ] +}) +export class DateFormatModule {} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/agent-id-duplication-check-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/duplication-check/agent-id-duplication-check-container.component.ts new file mode 100644 index 000000000000..b18c7826f8a4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/agent-id-duplication-check-container.component.ts @@ -0,0 +1,99 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, of } from 'rxjs'; +import { map, filter, switchMap, pluck } from 'rxjs/operators'; + +import { TranslateReplaceService } from 'app/shared/services'; +import { AgentIdDuplicationCheckDataService, IAgentIdAvailable } from './agent-id-duplication-check-data.service'; +import { AgentIdDuplicationCheckInteractionService } from './agent-id-duplication-check-interaction.service'; + +@Component({ + selector: 'pp-agent-id-duplication-check-container', + templateUrl: './duplication-check-container.component.html', + styleUrls: ['./duplication-check-container.component.css'], +}) +export class AgentIdDuplicationCheckContainerComponent implements OnInit { + labelText = 'Agent ID'; + message: string; + isValueValid: boolean; + placeholder$: Observable; + + private lengthGuide: string; + private MAX_CHAR = 24; + + constructor( + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + private agentIdDuplicationCheckDataService: AgentIdDuplicationCheckDataService, + private agentIdDuplicationCheckInteractionService: AgentIdDuplicationCheckInteractionService + ) {} + + ngOnInit() { + this.initPlaceholder(); + this.initLengthGuide(); + } + + private initPlaceholder(): void { + this.placeholder$ = this.translateService.get('CONFIGURATION.INSTALLATION.AGENT_ID_PLACEHOLDER'); + } + + private initLengthGuide(): void { + this.translateService.get('CONFIGURATION.INSTALLATION.LENGTH_GUIDE').pipe( + map((lengthGuide: string) => { + return this.translateReplaceService.replace(lengthGuide, this.MAX_CHAR.toString()); + }) + ).subscribe((lengthGuide: string) => { + this.lengthGuide = lengthGuide; + }); + } + + onCheckValue(inputValue: string): void { + of(inputValue).pipe( + filter((value: string) => { + return this.isLengthValid(value.length) ? true : (this.onCheckFail(this.lengthGuide), false); + }), + switchMap((value) => { + return this.fetchResponse(value).pipe( + map((res: IAgentIdAvailable) => { + return { value, res }; + }) + ); + }), + filter(({ res }) => { + return this.isValueAvailable(res.code) ? true : (this.onCheckFail(res.message), false); + }), + pluck('value') + ).subscribe((value: string) => { + this.onCheckSuccess(value, ''); + }, (errorMessage: string) => { + this.onCheckFail(errorMessage); + }); + } + + private isLengthValid(length: number): boolean { + return !(length === 0 || length > this.MAX_CHAR); + } + + private fetchResponse(value: string): Observable { + return this.agentIdDuplicationCheckDataService.getResponseWithParams(value); + } + + private isValueAvailable(code: number): boolean { + return code === 0; + } + + private onCheckFail(message: string): void { + this.message = message; + this.isValueValid = false; + } + + private onCheckSuccess(value: string, message: string): void { + this.message = message; + this.isValueValid = true; + this.notifyCheckSuccess(value); + } + + private notifyCheckSuccess(value: string): void { + this.agentIdDuplicationCheckInteractionService.notifyCheckSuccess(value); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/agent-id-duplication-check-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/duplication-check/agent-id-duplication-check-data.service.ts new file mode 100644 index 000000000000..d9abbfe6bd1a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/agent-id-duplication-check-data.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +export interface IAgentIdAvailable { + code: number; + message: string; +} + +@Injectable() +export class AgentIdDuplicationCheckDataService { + private requestURL = 'isAvailableAgentId.pinpoint'; + + constructor( + private http: HttpClient, + ) {} + + getResponseWithParams(value: string): Observable { + return this.http.get(this.requestURL, this.makeParams({agentId: value})).pipe( + catchError(this.handleError) + ); + } + private makeParams(paramObj: object): object { + return { + params: { ...paramObj } + }; + } + private handleError(error: HttpErrorResponse) { + return throwError(error.statusText); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/agent-id-duplication-check-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/duplication-check/agent-id-duplication-check-interaction.service.ts new file mode 100644 index 000000000000..fe12007a09b1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/agent-id-duplication-check-interaction.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable() +export class AgentIdDuplicationCheckInteractionService { + private outCheckSuccess = new BehaviorSubject(''); + + onCheckSuccess$ = this.outCheckSuccess.asObservable(); + + constructor() {} + + notifyCheckSuccess(value: string): void { + this.outCheckSuccess.next(value); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/application-name-duplication-check-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/duplication-check/application-name-duplication-check-container.component.ts new file mode 100644 index 000000000000..fa8a48b728d0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/application-name-duplication-check-container.component.ts @@ -0,0 +1,99 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, of } from 'rxjs'; +import { map, filter, switchMap, pluck } from 'rxjs/operators'; + +import { TranslateReplaceService } from 'app/shared/services'; +import { ApplicationNameDuplicationCheckDataService, IApplicationAvailable } from './application-name-duplication-check-data.service'; +import { ApplicationNameDuplicationCheckInteractionService } from './application-name-duplication-check-interaction.service'; + +@Component({ + selector: 'pp-application-name-duplication-check-container', + templateUrl: './duplication-check-container.component.html', + styleUrls: ['./duplication-check-container.component.css'], +}) +export class ApplicationNameDuplicationCheckContainerComponent implements OnInit { + labelText = 'Application Name'; + message: string; + isValueValid: boolean; + placeholder$: Observable; + + private lengthGuide: string; + private MAX_CHAR = 24; + + constructor( + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + private applicationNameDuplicationCheckDataService: ApplicationNameDuplicationCheckDataService, + private applicationNameDuplicationCheckInteractionService: ApplicationNameDuplicationCheckInteractionService + ) {} + + ngOnInit() { + this.initPlaceholder(); + this.initLengthGuide(); + } + + private initPlaceholder(): void { + this.placeholder$ = this.translateService.get('CONFIGURATION.INSTALLATION.APPLICATION_NAME_PLACEHOLDER'); + } + + private initLengthGuide(): void { + this.translateService.get('CONFIGURATION.INSTALLATION.LENGTH_GUIDE').pipe( + map((lengthGuide: string) => { + return this.translateReplaceService.replace(lengthGuide, this.MAX_CHAR.toString()); + }) + ).subscribe((lengthGuide: string) => { + this.lengthGuide = lengthGuide; + }); + } + + onCheckValue(inputValue: string): void { + of(inputValue).pipe( + filter((value: string) => { + return this.isLengthValid(value.length) ? true : (this.onCheckFail(this.lengthGuide), false); + }), + switchMap((value) => { + return this.fetchResponse(value).pipe( + map((res: IApplicationAvailable) => { + return { value, res }; + }) + ); + }), + filter(({ res }) => { + return this.isValueAvailable(res.code) ? true : (this.onCheckFail(res.message), false); + }), + pluck('value') + ).subscribe((value: string) => { + this.onCheckSuccess(value, ''); + }, (errorMessage: string) => { + this.onCheckFail(errorMessage); + }); + } + + private isLengthValid(length: number): boolean { + return !(length === 0 || length > this.MAX_CHAR); + } + + private fetchResponse(value: string): Observable { + return this.applicationNameDuplicationCheckDataService.getResponseWithParams(value); + } + + private isValueAvailable(code: number): boolean { + return code === 0; + } + + private onCheckFail(message: string): void { + this.message = message; + this.isValueValid = false; + } + + private onCheckSuccess(value: string, message: string): void { + this.message = message; + this.isValueValid = true; + this.notifyCheckSuccess(value); + } + + private notifyCheckSuccess(value: string): void { + this.applicationNameDuplicationCheckInteractionService.notifyCheckSuccess(value); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/application-name-duplication-check-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/duplication-check/application-name-duplication-check-data.service.ts new file mode 100644 index 000000000000..169fed72cfdd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/application-name-duplication-check-data.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +export interface IApplicationAvailable { + code: number; + message: string; +} + +@Injectable() +export class ApplicationNameDuplicationCheckDataService { + private requestURL = 'isAvailableApplicationName.pinpoint'; + + constructor( + private http: HttpClient, + ) {} + getResponseWithParams(value: string): Observable { + return this.http.get(this.requestURL, this.makeParams({applicationName: value})).pipe( + catchError(this.handleError) + ); + } + private makeParams(paramObj: object): object { + return { + params: { ...paramObj } + }; + } + private handleError(error: HttpErrorResponse | string) { + return throwError(error['statusText'] || error); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/application-name-duplication-check-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/duplication-check/application-name-duplication-check-interaction.service.ts new file mode 100644 index 000000000000..700216514b3b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/application-name-duplication-check-interaction.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable() +export class ApplicationNameDuplicationCheckInteractionService { + private outCheckSuccess = new BehaviorSubject(''); + + onCheckSuccess$ = this.outCheckSuccess.asObservable(); + + constructor() {} + + notifyCheckSuccess(value: string): void { + this.outCheckSuccess.next(value); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check-container.component.css b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check-container.component.css new file mode 100644 index 000000000000..7f26ddcb5503 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check-container.component.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check-container.component.html b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check-container.component.html new file mode 100644 index 000000000000..0d7997f056c7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check-container.component.html @@ -0,0 +1,7 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check.component.css b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check.component.css new file mode 100644 index 000000000000..7e7a808b7d35 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check.component.css @@ -0,0 +1,86 @@ +:host { + display: block; + margin-bottom: 15px; +} +label { + font-size: 14px; + font-weight: 600; + width: 15%; + display: inline-block; + text-align: right; + margin-right: 10px; +} +.l-input-wrapper { + width: 70%; + display: inline-block; + position: relative; +} + +input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 9px 14px; + font-size: 13px; + width: 100%; + display: inline-block; + outline: none; +} + +input:focus { + border-color: #4D90FE; + box-shadow: 0 0 2px #4D90FE; +} + +input.l-success-input { + border: 1px solid #3c763d; +} + +input.l-success-input:focus { + box-shadow: 0 0 2px #3c763d; +} + +input.l-fail-input { + border: 1px solid #a94442; +} + +input.l-fail-input:focus { + box-shadow: 0 0 2px #a94442; +} + +.l-check-status-icon { + position: absolute; + right: 10px; + top: 9px; + font-size: 20px; +} + +.fa-check { + color: #3c763d; +} + +.fa-times { + color: #a94442; +} + +.l-check-button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 9px; + font-size: 13px; + width: 10%; + margin-left: 10px; +} + +.l-check-button:hover { + background-color: #e5e8f0; +} + +.l-check-button:focus { + outline: 0; +} + +.l-message { + text-align: center; + font-size: 14px; + margin-top: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check.component.html b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check.component.html new file mode 100644 index 000000000000..f8bf205ceaeb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check.component.html @@ -0,0 +1,7 @@ + +
+ + +
+ +

{{message}}

diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check.component.ts b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check.component.ts new file mode 100644 index 000000000000..2d48770a137b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/duplication-check.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-duplication-check', + templateUrl: './duplication-check.component.html', + styleUrls: ['./duplication-check.component.css'], +}) +export class DuplicationCheckComponent implements OnInit { + @Input() labelText: string; + @Input() placeholder: string; + @Input() message: string; + @Input() isValueValid: boolean; + @Output() outCheckValue = new EventEmitter(); + + id = Math.random().toString(36).substr(2, 5); + + constructor() {} + ngOnInit() {} + + emitValue(value: string): void { + this.outCheckValue.emit(value); + } + + getInputStyleClass(): object { + return { + 'l-success-input': this.isValueValid, + 'l-fail-input': this.isValueValid === false + }; + } + + getSpanStyleClass(): object { + return { + 'fa-check': this.isValueValid, + 'fa-times': this.isValueValid === false + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/duplication-check/index.ts b/web/src/main/webapp/v2/src/app/core/components/duplication-check/index.ts new file mode 100644 index 000000000000..0b72e1219e36 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/duplication-check/index.ts @@ -0,0 +1,32 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { AgentIdDuplicationCheckContainerComponent } from './agent-id-duplication-check-container.component'; +import { ApplicationNameDuplicationCheckContainerComponent } from './application-name-duplication-check-container.component'; +import { DuplicationCheckComponent } from './duplication-check.component'; +import { ApplicationNameDuplicationCheckInteractionService } from './application-name-duplication-check-interaction.service'; +import { AgentIdDuplicationCheckInteractionService } from './agent-id-duplication-check-interaction.service'; +import { ApplicationNameDuplicationCheckDataService } from './application-name-duplication-check-data.service'; +import { AgentIdDuplicationCheckDataService } from './agent-id-duplication-check-data.service'; + +@NgModule({ + declarations: [ + AgentIdDuplicationCheckContainerComponent, + ApplicationNameDuplicationCheckContainerComponent, + DuplicationCheckComponent + ], + imports: [ + SharedModule + ], + exports: [ + AgentIdDuplicationCheckContainerComponent, + ApplicationNameDuplicationCheckContainerComponent + ], + providers: [ + ApplicationNameDuplicationCheckInteractionService, + AgentIdDuplicationCheckInteractionService, + ApplicationNameDuplicationCheckDataService, + AgentIdDuplicationCheckDataService + ] +}) +export class DuplicationCheckModule {} diff --git a/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/empty-inspector-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/empty-inspector-contents-container.component.css new file mode 100644 index 000000000000..472e282fbe48 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/empty-inspector-contents-container.component.css @@ -0,0 +1,12 @@ +:host { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} +.guide-text-wrapper { + background-color: #fff; + padding: 50px; + border-radius: 30px; + border: 1px solid #e8e5f0; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/empty-inspector-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/empty-inspector-contents-container.component.html new file mode 100644 index 000000000000..9f7305c734f5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/empty-inspector-contents-container.component.html @@ -0,0 +1,3 @@ +
+

{{guideText$ | async}}

+
diff --git a/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/empty-inspector-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/empty-inspector-contents-container.component.ts new file mode 100644 index 000000000000..d338c98c1e58 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/empty-inspector-contents-container.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; + +import { Observable } from 'rxjs'; + +@Component({ + selector: 'pp-empty-inspector-contents-container', + templateUrl: './empty-inspector-contents-container.component.html', + styleUrls: ['./empty-inspector-contents-container.component.css'] +}) +export class EmptyInspectorContentsContainerComponent implements OnInit { + guideText$: Observable; + + constructor( + private translateService: TranslateService, + ) { } + + ngOnInit() { + this.guideText$ = this.translateService.get('MAIN.SELECT_YOUR_APP'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/index.ts new file mode 100644 index 000000000000..23dff1791517 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/empty-inspector-contents/index.ts @@ -0,0 +1,18 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { EmptyInspectorContentsContainerComponent } from './empty-inspector-contents-container.component'; + +@NgModule({ + declarations: [ + EmptyInspectorContentsContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + EmptyInspectorContentsContainerComponent + ], + providers: [] +}) +export class EmptyInspectorContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-information/filter-information-container.component.css b/web/src/main/webapp/v2/src/app/core/components/filter-information/filter-information-container.component.css new file mode 100644 index 000000000000..470dcf63dbff --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-information/filter-information-container.component.css @@ -0,0 +1,42 @@ +.l-filter-info { + padding: 16px 25px; + border-bottom: 1px solid #EAEEF4; + background-color: #F9FAFC; +} +.l-title { + font-size: 16px; + font-weight: 600; + margin-bottom: 23px; +} +.l-info-list { + width: 100%; + display: grid; + font-size: 14px; + grid-template-rows: auto; + grid-template-columns: 35% 65%; +} +.l-info-list div { + padding-top: 2px; + padding-bottom: 2px; +} +.l-info-list div:nth-child(odd) { + font-weight: 600; +} +.l-info-list div:nth-child(even) { + padding-left: 6px; + text-align: center; +} +.l-agent { + display: grid; + grid-template-rows: auto; + grid-template-columns: 45% 10% 45%; +} +.l-agent i { + padding-top: 3px; +} +.l-agent span { + display : block; + overflow : hidden; + white-space : nowrap; + text-overflow : ellipsis; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-information/filter-information-container.component.html b/web/src/main/webapp/v2/src/app/core/components/filter-information/filter-information-container.component.html new file mode 100644 index 000000000000..f459bd17669f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-information/filter-information-container.component.html @@ -0,0 +1,17 @@ +
+

Filter Information

+
+
Agent
+
+
+ {{getAgentFrom()}} {{getAgentTo()}} +
+
+
Url Pattern
+
{{getUrlPattern()}}
+
Response Time
+
{{getResponseTimeFrom() | number}}ms ~ {{getResponseTimeTo() | number}}ms
+
Transaction Result
+
{{getTransactionResult()}}
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-information/filter-information-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/filter-information/filter-information-container.component.ts new file mode 100644 index 000000000000..bc15c5d3f638 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-information/filter-information-container.component.ts @@ -0,0 +1,91 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { NewUrlStateNotificationService, StoreHelperService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { Filter } from 'app/core/models'; + +@Component({ + selector: 'pp-filter-information-container', + templateUrl: './filter-information-container.component.html', + styleUrls: ['./filter-information-container.component.css'] +}) +export class FilterInformationContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private serverMapData: any; + private selectedTarget: ISelectedTarget; + filterInfo: Filter[]; + filterIndexOfCurrentLink: number; + constructor( + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.FILTER); + }) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.filterInfo = Filter.instanceFromString(urlService.getPathValue(UrlPathId.FILTER)); + }); + this.connectStore(); + } + private connectStore(): void { + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: any) => { + this.serverMapData = serverMapData; + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.filterIndexOfCurrentLink = -1; + this.selectedTarget = target; + }); + } + showFilterInfo(): boolean { + if (this.selectedTarget) { + if (this.selectedTarget.isLink === true && this.selectedTarget.isMerged === false) { + const link = this.serverMapData.getLinkData(this.selectedTarget.link[0]); + if (this.isFilterLink(link)) { + return true; + } + } + } + return false; + } + isFilterLink(link: any): boolean { + for (let i = 0 ; i < this.filterInfo.length ; i++) { + const f = this.filterInfo[i]; + if ((f.fromApplication + '^' + f.fromServiceType) === link.from && (f.toApplication + '^' + f.toServiceType) === link.to) { + this.filterIndexOfCurrentLink = i; + return true; + } + } + return false; + } + getAgentFrom(): string { + return this.filterInfo[this.filterIndexOfCurrentLink].fromAgentName || 'All'; + } + getAgentTo(): string { + return this.filterInfo[this.filterIndexOfCurrentLink].toAgentName || 'All'; + } + getUrlPattern(): string { + return this.filterInfo[this.filterIndexOfCurrentLink].urlPattern || 'none'; + } + getResponseTimeFrom(): number { + return this.filterInfo[this.filterIndexOfCurrentLink].responseFrom || 0; + } + getResponseTimeTo(): number { + return this.filterInfo[this.filterIndexOfCurrentLink].responseTo || 30000; + } + getTransactionResult(): string { + return this.filterInfo[this.filterIndexOfCurrentLink].getTransactionResultStr(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-information/index.ts b/web/src/main/webapp/v2/src/app/core/components/filter-information/index.ts new file mode 100644 index 000000000000..c5f84d9e4e27 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-information/index.ts @@ -0,0 +1 @@ +export * from './filter-information-container.component'; diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component.css b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component.css new file mode 100644 index 000000000000..3c6a97654ec4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component.css @@ -0,0 +1,18 @@ +:host { + display: block; + background-color: transparent; + width: 100%; + height: 100%; +} + +:host::before { + content: ''; + display: block; + height: 100%; + width: 100%; + background: #000; + opacity: 0.6; + position: absolute; + left: 0; + top: 0; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component.html b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component.html new file mode 100644 index 000000000000..eaeab1e2474b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component.html @@ -0,0 +1,7 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component.ts new file mode 100644 index 000000000000..926ffae8861b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component.ts @@ -0,0 +1,96 @@ +import { Component, OnInit, Input, Output, EventEmitter, AfterViewInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; + +import { + WindowRefService, + WebAppSettingDataService, + UrlRouteManagerService, + NewUrlStateNotificationService, + DynamicPopup +} from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { Filter } from 'app/core/models'; + +@Component({ + selector: 'pp-filter-transaction-wizard-popup-container', + templateUrl: './filter-transaction-wizard-popup-container.component.html', + styleUrls: ['./filter-transaction-wizard-popup-container.component.css'] +}) +export class FilterTransactionWizardPopupContainerComponent implements OnInit, AfterViewInit, DynamicPopup { + @Input() data: any; + @Output() outCreated = new EventEmitter(); + @Output() outClose = new EventEmitter(); + + filterInfo$: Observable; + funcImagePath: Function; + + constructor( + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private windowRefService: WindowRefService, + ) {} + + ngOnInit() { + this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc(); + this.filterInfo$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe( + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.FILTER); + }), + map((urlService) => { + const filterInfo = Filter.instanceFromString(urlService.getPathValue(UrlPathId.FILTER)); + const { from, to } = this.data; + + return filterInfo.find((f: Filter) => { + return (`${f.fromApplication}^${f.fromServiceType}` === from) && (`${f.toApplication}^${f.toServiceType}` === to); + }); + }), + filter((f: Filter) => !!f) + ); + } + + ngAfterViewInit() { + this.outCreated.emit({ coordX: 0, coordY: 0 }); + } + + openFilterMapPage(param: any): void { + const f = new Filter( + param.from.applicationName, + param.from.serviceType, + param.to.applicationName, + param.to.serviceType, + param.transactionResult + ); + f.setResponseFrom(param.responseFrom); + f.setResponseTo(param.responseTo); + if (param.urlPattern) { + f.setUrlPattern(this.windowRefService.nativeWindow.btoa(param.urlPattern)); + } + if (param.from.agent) { + f.setFromAgentName(param.from.agent); + } + if (param.to.agent) { + f.setToAgentName(param.to.agent); + } + const isBothWas = param.from.isWas && param.to.isWas; + this.urlRouteManagerService.openPage( + this.urlRouteManagerService.makeFilterMapUrl({ + applicationName: param.from.applicationName, + serviceType: param.from.serviceType, + periodStr: this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithAddedWords(), + timeStr: this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + filterStr: this.newUrlStateNotificationService.hasValue(UrlPathId.FILTER) ? this.newUrlStateNotificationService.getPathValue(UrlPathId.FILTER) : '', + hintStr: this.newUrlStateNotificationService.hasValue(UrlPathId.HINT) ? this.newUrlStateNotificationService.getPathValue(UrlPathId.HINT) : '', + addedFilter: f, + addedHint: (isBothWas ? { + [param.to.applicationName]: param.filterTargetRpcList + } : null) + }) + ); + } + + onClosePopup(): void { + this.outClose.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup.component.css b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup.component.css new file mode 100644 index 000000000000..1121370c7b6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup.component.css @@ -0,0 +1,241 @@ +:host { + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 752px; + max-width: 1000px; + height: 730px; + background-color: #fff; + border: 1px solid #e5e8f0; + box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.75); +} +.l-title-group { + padding: 17px 18px; + height: auto; + background-color: #FFF; + border-bottom: 1px solid #E5E8F0; + position: relative; + font-size: 13px; + font-weight: 600; + color: #333; + display: flex; + align-items: center; + justify-content: space-between; +} +.l-title-group dt { + font-weight: normal; + font-size: 20px; + color: #4A8FD2; +} +.l-popup-close { + font-size: 30px; +} +.l-contents-group { + padding: 17px 18px 0px 18px; +} +.l-table { + width: 100%; + display: grid; + position: relative; + font-size: 13px; + grid-template-columns: 140px 286px 286px; + grid-template-rows: 46px 140px 21px 195px 106px 64px; +} +.l-table > div { + display: flex; + padding: 10px 15px; + border-bottom: 1px solid #E5E8F0; + border-right: 1px solid #E5E8F0; +} +.l-table .first-row { + border-top: 1px solid #E5E8F0; +} +.l-table .first-row img { + margin-right: 8px; +} +.l-table .first-row span { + float: left; + margin-left: -25px; + margin-right: 10px; + font-size: 18px; + color: #4B99E3; +} +.l-table .col1 { + padding: 0; + border-left: 1px solid #E5E8F0; +} +.row-col-1-1 { + grid-column: 1 / 2; + /* autoprefixer: off */ + grid-row: 1 / 2; +} +.row-col-1-2 { + align-items: center; + grid-column: 2 / 3; + grid-row: 1 / 2; +} +.row-col-1-3 { + align-items: center; + grid-column: 3 / 4; + grid-row: 1 / 2; +} +.row-col-2-1 { + grid-column: 1; + grid-row: 2; +} +.row-col-2-2 { + overflow-y: auto; + padding: 0 !important; + grid-column: 2; + grid-row: 2; +} +.row-col-2-3 { + overflow-y: auto; + padding: 0 !important; + grid-column: 3; + grid-row: 2; +} +.row-col-3 { + grid-column: 1 / 4; + grid-row: 3; +} +.row-col-4-1 { + grid-column: 1; + grid-row: 4; +} +.row-col-4-2 { + flex-flow: row wrap; + grid-column: 2 / 4; + grid-row: 4; +} +.row-col-5-1 { + grid-column: 1; + grid-row: 5; +} +.row-col-5-2 { + grid-column: 2 / 4; + grid-row: 5; +} +.row-col-6-1 { + grid-column: 1; + grid-row: 6; +} +.row-col-6-2 { + grid-column: 2 / 4; + grid-row: 6; +} +.col1 { + justify-content: center; + align-items: center; + font-size: 13px; + background-color: #f6f8fb; +} +.ellipsis { + display : block; + overflow : hidden; + white-space : nowrap; + text-overflow : ellipsis; +} +.l-application-list { + width: 100%; + cursor: pointer; + margin: 0; + padding: 0; + list-style: none; +} +.l-application-list li { + padding: 7px 15px; + font-size: 13px; +} +.l-application-list li.active { + color: #fff; + font-weight: 600; + background-color: #4B99E3; +} +.l-application-list li:hover { + color: #000; + background-color: #e4f3eb; +} +.l-widget-group { + padding: 0; + width: 100%; +} +.l-widget-group input { + border: 1px solid #469ae4; + font-size: 13px; + color: #b3b3b4; + padding: 6px 11px; + width: 100%; +} +.l-pattern-guide { + margin-top: 6px; + border: 1px solid #e6e8ec; + background-color: #F6F8FB; +} +.l-pattern-guide tr td:first-child { + padding:4px; + font-size: 11px; + text-align: center; +} +.l-pattern-guide tr td:last-child { + padding:4px; + font-size: 11px; +} +.l-pattern-guide td { + border: none; +} +.l-pattern-guide tr:last-child { + border-top: 1px solid #e6e8ec; +} +.l-response-time { + display: block !important; + padding: 30px 30px 10px 30px !important; + font-size: 11px !important; +} +.l-response-time div.noUi-tooltip { + color: #4B99E3; +} +.l-response-time div.noUi-connect { + background-color: #3dcfa8 !important; +} +.l-range-info { + text-align: center !important; +} +.l-range-info span:first-child { + float: left; +} +.l-range-info .l-range-text { + color: #4B99E3; +} +.l-range-info span:last-child { + float: right; + margin-right: -26px; +} +.l-transaction-result { + width: 100%; +} +.l-transaction-result button { + float: left; + width: 33.3%; + border: 1px solid #E6E8EC; + padding: 12px; + font-size: 13px; + font-weight: 600; +} +.l-transaction-result button.active { + color: #fff; + background: #4a8fd2; + border-color: transparent; +} + +.l-buttons { + text-align: right; + padding-top: 14px; +} +.l-buttons button { + margin-right: 15px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup.component.html b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup.component.html new file mode 100644 index 000000000000..35d3fd338f34 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup.component.html @@ -0,0 +1,95 @@ +
+
Filter Wizard
+ +
+
+
+
Path
+
+ +
{{link?.sourceInfo.applicationName}}
+
+
+ + +
{{link?.targetInfo.applicationName}}
+
+
Agent
+
+
    +
  • ALL
  • +
  • {{agent}}
  • +
+
+
+
    +
  • ALL
  • +
  • {{agent}}
  • +
+
+
+
URL Pattern
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
*Matchers zero or more characters
?Matchers exactly one characters
**Matchers zero or more directories
Example +
/pinpoint/**/*.html
+
/pinpoint/??.html
+
/pinpoint/**/??.html
+
+
+
+
Response Time
+
+ +
+ {{responseTimeMin.toLocaleString()}} ms + Range {{(responseTimeRange[1] - responseTimeRange[0]).toLocaleString()}} ms + {{responseTimeMax.toLocaleString()}}+ ms +
+
+
Transaction Result
+
+
+ +
+
+
+
+
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup.component.ts b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup.component.ts new file mode 100644 index 000000000000..3bcde01806d8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup.component.ts @@ -0,0 +1,120 @@ +import { Component, OnInit, Input, Output, EventEmitter, HostBinding } from '@angular/core'; +import { NouiFormatter } from 'ng2-nouislider'; + +import { Filter } from 'app/core/models/'; + +export class TimeFormatter implements NouiFormatter { + to(value: number): string { + return value.toLocaleString() + (value === 30000 ? '+' : '') + ' ms'; + } + + from(value: string): number { + return parseInt(value.replace(/(.) ms/, '$1').replace(/,/, ''), 10); + } +} +enum RESULT_TYPE { + SUCCESS_AND_FAIL, + SUCCESS_ONLY, + FAIL_ONLY +} +const AGENT_ALL = 'All'; + +@Component({ + selector: 'pp-filter-transaction-wizard-popup', + templateUrl: './filter-transaction-wizard-popup.component.html', + styleUrls: ['./filter-transaction-wizard-popup.component.css'] +}) +export class FilterTransactionWizardPopupComponent implements OnInit { + @Input() filterInfo: Filter; + @Input() link: ILinkInfo; + @Input() funcImagePath: Function; + @Output() outRequestFilterOpen = new EventEmitter(); + @Output() outClosePopup = new EventEmitter(); + @HostBinding('class.font-opensans') fontFamily = true; + + resultType: string[] = ['Success + Failed', 'Success Only', 'Failed Only']; + selectedResultType: RESULT_TYPE = RESULT_TYPE.SUCCESS_AND_FAIL; + selectedFromAgent = AGENT_ALL; + selectedToAgent = AGENT_ALL; + urlPattern = ''; + responseTimeMin = 0; + responseTimeMax = 30000; + responseTimeRange = [0, 30000]; + + constructor() {} + ngOnInit() { + this.resetValue(); + } + resetValue() { + if (this.filterInfo) { + this.selectedFromAgent = this.filterInfo.fromAgentName || AGENT_ALL; + this.selectedToAgent = this.filterInfo.toAgentName || AGENT_ALL; + this.responseTimeRange = [this.filterInfo.responseFrom, this.filterInfo.responseTo]; + this.urlPattern = this.filterInfo.urlPattern || ''; + if (this.filterInfo.transactionResult === true) { + this.selectedResultType = RESULT_TYPE.SUCCESS_ONLY; + } else if (this.filterInfo.transactionResult === false) { + this.selectedResultType = RESULT_TYPE.FAIL_ONLY; + } else { + this.selectedResultType = RESULT_TYPE.SUCCESS_AND_FAIL; + } + } + } + onClickFilter(): void { + this.outRequestFilterOpen.emit({ + filterApplicationName: this.link.filterApplicationName, + filterApplicationServiceTypeName: this.link.filterApplicationServiceTypeName, + from: { + applicationName: this.link.sourceInfo.applicationName, + serviceType: this.link.sourceInfo.serviceType, + agent: this.selectedFromAgent === AGENT_ALL ? null : this.selectedFromAgent, + isWas: this.link.sourceInfo.isWas + }, + to: { + applicationName: this.link.targetInfo.applicationName, + serviceType: this.link.targetInfo.serviceType, + agent: this.selectedToAgent === AGENT_ALL ? null : this.selectedToAgent, + isWas: this.link.targetInfo.isWas + }, + urlPattern: this.urlPattern, + responseFrom: this.responseTimeRange[0], + responseTo: this.responseTimeRange[1], + transactionResult: this.selectedResultType === 0 ? null : this.selectedResultType === 1 ? true : false, + filterTargetRpcList : this.link.sourceInfo.isWas && this.link.targetInfo.isWas ? this.link.filterTargetRpcList : [] + }); + this.onClickClose(); + } + onClickClose(): void { + // this.selectedResultType = RESULT_TYPE.SUCCESS_AND_FAIL; + // this.selectedFromAgent = AGENT_ALL; + // this.selectedToAgent = AGENT_ALL; + // this.responseTimeRange = [this.responseTimeMin, this.responseTimeMax]; + // this.urlPattern = ''; + + this.outClosePopup.emit(); + } + onSelectResultType(type: number): void { + this.selectedResultType = type; + } + onSelectFromAgent(agent: string): void { + this.selectedFromAgent = agent; + } + onSelectToAgent(agent: string): void { + this.selectedToAgent = agent; + } + getIconFullPath(applicationName: string): string { + return this.funcImagePath(applicationName); + } + isSelectedResultType(type: number): boolean { + return this.selectedResultType === type; + } + isSelectedFromAgent(agent: string): boolean { + return this.selectedFromAgent === agent; + } + isSelectedToAgent(agent: string): boolean { + return this.selectedToAgent === agent; + } + getTimeFormatter(): NouiFormatter { + return new TimeFormatter(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/index.ts b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/index.ts new file mode 100644 index 000000000000..5c87f10ca34a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filter-transaction-wizard-popup/index.ts @@ -0,0 +1,24 @@ + +import { NgModule } from '@angular/core'; +import { NouisliderModule } from 'ng2-nouislider'; + +import { SharedModule } from 'app/shared'; +import { FilterTransactionWizardPopupComponent } from './filter-transaction-wizard-popup.component'; +import { FilterTransactionWizardPopupContainerComponent } from './filter-transaction-wizard-popup-container.component'; + +@NgModule({ + declarations: [ + FilterTransactionWizardPopupComponent, + FilterTransactionWizardPopupContainerComponent + ], + imports: [ + NouisliderModule, + SharedModule + ], + exports: [], + entryComponents: [ + FilterTransactionWizardPopupContainerComponent + ], + providers: [] +}) +export class FilterTransactionWizardPopupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/filtered-map-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/filtered-map-contents-container.component.css new file mode 100644 index 000000000000..f47e448eed3b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/filtered-map-contents-container.component.css @@ -0,0 +1,17 @@ +.l-main-contents-top { + display: flex; + flex-flow: row wrap; + margin: 11px 0 0 25px; + align-items: center; + top: 0; + right: 0; + z-index: 8; + padding:0 10px; + font-size:13px; + justify-content: flex-end; + position:absolute; +} +.fas { + color:#a8acb5; + font-size:18px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/filtered-map-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/filtered-map-contents-container.component.html new file mode 100644 index 000000000000..4824dcf6a569 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/filtered-map-contents-container.component.html @@ -0,0 +1,6 @@ +
+ + + +
+ diff --git a/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/filtered-map-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/filtered-map-contents-container.component.ts new file mode 100644 index 000000000000..75a1b8d7061e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/filtered-map-contents-container.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit } from '@angular/core'; + +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; + +@Component({ + selector: 'pp-filtered-map-contents-container', + templateUrl: './filtered-map-contents-container.component.html', + styleUrls: ['./filtered-map-contents-container.component.css'] +}) +export class FilteredMapContentsContainerComponent implements OnInit { + constructor( + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() {} + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.SERVER_MAP); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.SERVER_MAP, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/index.ts new file mode 100644 index 000000000000..48de3ba8a360 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/filtered-map-contents/index.ts @@ -0,0 +1,27 @@ + +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { FixedPeriodMoverModule } from 'app/core/components/fixed-period-mover'; +import { ServerMapSearchResultViewerModule } from 'app/core/components/server-map-search-result-viewer'; +import { ServerMapModule } from 'app/core/components/server-map'; +import { FilteredMapContentsContainerComponent } from './filtered-map-contents-container.component'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + FilteredMapContentsContainerComponent + ], + imports: [ + SharedModule, + FixedPeriodMoverModule, + ServerMapSearchResultViewerModule, + ServerMapModule, + HelpViewerPopupModule + ], + exports: [ + FilteredMapContentsContainerComponent + ], + providers: [] +}) +export class FilteredMapContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover-container.component.css b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover-container.component.html b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover-container.component.html new file mode 100644 index 000000000000..bf42be50dcab --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover-container.component.html @@ -0,0 +1,8 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover-container.component.ts new file mode 100644 index 000000000000..53066e20914f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover-container.component.ts @@ -0,0 +1,69 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + StoreHelperService, + NewUrlStateNotificationService, + UrlRouteManagerService, + AnalyticsService, + TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { Period, EndTime } from 'app/core/models'; + +@Component({ + selector: 'pp-fixed-period-mover-container', + templateUrl: './fixed-period-mover-container.component.html', + styleUrls: ['./fixed-period-mover-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FixedPeriodMoverContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + hiddenComponent = true; + period: Period; + endTime: EndTime; + timezone$: Observable; + dateFormat$: Observable; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private analyticsService: AnalyticsService, + private storeHelperService: StoreHelperService + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.PERIOD, UrlPathId.END_TIME)) { + this.period = urlService.getPathValue(UrlPathId.PERIOD); + this.endTime = urlService.getPathValue(UrlPathId.END_TIME); + this.hiddenComponent = false; + } else { + this.hiddenComponent = true; + } + this.changeDetectorRef.markForCheck(); + }); + this.connectStore(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(); + this.dateFormat$ = this.storeHelperService.getDateFormat(this.unsubscribe, 1); + } + onMovePeriod(moveTime: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_FIXED_PERIOD_MOVE_BUTTON); + this.urlRouteManagerService.moveOnPage({ + url: [ + this.newUrlStateNotificationService.getStartPath(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.period.getValueWithTime(), + moveTime + ] + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover.component.css b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover.component.css new file mode 100644 index 000000000000..efb6182ac04d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover.component.css @@ -0,0 +1,17 @@ +.l-time-select { + color:#8a939e; + background-color:rgba(255, 255, 255, 0.6); + padding:7px; + margin-right: 15px; +} +.l-time-select span { + font-size:13px; + font-weight:600; + margin-right: 10px; +} +.l-time-select .fab { + font-size:15px; +} +.l-time-select button:first-child { + margin-right: 4px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover.component.html b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover.component.html new file mode 100644 index 000000000000..a938aed7d3b2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover.component.html @@ -0,0 +1,5 @@ +
+ {{getStartTime()}} ~ {{getEndTime()}} + + +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover.component.ts b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover.component.ts new file mode 100644 index 000000000000..c47d14187e79 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/fixed-period-mover.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import * as moment from 'moment-timezone'; +import { Period } from 'app/core/models/period'; +import { EndTime } from 'app/core/models/end-time'; + +@Component({ + selector: 'pp-fixed-period-mover', + templateUrl: './fixed-period-mover.component.html', + styleUrls: ['./fixed-period-mover.component.css'] +}) +export class FixedPeriodMoverComponent implements OnInit { + + @Input() hiddenComponent: boolean; + @Input() period: Period; + @Input() endTime: EndTime; + @Input() timezone: string; + @Input() dateFormat: string; + @Output() outMove: EventEmitter = new EventEmitter(); + constructor() { } + + ngOnInit() { } + getStartTime(): string { + if (this.endTime) { + return moment(this.endTime.calcuStartTime(this.period.getValue()).getMilliSecond()).tz(this.timezone).format(this.dateFormat); + } else { + return ''; + } + } + getEndTime(): string { + if (this.endTime) { + return moment(this.endTime.getMilliSecond()).tz(this.timezone).format(this.dateFormat); + } else { + return ''; + } + } + onMovePrev(): void { + this.outMove.emit(this.endTime.calcuStartTime(this.period.getValue()).getEndTime()); + } + onMoveNext(): void { + this.outMove.emit(this.endTime.calcuNextTime(this.period.getValue()).getEndTime()); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/index.ts b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/index.ts new file mode 100644 index 000000000000..4a0e7f45d5f5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/fixed-period-mover/index.ts @@ -0,0 +1,20 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { FixedPeriodMoverComponent } from './fixed-period-mover.component'; +import { FixedPeriodMoverContainerComponent } from './fixed-period-mover-container.component'; + +@NgModule({ + declarations: [ + FixedPeriodMoverComponent, + FixedPeriodMoverContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + FixedPeriodMoverContainerComponent + ], + providers: [] +}) +export class FixedPeriodMoverModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-container.component.css b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-container.component.css new file mode 100644 index 000000000000..22569b457de3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-container.component.css @@ -0,0 +1,49 @@ +:host { + position: relative; +} +.l-group-member-wrapper { + color: #333; + border: 1px solid #e5e8f0; + height: 100%; + display: grid; + position: relative; + font-size: 13px; + font-family: 'Open Sans', sans-serif; + font-weight: 600; + grid-template-columns: auto; + grid-template-rows: 48px 469px; +} +.l-group-member-title { + display: flex; + padding: 0px 15px; + align-items: center; + justify-content: space-between; + background-color: #f6f8fb; + border-bottom: 1px solid #e5e8f0; +} +.l-group-member-sort-exchange { + color: #b3b6bf; + transform: rotate(90deg); +} +.l-group-member-list { + overflow-y: auto; +} +.l-message { + width: 100%; + height: 100%; + z-index: 15; + display: flex; + position: absolute; + align-items: center; + justify-content: center; + background-color: rgba(226, 226, 226, 0.8); +} +.l-message span { + color: #ff8c00; + text-align: center; +} +.l-message button { + top: 0px; + right: 0px; + position: absolute; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-container.component.html b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-container.component.html new file mode 100644 index 000000000000..005bf67cbbd9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-container.component.html @@ -0,0 +1,18 @@ +
+
+ Group Member ({{groupMemberList.length}}) + +
+
+ +
+
+ + {{message}} +
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-container.component.ts new file mode 100644 index 000000000000..3937cb9cd1f3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-container.component.ts @@ -0,0 +1,149 @@ +import { Component, OnInit } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { UserGroupInteractionService } from 'app/core/components/user-group/user-group-interaction.service'; +import { PinpointUserInteractionService } from 'app/core/components/pinpoint-user/pinpoint-user-interaction.service'; +import { GroupMemberInteractionService } from './group-member-interaction.service'; +import { GroupMemberDataService, IGroupMember, IGroupMemberResponse } from './group-member-data.service'; + +@Component({ + selector: 'pp-group-member-container', + templateUrl: './group-member-container.component.html', + styleUrls: ['./group-member-container.component.css'] +}) +export class GroupMemberContainerComponent implements OnInit { + private unsubscribe: Subject = new Subject(); + private ascendSort = true; + private currentUserGroupId = ''; + groupMemberList: IGroupMember[] = []; + useDisable = false; + showLoading = false; + message = ''; + constructor( + private groupMemberDataService: GroupMemberDataService, + private groupMemberInteractionService: GroupMemberInteractionService, + private userGroupInteractionService: UserGroupInteractionService, + private pinpointUserInteracionService: PinpointUserInteractionService + ) {} + ngOnInit() { + this.userGroupInteractionService.onSelect$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((id: string) => { + this.currentUserGroupId = id; + if (this.isValidUserGroupId()) { + this.getGroupMemberList(); + } else { + this.groupMemberList = []; + this.groupMemberInteractionService.setChangeGroupMember([]); + } + }); + this.pinpointUserInteracionService.onAdd$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((userId: string) => { + this.addGroupMember(userId); + }); + this.pinpointUserInteracionService.onUpdate$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((memberInfo: any) => { + let memberIndex = -1; + let editMemberInfo; + for (let i = 0 ; i < this.groupMemberList.length ; i++) { + if (this.groupMemberList[i].memberId === memberInfo.userId) { + memberIndex = i; + editMemberInfo = { + name: memberInfo.name, + department: memberInfo.department, + number: this.groupMemberList[i].number, + memberId: this.groupMemberList[i].memberId, + userGroupId: this.groupMemberList[i].userGroupId + }; + break; + } + } + this.groupMemberList.splice(memberIndex, 1, editMemberInfo); + }); + } + private isValidUserGroupId(): boolean { + return this.currentUserGroupId !== ''; + } + private getGroupMemberList(): void { + this.showProcessing(); + this.groupMemberDataService.retrieve(this.currentUserGroupId).subscribe((groupMemberData: IGroupMember[]) => { + this.groupMemberList = groupMemberData; + this.sortGroupMemberList(); + this.groupMemberInteractionService.setChangeGroupMember(this.getMemberIdList()); + this.hideProcessing(); + }, (error: string) => { + this.groupMemberInteractionService.setChangeGroupMember(this.getMemberIdList()); + this.hideProcessing(); + this.message = error; + }); + } + private addGroupMember(userId: string): void { + this.groupMemberDataService.create(userId, this.currentUserGroupId).subscribe((response: IGroupMemberResponse) => { + this.doAfterAddAndRemoveAction(response); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + } + private getMemberIdList(): string[] { + return this.groupMemberList.map((groupMember: IGroupMember): string => { + return groupMember.memberId; + }); + } + private doAfterAddAndRemoveAction(response: IGroupMemberResponse): void { + if (response.result === 'SUCCESS') { + this.getGroupMemberList(); + } else { + this.hideProcessing(); + } + } + private sortAscend(): void { + this.groupMemberList.sort((a: IGroupMember, b: IGroupMember): number => { + return a.name > b.name ? 1 : -1; + }); + } + private sortDescend(): void { + this.groupMemberList.sort((a: IGroupMember, b: IGroupMember): number => { + return a.name < b.name ? 1 : -1; + }); + } + private sortGroupMemberList() { + if (this.ascendSort === true) { + this.sortAscend(); + } else { + this.sortDescend(); + } + } + onRemoveGroupMember(id: string): void { + this.showProcessing(); + this.groupMemberDataService.remove(id, this.currentUserGroupId).subscribe((response: IGroupMemberResponse) => { + this.doAfterAddAndRemoveAction(response); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + } + onCloseMessage(): void { + this.message = ''; + this.groupMemberInteractionService.setChangeGroupMember(this.getMemberIdList()); + } + onSort(): void { + if (this.isValidUserGroupId()) { + this.ascendSort = !this.ascendSort; + this.sortGroupMemberList(); + } + } + hasMessage(): boolean { + return this.message !== ''; + } + private showProcessing(): void { + this.useDisable = true; + this.showLoading = true; + } + private hideProcessing(): void { + this.useDisable = false; + this.showLoading = false; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-data.service.ts new file mode 100644 index 000000000000..abcf4c8c79f3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-data.service.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, tap } from 'rxjs/operators'; + +export interface IGroupMember { + department: string; + memberId: string; + name: string; + number: string; + userGroupId: string; +} +export interface IGroupMemberResponse { + result: string; +} + +@Injectable() +export class GroupMemberDataService { + url = 'userGroup/member.pinpoint'; + constructor(private http: HttpClient) { } + retrieve(userGroupId: string): Observable { + return this.http.get(this.url, { params: { userGroupId }}).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + create(memberId: string, userGroupId: string): Observable { + return this.http.post(this.url, { + memberId, + userGroupId + }).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + remove(memberId: string, userGroupId: string): Observable { + return this.http.request('delete', this.url, { + body: { + memberId, + userGroupId + } + }).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + private handleError(error: HttpErrorResponse) { + return throwError(error.statusText || error); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-interaction.service.ts new file mode 100644 index 000000000000..422f43e6ddc6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member-interaction.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; + +@Injectable() +export class GroupMemberInteractionService { + private outChangeGroupMember = new Subject(); + onChangeGroupMember$: Observable; + + constructor() { + this.onChangeGroupMember$ = this.outChangeGroupMember.asObservable(); + } + setChangeGroupMember(groupMemberList: string[]): void { + this.outChangeGroupMember.next(groupMemberList); + } +} + diff --git a/web/src/main/webapp/v2/src/app/core/components/group-member/group-member.component.css b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member.component.css new file mode 100644 index 000000000000..ebe69e85053f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member.component.css @@ -0,0 +1,31 @@ +li { + color: #333; + height: 28px; + margin: 0; + cursor: pointer; + padding: 6px 15px 0px 15px; + font-size: 13px; +} +li:hover { + background:#fff0f0; +} +li.selected { + background:#fff0f0; +} +.item-wrapper { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.item-wrapper > div { + float: right; +} +.item-wrapper .fa-trash-alt { + color: #b3b6bf; + font-size: 14px; +} +.item-wrapper .fa-check { + color: #F00; + margin-left: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/group-member/group-member.component.html b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member.component.html new file mode 100644 index 000000000000..17b96394fcf7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member.component.html @@ -0,0 +1,12 @@ +
    +
  • +
    +
    + + + +
    + ({{group.department}}) {{group.name}} +
    +
  • +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/group-member/group-member.component.ts b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member.component.ts new file mode 100644 index 000000000000..a177acb2826a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/group-member/group-member.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { IGroupMember } from './group-member-data.service'; + +@Component({ + selector: 'pp-group-member', + templateUrl: './group-member.component.html', + styleUrls: ['./group-member.component.css'] +}) +export class GroupMemberComponent implements OnInit { + @Input() groupMemberList: IGroupMember[]; + @Output() outRemove: EventEmitter = new EventEmitter(); + private removeConformId = ''; + constructor() {} + ngOnInit() {} + onRemove(id: string): void { + this.removeConformId = id; + } + onCancelRemove(): void { + this.removeConformId = ''; + } + onConfirmRemove(): void { + this.outRemove.emit(this.removeConformId); + this.removeConformId = ''; + } + isRemoveTarget(id: string): boolean { + return this.removeConformId === id; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/group-member/index.ts b/web/src/main/webapp/v2/src/app/core/components/group-member/index.ts new file mode 100644 index 000000000000..d73d292844e5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/group-member/index.ts @@ -0,0 +1,29 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { GroupMemberComponent } from './group-member.component'; +import { GroupMemberContainerComponent } from './group-member-container.component'; +import { GroupMemberInteractionService } from './group-member-interaction.service'; +import { GroupMemberDataService } from './group-member-data.service'; + +@NgModule({ + declarations: [ + GroupMemberComponent, + GroupMemberContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + GroupMemberContainerComponent + ], + entryComponents: [ + GroupMemberComponent, + GroupMemberContainerComponent + ], + providers: [ + GroupMemberInteractionService, + GroupMemberDataService + ] +}) +export class GroupMemberModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup-container.component.css b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup-container.component.css new file mode 100644 index 000000000000..c8c6286b3461 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup-container.component.css @@ -0,0 +1,86 @@ +:host { + display: block; + border: 1px solid transparent; +} + +:host(.navbar), +:host(.server_map) { + width: 500px; +} + +:host(.response_summary), +:host(.load), +:host(.agent_response_time), +:host(.application_response_time) { + width: 340px; +} + +:host(.call_tree) { + width: 540px; +} + +:host(.scatter) { + width: 580px; +} + +:host(.real_time) { + width: 650px; +} + +:host(.agent_list), +:host(.agent_data_source), +:host(.application_data_source) { + width: 300px; +} + +:host(.agent_heap), +:host(.application_heap), +:host(.agent_non_heap), +:host(.application_non_heap) { + width: 530px; +} + +:host(.agent_active_thread), +:host(.application_active_thread), +:host(.agent_tps), +:host(.application_tps), +:host(.agent_open_file_descriptor), +:host(.application_open_file_descriptor), +:host(.application_direct_buffer_count), +:host(.application_direct_buffer_memory), +:host(.application_mapped_buffer_count) { + width: 405px; +} + +:host(.application_mapped_buffer_memory) { + width: 420px; +} + +:host(.agent_cpu_usage) { + width: 470px; +} + +:host(.application_jvm_cpu_usage), +:host(.application_system_cpu_usage) { + width: 520px; +} + +:host(.agent_direct_buffer_count), +:host(.agent_mapped_buffer_count) { + width: 270px; +} + +:host(.agent_direct_buffer_memory) { + width: 310px; +} + +:host(.agent_mapped_buffer_memory) { + width: 320px; +} + +.tooltip-triangle { + width: 0; + height: 0; + display: inline-block; + position: fixed; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup-container.component.html b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup-container.component.html new file mode 100644 index 000000000000..3cc858bfd818 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup-container.component.html @@ -0,0 +1,8 @@ + + + +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup-container.component.ts new file mode 100644 index 000000000000..78363a94e95a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup-container.component.ts @@ -0,0 +1,212 @@ +import { Component, OnInit, ElementRef, Input, Output, EventEmitter, AfterViewInit, HostBinding } from '@angular/core'; +import { Observable } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; + +import { WindowRefService, DynamicPopup } from 'app/shared/services'; + +export enum HELP_VIEWER_LIST { + NAVBAR = 'HELP_VIEWER.NAVBAR', + RESPONSE_SUMMARY = 'HELP_VIEWER.RESPONSE_SUMMARY', + LOAD = 'HELP_VIEWER.LOAD', + SERVER_MAP = 'HELP_VIEWER.SERVER_MAP', + REAL_TIME = 'HELP_VIEWER.REAL_TIME', + CALL_TREE = 'HELP_VIEWER.CALL_TREE', + SCATTER = 'HELP_VIEWER.SCATTER', + AGENT_LIST = 'HELP_VIEWER.INSPECTOR.AGENT_LIST', + AGENT_HEAP = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.HEAP', + AGENT_NON_HEAP = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.NON_HEAP', + AGENT_CPU_USAGE = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.CPU_USAGE', + AGENT_TPS = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.TPS', + AGENT_ACTIVE_THREAD = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.ACTIVE_THREAD', + AGENT_RESPONSE_TIME = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.RESPONSE_TIME', + AGENT_DATA_SOURCE = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.DATA_SOURCE', + AGENT_OPEN_FILE_DESCRIPTOR = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.OPEN_FILE_DESCRIPTOR', + AGENT_DIRECT_BUFFER_COUNT = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.DIRECT_BUFFER_COUNT', + AGENT_DIRECT_BUFFER_MEMORY = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.DIRECT_BUFFER_MEMORY', + AGENT_MAPPED_BUFFER_COUNT = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.MAPPED_BUFFER_COUNT', + AGENT_MAPPED_BUFFER_MEMORY = 'HELP_VIEWER.INSPECTOR.AGENT_CHART.MAPPED_BUFFER_MEMORY', + APPLICATION_HEAP = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.HEAP', + APPLICATION_NON_HEAP = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.NON_HEAP', + APPLICATION_JVM_CPU_USAGE = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.JVM_CPU_USAGE', + APPLICATION_SYSTEM_CPU_USAGE = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.SYSTEM_CPU_USAGE', + APPLICATION_TPS = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.TPS', + APPLICATION_ACTIVE_THREAD = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.ACTIVE_THREAD', + APPLICATION_RESPONSE_TIME = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.RESPONSE_TIME', + APPLICATION_DATA_SOURCE = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.DATA_SOURCE', + APPLICATION_OPEN_FILE_DESCRIPTOR = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.OPEN_FILE_DESCRIPTOR', + APPLICATION_DIRECT_BUFFER_COUNT = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.DIRECT_BUFFER_COUNT', + APPLICATION_DIRECT_BUFFER_MEMORY = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.DIRECT_BUFFER_MEMORY', + APPLICATION_MAPPED_BUFFER_COUNT = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.MAPPED_BUFFER_COUNT', + APPLICATION_MAPPED_BUFFER_MEMORY = 'HELP_VIEWER.INSPECTOR.APPLICATION_CHART.MAPPED_BUFFER_MEMORY', +} + +const enum HELP_VIEWER_WIDTH_STATE { + OK, + LEFT_OVERFLOW, + RIGHT_OVERFLOW +} + +const enum HELP_VIEWER_HEIGHT_STATE { + OK, + DOWN_OVERFLOW +} + +const enum TOOLTIP_CONSTANT { + START_POINT = 28, // 툴팁 시작을 모서리에서 살짝 밀어주는 길이 + DISTANCE_FROM_BUTTON = 9, // 클릭한 버튼에서 살짝 떨어뜨려줄 길이 + HEIGHT = 7, // 툴팁 삼각형 높이 + WIDTH = 14 // 툴팁 삼각형 넓이 +} + +@Component({ + selector: 'pp-help-viewer-popup-container', + templateUrl: './help-viewer-popup-container.component.html', + styleUrls: ['./help-viewer-popup-container.component.css'], +}) +export class HelpViewerPopupContainerComponent implements OnInit, AfterViewInit, DynamicPopup { + @Input() data: HELP_VIEWER_LIST; + @Input() coord: ICoordinate; + @Output() outCreated = new EventEmitter(); + @Output() outClose = new EventEmitter(); + @Output() outReInit = new EventEmitter<{[key: string]: any}>(); + @HostBinding('class') styleClass: string; + + data$: Observable<{[key: string]: any}[]>; + tooltipTriangleStyle: {[key: string]: any} = { + 'border-bottom': `${TOOLTIP_CONSTANT.HEIGHT}px solid #fff`, + 'border-right': `${TOOLTIP_CONSTANT.HEIGHT}px solid transparent`, + 'border-left': `${TOOLTIP_CONSTANT.HEIGHT}px solid transparent`, + 'transform-origin': `50% -${TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON}px`, + }; + + constructor( + private elementRef: ElementRef, + private translateService: TranslateService, + private windowRefService: WindowRefService, + ) {} + + ngOnInit() { + this.setStyleClass(this.data); + this.data$ = this.getHelpViewerText(this.data); + } + + ngAfterViewInit() { + this.outCreated.emit(this.getPosition(this.coord)); + } + + onInputChange({data, coord}: {data: HELP_VIEWER_LIST, coord: ICoordinate}): void { + if (this.coord) { + const { coordX: x1, coordY: y1 } = this.coord; + const { coordX: x2, coordY: y2 } = coord; + + x1 === x2 && y1 === y2 ? this.outClose.emit() : this.outReInit.emit({ data, coord }); + } + } + + private setStyleClass(data: HELP_VIEWER_LIST): void { + const className = Object.keys(HELP_VIEWER_LIST).find((cur: keyof typeof HELP_VIEWER_LIST) => { + return HELP_VIEWER_LIST[cur] === data; + }).toLowerCase(); + + this.styleClass = `popup ${className}`; + } + + private setPosition(coordY: number, coordX: number): ICoordinate { + return { coordX, coordY }; + } + + private getPosition({coordX, coordY}: ICoordinate): ICoordinate { + /** + * HelpViewer위치 띄워주는 기준 + * Width기준: event.clientX - TOOLTIP_CONSTANT.INDENT_WIDTH(왼쪽으로 살짝 밀어줄 너비) + width 의 overflow여부 + * Height기준: event.clientY + TOOLTIP_CONSTANT.TRIANGLE_HEIGHT(말풍선 삼각형 높이) + height 의 overflow여부 + * 1. Width: OK, Height: OK => 클릭한 버튼 기준 밑에 위치 + * 2. Width: OK, Height: Overflow => 클릭한 버튼 기준 위에 위치 + * 3. Width: Left Overflow, Height: OK => 클릭한 버튼 기준 오른쪽, 밑방향으로 위치 + * 4. Width: Right Overflow, Height: OK => 클릭한 버튼 기준 왼쪽, 밑방향으로 위치 + * 5. Width: Left Overflow, Height: Overflow => 클릭한 기준 오른쪽, 윗방향으로 위치 + * 6. Width: Right Overflow, Height: Overflow => 클릭한 기준 왼쪽, 윗방향으로 위치 + */ + const width = this.elementRef.nativeElement.offsetWidth; + const height = this.elementRef.nativeElement.offsetHeight; + const widthState = this.checkWidth(width); + const heightState = this.checkHeight(height); + let pos: ICoordinate; + + switch (widthState) { + case HELP_VIEWER_WIDTH_STATE.OK: + switch (heightState) { + case HELP_VIEWER_HEIGHT_STATE.OK: + this.setTooltipTriangleStyle(coordY + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON, coordX - TOOLTIP_CONSTANT.WIDTH / 2, ''); + pos = this.setPosition(coordY + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON + TOOLTIP_CONSTANT.HEIGHT, coordX - TOOLTIP_CONSTANT.START_POINT); + break; + case HELP_VIEWER_HEIGHT_STATE.DOWN_OVERFLOW: + this.setTooltipTriangleStyle(coordY + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON, coordX - TOOLTIP_CONSTANT.WIDTH / 2, 'rotate(-180deg)'); + pos = this.setPosition(coordY - height - TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON - TOOLTIP_CONSTANT.HEIGHT, coordX - TOOLTIP_CONSTANT.START_POINT); + break; + } + break; + case HELP_VIEWER_WIDTH_STATE.LEFT_OVERFLOW: + switch (heightState) { + case HELP_VIEWER_HEIGHT_STATE.OK: + this.setTooltipTriangleStyle(coordY + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON, coordX - TOOLTIP_CONSTANT.WIDTH / 2, 'rotate(-90deg)'); + pos = this.setPosition(coordY - TOOLTIP_CONSTANT.START_POINT, coordX + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON + TOOLTIP_CONSTANT.HEIGHT); + break; + case HELP_VIEWER_HEIGHT_STATE.DOWN_OVERFLOW: + this.setTooltipTriangleStyle(coordY + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON, coordX - TOOLTIP_CONSTANT.WIDTH / 2, 'rotate(-90deg)'); + pos = this.setPosition(coordY - height + TOOLTIP_CONSTANT.START_POINT, coordX + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON + TOOLTIP_CONSTANT.HEIGHT); + break; + } + break; + case HELP_VIEWER_WIDTH_STATE.RIGHT_OVERFLOW: + switch (heightState) { + case HELP_VIEWER_HEIGHT_STATE.OK: + this.setTooltipTriangleStyle(coordY + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON, coordX - TOOLTIP_CONSTANT.WIDTH / 2, 'rotate(90deg)'); + pos = this.setPosition(coordY - TOOLTIP_CONSTANT.START_POINT, coordX - width - TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON - TOOLTIP_CONSTANT.HEIGHT); + break; + case HELP_VIEWER_HEIGHT_STATE.DOWN_OVERFLOW: + this.setTooltipTriangleStyle(coordY + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON, coordX - TOOLTIP_CONSTANT.WIDTH / 2, 'rotate(90deg)'); + pos = this.setPosition(coordY - height + TOOLTIP_CONSTANT.START_POINT, coordX - width - TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON - TOOLTIP_CONSTANT.HEIGHT); + break; + } + break; + } + + return pos; + } + + private checkWidth(width: number): HELP_VIEWER_WIDTH_STATE { + const value = this.coord.coordX - TOOLTIP_CONSTANT.START_POINT; + const windowWidth = this.windowRefService.nativeWindow.innerWidth; + + return (value >= 0 && value + width <= windowWidth) ? HELP_VIEWER_WIDTH_STATE.OK + : value < 0 ? HELP_VIEWER_WIDTH_STATE.LEFT_OVERFLOW + : HELP_VIEWER_WIDTH_STATE.RIGHT_OVERFLOW; + } + + private checkHeight(height: number): HELP_VIEWER_HEIGHT_STATE { + const value = this.coord.coordY + TOOLTIP_CONSTANT.DISTANCE_FROM_BUTTON + height; + const windowHeight = this.windowRefService.nativeWindow.innerHeight; + + return value <= windowHeight ? HELP_VIEWER_HEIGHT_STATE.OK : HELP_VIEWER_HEIGHT_STATE.DOWN_OVERFLOW; + } + + private setTooltipTriangleStyle(top: number, left: number, transform: string): void { + this.tooltipTriangleStyle = { + ...this.tooltipTriangleStyle, + ...{ + left: left < 0 ? 0 : `${left}px`, + top: top < 0 ? 0 : `${top}px`, + transform + } + }; + } + + private getHelpViewerText(viewerType: HELP_VIEWER_LIST): Observable<{[key: string]: any}[]> { + return this.translateService.get(viewerType); + } + + onClickOutside(): void { + this.outClose.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup.component.css b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup.component.css new file mode 100644 index 000000000000..eb07fe175338 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup.component.css @@ -0,0 +1,72 @@ +:host { + display: block; + width: 100%; +} + +.main-dl:not(:first-of-type) { + border-top: 1px solid #e5e8f0; +} + +.title-group { + display: block; + padding: 14px 18px; + height: auto; + border-bottom: 1px solid #e5e8f0; + font-size: 13px; + font-weight: 600; + color: #333; +} + +.title-group > dt { + font-size: 16px; + font-weight: 600; + color: #4a8fd2; +} + +.title-group > dd { + font-size: 12px; + color: #777879; + margin-top: 8px; +} + +.contents-group .category-title { + font-size: 13px; + font-weight: 600; + color: #333; + margin-bottom: 12px; + padding-left: 26px; +} + +.contents-group .category-item-list { + display: flex; + padding: 5px 0; + line-height: 20px; +} + +.category-list { + padding: 14px 8px; +} + +.category-list:not(:last-of-type) { + border-bottom: 1px solid #e5e8f0; +} + +.category-item-list > dt { + font-size: 12px; + font-weight: 600; + color: #666; + width: 80px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 5px; + text-align: center; +} + +.category-item-list > dd { + font-size: 12px; + color: #999; + flex: 1; + display: flex; + align-items: center; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup.component.html b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup.component.html new file mode 100644 index 000000000000..bcdf7f4d483e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup.component.html @@ -0,0 +1,17 @@ +
+
+
{{helpViewerText.TITLE}}
+
{{helpViewerText.DESC}}
+
+
+
+
{{category.TITLE}}
+
+
+
+
+
+
+
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup.component.ts b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup.component.ts new file mode 100644 index 000000000000..3a18ec7b326a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/help-viewer-popup.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'pp-help-viewer-popup', + templateUrl: './help-viewer-popup.component.html', + styleUrls: ['./help-viewer-popup.component.css'] +}) +export class HelpViewerPopupComponent implements OnInit { + @Input() data: {[key: string]: any}[]; + + constructor() {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/index.ts b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/index.ts new file mode 100644 index 000000000000..e73f2d8344a4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/help-viewer-popup/index.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { HelpViewerPopupContainerComponent } from './help-viewer-popup-container.component'; +import { HelpViewerPopupComponent } from './help-viewer-popup.component'; + +@NgModule({ + declarations: [ + HelpViewerPopupContainerComponent, + HelpViewerPopupComponent + ], + imports: [ + SharedModule + ], + exports: [], + entryComponents: [ + HelpViewerPopupContainerComponent + ], + providers: [] +}) +export class HelpViewerPopupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-container.component.css new file mode 100644 index 000000000000..bc32c5675c9f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-container.component.css @@ -0,0 +1,4 @@ +:host { + display: block; + margin-right: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-container.component.html new file mode 100644 index 000000000000..bc115f1dac0e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-container.component.html @@ -0,0 +1,8 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-container.component.ts new file mode 100644 index 000000000000..339c70b0c71f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-container.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, tap, map } from 'rxjs/operators'; + +import { UrlQuery, UrlPathId } from 'app/shared/models'; +import { WebAppSettingDataService, NewUrlStateNotificationService, UrlRouteManagerService } from 'app/shared/services'; + +@Component({ + selector: 'pp-inbound-outbound-range-selector-container', + templateUrl: './inbound-outbound-range-selector-container.component.html', + styleUrls: ['./inbound-outbound-range-selector-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InboundOutboundRangeSelectorContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + + hiddenComponent: boolean; + inboundList: string[]; + outboundList: string[]; + selectedInbound: string; + selectedOutbound: string; + + constructor( + private changeDetectorRef: ChangeDetectorRef, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + ) {} + + ngOnInit() { + this.inboundList = this.webAppSettingDataService.getInboundList(); + this.outboundList = this.webAppSettingDataService.getOutboundList(); + + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + tap((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.APPLICATION, UrlPathId.PERIOD, UrlPathId.END_TIME)) { + this.hiddenComponent = false; + } else { + this.hiddenComponent = true; + } + }), + map((urlService: NewUrlStateNotificationService) => { + return { + inbound: urlService.hasValue(UrlQuery.INBOUND) ? urlService.getQueryValue(UrlQuery.INBOUND) : this.webAppSettingDataService.getUserDefaultInbound(), + outbound: urlService.hasValue(UrlQuery.OUTBOUND) ? urlService.getQueryValue(UrlQuery.OUTBOUND) : this.webAppSettingDataService.getUserDefaultOutbound() + }; + }) + ).subscribe(({inbound, outbound}: {inbound: string, outbound: string}) => { + this.selectedInbound = inbound; + this.selectedOutbound = outbound; + this.changeDetectorRef.detectChanges(); + }); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + onChangeBound(bound: string[]): void { + this.urlRouteManagerService.moveOnPage({ + url: [ + this.newUrlStateNotificationService.getStartPath(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ], + queryParam: { + [UrlQuery.INBOUND]: bound[0], + [UrlQuery.OUTBOUND]: bound[1] + } + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-for-configuration-popup-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-for-configuration-popup-container.component.css new file mode 100644 index 000000000000..e8922768b7b9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-for-configuration-popup-container.component.css @@ -0,0 +1,4 @@ +:host { + display: inline-block; + margin-left: 15px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-for-configuration-popup-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-for-configuration-popup-container.component.html new file mode 100644 index 000000000000..a0eb52b8d002 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-for-configuration-popup-container.component.html @@ -0,0 +1,7 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-for-configuration-popup-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-for-configuration-popup-container.component.ts new file mode 100644 index 000000000000..61bd403a4c89 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector-for-configuration-popup-container.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; + +import { WebAppSettingDataService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +@Component({ + selector: 'pp-inbound-outbound-range-selector-for-configuration-popup-container', + templateUrl: './inbound-outbound-range-selector-for-configuration-popup-container.component.html', + styleUrls: ['./inbound-outbound-range-selector-for-configuration-popup-container.component.css'] +}) +export class InboundOutboundRangeSelectorForConfigurationPopupContainerComponent implements OnInit { + inboundList: string[]; + outboundList: string[]; + selectedInbound: string; + selectedOutbound: string; + + constructor( + private webAppSettingDataService: WebAppSettingDataService, + private analyticsService: AnalyticsService + ) {} + + ngOnInit() { + this.inboundList = this.webAppSettingDataService.getInboundList(); + this.outboundList = this.webAppSettingDataService.getOutboundList(); + this.selectedInbound = this.webAppSettingDataService.getUserDefaultInbound(); + this.selectedOutbound = this.webAppSettingDataService.getUserDefaultOutbound(); + } + + onChangeBound(bound: string[]): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SET_BOUND_IN_CONFIGURATION, `Inbound: ${bound[0]}, Outbound: ${bound[1]}`); + this.webAppSettingDataService.setUserDefaultInbound(bound[0]); + this.webAppSettingDataService.setUserDefaultOutbound(bound[1]); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector.component.css b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector.component.css new file mode 100644 index 000000000000..cbbe302a3d9f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector.component.css @@ -0,0 +1,108 @@ +:host { + display: block; + position: relative; + width: 115px; +} +.fa-sign-in-alt, .fa-sign-out-alt { + color: #33b692; +} +button { + outline: none; +} +.l-wrapper { + border: 1px solid #4488CB; +} +.l-dropdown-button { + text-align: left; + width: 100%; + background-color: #fff; + font-size: 13px; + padding: 7px 0 6px 0; + outline: 0; +} +.l-dropdown-button > .fa-angle-down { + font-size: 15px; + display: inline-block; + margin-left: 9px; + color: #33b692; +} + +.l-bound-text { + display: inline-block; + font-size: 14px; + margin-left: 15px; + color: #333; +} + +.l-dropdown-menu-wrapper { + position: absolute; + top: 100%; + left: 0; + z-index: 9999; + display: flex; + flex-wrap: wrap; + border: 1px solid #e5e8f0; + border-radius: 2px; + margin-top: 2px; + background-color: #fff; + width: 180px; +} + +.l-inbound-list { + border-right: 1px solid #e5e8f0; +} + +.l-inbound-list, .l-outbound-list { + width: 50%; + text-align: center; +} + +.l-bound-list-item { + font-weight: 400; + color: #333; + font-size: 13px; + padding: 5px 10px; + cursor: pointer; +} + +.l-bound-title { + padding: 5px 10px; + color: #333; + background-color: #edf2f8; + font-weight: 600 !important; + font-size: 13px; + border-bottom: 1px solid #e5e8f0; +} + +.l-bound-list-item:hover { + background-color: #eee; +} + +.l-bound-list-item.active { + color: #4b99e3; + background-color: #edf2f8; +} + +.l-button-group-wrapper { + width: 100%; + border-top: 1px solid #e5e8f0; + padding: 1px; + text-align: right; +} + +.l-apply-button { + background-color: #4a8fd2; + border: 1px solid #4a8fd2; + border-radius: 0px; + padding: 5px 11px; + color: #fff; + margin: 2px; +} + +.l-cancel-button { + border: 1px solid #e5e8f0; + border-radius: 0px; + padding: 5px 7px; + color: #333; + margin: 2px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector.component.html b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector.component.html new file mode 100644 index 000000000000..cfb72cc7c5d2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector.component.html @@ -0,0 +1,21 @@ +
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector.component.ts b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector.component.ts new file mode 100644 index 000000000000..b89f93730fd0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/inbound-outbound-range-selector.component.ts @@ -0,0 +1,63 @@ +import { Component, EventEmitter, Input, Output, OnInit, OnChanges, SimpleChanges } from '@angular/core'; + +@Component({ + selector: 'pp-inbound-outbound-range-selector', + templateUrl: './inbound-outbound-range-selector.component.html', + styleUrls: ['./inbound-outbound-range-selector.component.css'] +}) +export class InboundOutboundRangeSelectorComponent implements OnInit, OnChanges { + hideList = true; + prevSelectedInbound: string; + prevSelectedOutbound: string; + @Input() selectedInbound: string; + @Input() selectedOutbound: string; + @Input() inboundList: string[]; + @Input() outboundList: string[]; + @Output() outSelected: EventEmitter = new EventEmitter(); + + constructor() {} + + ngOnChanges(changes: SimpleChanges) { + if (changes['selectedInbound']) { + this.prevSelectedInbound = this.selectedInbound = changes['selectedInbound'].currentValue; + } + if (changes['selectedOutbound']) { + this.prevSelectedOutbound = this.selectedOutbound = changes['selectedOutbound'].currentValue; + } + } + + ngOnInit() { } + onSelectInbound(inbound: string): void { + this.selectedInbound = inbound; + } + + onSelectOutbound(outbound: string): void { + this.selectedOutbound = outbound; + } + + onApply(): void { + if (!(this.selectedInbound === this.prevSelectedInbound && this.selectedOutbound === this.prevSelectedOutbound)) { + this.outSelected.emit([this.selectedInbound, this.selectedOutbound]); + } + this.close(); + } + + onCancel(): void { + this.selectedInbound = this.prevSelectedInbound; + this.selectedOutbound = this.prevSelectedOutbound; + this.close(); + } + + toggleList(): void { + this.hideList = !this.hideList; + } + + onClose(): void { + this.onCancel(); + this.close(); + } + + private close(): void { + this.hideList = true; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/index.ts b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/index.ts new file mode 100644 index 000000000000..9935d7cf140a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inbound-outbound-range-selector/index.ts @@ -0,0 +1,23 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { InboundOutboundRangeSelectorComponent } from './inbound-outbound-range-selector.component'; +import { InboundOutboundRangeSelectorContainerComponent } from './inbound-outbound-range-selector-container.component'; +import { InboundOutboundRangeSelectorForConfigurationPopupContainerComponent } from './inbound-outbound-range-selector-for-configuration-popup-container.component'; + +@NgModule({ + declarations: [ + InboundOutboundRangeSelectorComponent, + InboundOutboundRangeSelectorContainerComponent, + InboundOutboundRangeSelectorForConfigurationPopupContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + InboundOutboundRangeSelectorContainerComponent, + InboundOutboundRangeSelectorForConfigurationPopupContainerComponent + ], + providers: [] +}) +export class InboundOutboundRangeSelectorModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/info-per-server/index.ts b/web/src/main/webapp/v2/src/app/core/components/info-per-server/index.ts new file mode 100644 index 000000000000..2d99f6436282 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/info-per-server/index.ts @@ -0,0 +1,31 @@ + +import { NgModule } from '@angular/core'; +import { MatTooltipModule } from '@angular/material'; +import { SharedModule } from 'app/shared'; +import { ScatterChartModule } from 'app/core/components/scatter-chart'; +import { ResponseSummaryChartModule } from 'app/core/components/response-summary-chart'; +import { LoadChartModule } from 'app/core/components/load-chart'; +import { ServerListModule } from 'app/core/components/server-list'; +import { InfoPerServerContainerComponent } from './info-per-server-container.component'; +import { InfoPerServerForFilteredMapContainerComponent } from './info-per-server-for-filtered-map-container.component'; + +@NgModule({ + declarations: [ + InfoPerServerContainerComponent, + InfoPerServerForFilteredMapContainerComponent + ], + imports: [ + SharedModule, + MatTooltipModule, + ScatterChartModule, + ResponseSummaryChartModule, + LoadChartModule, + ServerListModule + ], + exports: [ + InfoPerServerContainerComponent, + InfoPerServerForFilteredMapContainerComponent + ], + providers: [] +}) +export class InfoPerServerModule {} diff --git a/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-container.component.css b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-container.component.css new file mode 100644 index 000000000000..555c38936fff --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-container.component.css @@ -0,0 +1,116 @@ +:host { + z-index: 9; +} +.l-sidemenu2 { + position: absolute; + top: 0px; + left: 0px; + width: 461px; + border-left: 1px solid #e5e8f0; + border-right: 1px solid #e5e8f0; + background: #fff; + height: 100%; + overflow-y: auto; + overflow-x: hidden; +} +.l-title-group2 { + display: flex; + flex-flow: row wrap; + color: #666; + justify-content: space-between; + align-items: center; + padding: 0 25px; + background: none; + border: none; + height: 50px; +} +.l-selected-agent { + color: #666; + font-size: 13px; + display: block; + overflow : hidden; + white-space : nowrap; + text-overflow : ellipsis; +} +.l-selected-agent label { + color: #fff; + font-size: 11px; + font-weight: 600; + display: inline-block; + background: #4b99e3; + margin: 0 12px 0 0; + padding: 6px 11px; + line-height: 1em; + position: relative; +} +.l-selected-agent label:after { + content:''; + position:absolute; + width:0; + height:0; + border-top:4px solid transparent; + border-bottom:4px solid transparent; + border-left:3px solid #4b99e3; + right:-3px; + top:50%; + transform:translateY(-50%); +} +.l-contents-group { + padding: 51px 0px 16px; + overflow-y: auto; + overflow-x: hidden; +} +.l-chart-group-list { + flex: 1; +} + +.l-sidemenu3 { + top: 0px; + left: 0px; + width: 348px; + background: #f8f9fb; + border: none; + position: absolute; + height: 100%; + overflow-y: auto; + overflow-x: hidden; +} +.l-title-group3 { + display: flex; + flex-flow: row wrap; + align-items: center; + height: 64px; + padding: 0 25px; + position: relative; +} +.l-title-group3 .fas { + font-size: 14px; + width: 33px; + height: 33px; + border-radius: 50%; + background: #4a8fd2; + color: #fff; + align-items: center; + display: flex; + justify-content: center; + margin: 0 7px 0 0; +} +.l-title-group3 .l-title { + font-size: 24px; + font-weight: 600; + color: #666; +} + + +.sidemenu { + top: 0px; + left: -461px; +} +.title-group2 { + background-color: #f9fafc; + border-bottom: 1px solid #eaeef4; +} +hr { + height: 1px; + border-top: 1px solid #EAEEF4; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-container.component.html b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-container.component.html new file mode 100644 index 000000000000..772421c5db8a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-container.component.html @@ -0,0 +1,29 @@ +
+
+
+ + {{ selectedAgent }} +
+
+
+
+ +
+ +
+ +
+
+
+
+
+ + Servers List +
+
+ +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-container.component.ts new file mode 100644 index 000000000000..e2301d6c006a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-container.component.ts @@ -0,0 +1,138 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { trigger, state, style, animate, transition } from '@angular/animations'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + StoreHelperService, + NewUrlStateNotificationService, + UrlRouteManagerService, + AgentHistogramDataService, + AnalyticsService, + TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ServerMapData } from 'app/core/components/server-map/class/server-map-data.class'; + + +@Component({ + selector: 'pp-info-per-server-container', + templateUrl: './info-per-server-container.component.html', + styleUrls: ['./info-per-server-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('listAnimationTrigger', [ + state('start', style({ + left: '0px' + })), + state('end', style({ + left: '-809px' + })), + transition('* => *', [ + animate('0.2s 0.5s ease-out') + ]) + ]), + trigger('chartAnimationTrigger', [ + state('start', style({ + left: '0px' + })), + state('end', style({ + left: '-461px' + })), + transition('* => *', [ + animate('0.2s 0s ease-out') + ]) + ]), + ] +}) +export class InfoPerServerContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + selectedTarget: ISelectedTarget; + serverMapData: ServerMapData; + agentHistogramData: any; + selectedAgent = ''; + listAnimationTrigger = 'start'; + chartAnimationTrigger = 'start'; + constructor( + private storeHelperService: StoreHelperService, + private changeDetector: ChangeDetectorRef, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private agentHistogramDataService: AgentHistogramDataService, + private analyticsService: AnalyticsService, + ) {} + ngOnInit() { + this.connectStore(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.hide(); + this.changeDetector.detectChanges(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).subscribe((target: ISelectedTarget) => { + this.selectedTarget = target; + }); + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: ServerMapData) => { + this.serverMapData = serverMapData; + }); + this.storeHelperService.getInfoPerServerState(this.unsubscribe).subscribe((visibleState: boolean) => { + if (this.selectedTarget && this.selectedTarget.isNode) { + const node = this.serverMapData.getNodeData(this.selectedTarget.node[0]); + if (visibleState === true) { + this.agentHistogramDataService.getData(node.key, node.applicationName, node.serviceTypeCode, this.serverMapData).subscribe((histogramData: any) => { + this.show(); + this.agentHistogramData = histogramData || {}; + this.agentHistogramData.isWas = node.isWas; + this.changeDetector.detectChanges(); + this.storeHelperService.dispatch(new Actions.UpdateServerList(this.agentHistogramData)); + this.onSelectAgent(this.getFirstAgent()); + this.storeHelperService.dispatch(new Actions.ChangeInfoPerServerVisibleState(true)); + }); + } else { + this.hide(); + this.changeDetector.detectChanges(); + this.storeHelperService.dispatch(new Actions.ChangeInfoPerServerVisibleState(false)); + } + } + }); + } + private hide(): void { + this.listAnimationTrigger = 'start'; + this.chartAnimationTrigger = 'start'; + } + private show(): void { + this.listAnimationTrigger = 'end'; + this.chartAnimationTrigger = 'end'; + } + getFirstAgent(): string { + const firstKey = Object.keys(this.agentHistogramData['serverList']).sort()[0]; + return Object.keys(this.agentHistogramData['serverList'][firstKey]['instanceList']).sort()[0]; + } + onSelectAgent(agentName: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_AGENT); + this.storeHelperService.dispatch(new Actions.ChangeAgentForServerList({ + agent: agentName, + responseSummary: this.agentHistogramData['agentHistogram'][agentName], + load: this.agentHistogramData['agentTimeSeriesHistogram'][agentName] + })); + this.selectedAgent = agentName; + this.changeDetector.detectChanges(); + } + onOpenInspector(agentName: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_INSPECTOR_WITH_AGENT); + this.urlRouteManagerService.openPage([ + UrlPath.INSPECTOR, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + agentName + ]); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-for-filtered-map-container.component.css b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-for-filtered-map-container.component.css new file mode 100644 index 000000000000..510be65bcd4c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-for-filtered-map-container.component.css @@ -0,0 +1,113 @@ +:host { + z-index: 9; +} +.l-sidemenu2 { + position: absolute; + top: 0px; + left: 0px; + width: 461px; + border-left: 1px solid #e5e8f0; + border-right: 1px solid #e5e8f0; + background: #fff; + height: 100%; + overflow-y: auto; + overflow-x: hidden; +} +.l-title-group2 { + display: flex; + flex-flow: row wrap; + color: #666; + justify-content: space-between; + align-items: center; + padding: 0 25px; + background: none; + border: none; + height: 50px; +} +.l-selected-agent { + color: #666; + font-size: 13px; +} +.l-selected-agent button { + color: #fff; + font-size: 11px; + font-weight: 600; + display: inline-block; + background: #4b99e3; + margin: 0 12px 0 0; + padding: 6px 11px; + line-height: 1em; + position: relative; +} +.l-selected-agent button:after { + content:''; + position:absolute; + width:0; + height:0; + border-top:4px solid transparent; + border-bottom:4px solid transparent; + border-left:3px solid #4b99e3; + right:-3px; + top:50%; + transform:translateY(-50%); +} +.l-contents-group { + padding-bottom: 16px; + overflow-y: auto; + overflow-x: hidden; + margin-top: 51px; +} +.l-chart-group-list { + flex: 1; +} + +.l-sidemenu3 { + top: 0px; + left: 0px; + width: 348px; + background: #f8f9fb; + border: none; + position: absolute; + height: 100%; + overflow-y: auto; + overflow-x: hidden; +} +.l-title-group3 { + display: flex; + flex-flow: row wrap; + align-items: center; + height: 64px; + padding: 0 25px; + position: relative; +} +.l-title-group3 .fas { + font-size: 14px; + width: 33px; + height: 33px; + border-radius: 50%; + background: #4a8fd2; + color: #fff; + align-items: center; + display: flex; + justify-content: center; + margin: 0 7px 0 0; +} +.l-title-group3 .l-title { + font-size: 24px; + font-weight: 600; + color: #666; +} + + +.sidemenu { + top: 0px; + left: -461px; +} +.title-group2 { + background-color: #f9fafc; + border-bottom: 1px solid #eaeef4; +} +hr { + height: 1px; + border-top: 1px solid #EAEEF4; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-for-filtered-map-container.component.html b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-for-filtered-map-container.component.html new file mode 100644 index 000000000000..16b0da2780e9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-for-filtered-map-container.component.html @@ -0,0 +1,29 @@ +
+
+

+ + {{ selectedAgent }} +

+
+
+
+ +
+ +
+ +
+
+
+
+
+ + Servers List +
+
+ +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-for-filtered-map-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-for-filtered-map-container.component.ts new file mode 100644 index 000000000000..c60bb3062f24 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/info-per-server/info-per-server-for-filtered-map-container.component.ts @@ -0,0 +1,133 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { trigger, state, style, animate, transition } from '@angular/animations'; +import { Subject } from 'rxjs'; + +import { + StoreHelperService, + NewUrlStateNotificationService, + UrlRouteManagerService, + AnalyticsService, + TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ServerMapData } from 'app/core/components/server-map/class/server-map-data.class'; + +@Component({ + selector: 'pp-info-per-server-for-filtered-map-container', + templateUrl: './info-per-server-for-filtered-map-container.component.html', + styleUrls: ['./info-per-server-for-filtered-map-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('listAnimationTrigger', [ + state('start', style({ + left: '0px' + })), + state('end', style({ + left: '-809px' + })), + transition('* => *', [ + animate('0.2s 0.5s ease-out') + ]) + ]), + trigger('chartAnimationTrigger', [ + state('start', style({ + left: '0px' + })), + state('end', style({ + left: '-461px' + })), + transition('* => *', [ + animate('0.2s 0s ease-out') + ]) + ]), + ] +}) +export class InfoPerServerForFilteredMapContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + selectedTarget: ISelectedTarget; + serverMapData: ServerMapData; + agentHistogramData: any; + selectedAgent = ''; + listAnimationTrigger = 'start'; + chartAnimationTrigger = 'start'; + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private analyticsService: AnalyticsService, + ) {} + ngOnInit() { + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).subscribe((target: ISelectedTarget) => { + this.selectedTarget = target; + }); + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: ServerMapData) => { + this.serverMapData = serverMapData; + }); + this.storeHelperService.getInfoPerServerState(this.unsubscribe).subscribe((visibleState: boolean) => { + if (this.selectedTarget && this.selectedTarget.isNode) { + if (visibleState === true) { + const node = this.serverMapData.getNodeData(this.selectedTarget.node[0]); + this.show(); + this.agentHistogramData = { + serverList: node.serverList, + agentHistogram: node.agentHistogram, + agentTimeSeriesHistogram: node.agentTimeSeriesHistogram, + isWas: node.isWas + }; + this.changeDetector.detectChanges(); + this.storeHelperService.dispatch(new Actions.UpdateServerList(this.agentHistogramData)); + this.onSelectAgent(this.getFirstAgent()); + this.storeHelperService.dispatch(new Actions.ChangeInfoPerServerVisibleState(true)); + } else { + this.hide(); + this.changeDetector.detectChanges(); + this.storeHelperService.dispatch(new Actions.ChangeInfoPerServerVisibleState(false)); + } + } + }); + } + private hide(): void { + this.listAnimationTrigger = 'start'; + this.chartAnimationTrigger = 'start'; + } + private show(): void { + this.listAnimationTrigger = 'end'; + this.chartAnimationTrigger = 'end'; + } + isWAS(): boolean { + return this.selectedTarget.isWAS; + } + getFirstAgent(): string { + const firstKey = Object.keys(this.agentHistogramData['serverList']).sort()[0]; + return Object.keys(this.agentHistogramData['serverList'][firstKey]['instanceList']).sort()[0]; + } + onSelectAgent(agentName: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_AGENT); + this.storeHelperService.dispatch(new Actions.ChangeAgentForServerList({ + agent: agentName, + responseSummary: this.agentHistogramData['agentHistogram'][agentName], + load: this.agentHistogramData['agentTimeSeriesHistogram'][agentName] + })); + this.selectedAgent = agentName; + this.changeDetector.detectChanges(); + } + onOpenInspector(agentName: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_INSPECTOR_WITH_AGENT); + this.urlRouteManagerService.openPage([ + UrlPath.INSPECTOR, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + agentName + ]); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-container.component.html new file mode 100644 index 000000000000..15084f4f991a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Active Thread
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-container.component.ts new file mode 100644 index 000000000000..e15e4241f8f2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-container.component.ts @@ -0,0 +1,250 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { + WebAppSettingDataService, + NewUrlStateNotificationService, + AjaxExceptionCheckerService, + AnalyticsService, + StoreHelperService, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { AgentActiveThreadChartDataService } from './agent-active-thread-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-active-thread-chart-container', + templateUrl: './agent-active-thread-chart-container.component.html', + styleUrls: ['./agent-active-thread-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentActiveThreadChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentActiveThreadChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 10, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected parseData(data: number): number | null { + return data === -1 ? null : Number(data.toFixed(2)); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const fastArr = []; + const normalArr = []; + const slowArr = []; + const verySlowArr = []; + + const xData = chartData.charts.x; + const atFast = chartData.charts.y['ACTIVE_TRACE_FAST']; + const atNormal = chartData.charts.y['ACTIVE_TRACE_NORMAL']; + const atSlow = chartData.charts.y['ACTIVE_TRACE_SLOW']; + const atVerySlow = chartData.charts.y['ACTIVE_TRACE_VERY_SLOW']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if ( atFast.length === 0 ) { + continue; + } + fastArr.push(this.parseData(atFast[i][2])); + normalArr.push(this.parseData(atNormal[i][2])); + slowArr.push(this.parseData(atSlow[i][2])); + verySlowArr.push(this.parseData(atVerySlow[i][2])); + } + return { + x: xArr, + fast: fastArr, + normal: normalArr, + slow: slowArr, + verySlow: verySlowArr + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Fast', + data: data.fast, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(44, 160, 44)', + backgroundColor: 'rgba(44, 160, 44, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Normal', + data: data.normal, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(60, 129, 250)', + backgroundColor: 'rgba(60, 129, 250, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Slow', + data: data.slow, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(248, 199, 49)', + backgroundColor: 'rgba(248, 199, 49, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Very Slow', + data: data.verySlow, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(246, 145, 36)', + backgroundColor: 'rgba(246, 145, 36, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Active Thread' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Active Thread (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_ACTIVE_THREAD); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-data.service.ts new file mode 100644 index 000000000000..406a4aa90244 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-active-thread-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class AgentActiveThreadChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/activeTrace/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-container.component.html new file mode 100644 index 000000000000..deec38222b39 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-container.component.html @@ -0,0 +1,10 @@ +
+
JVM/System CPU Usage
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-container.component.ts new file mode 100644 index 000000000000..fb08ac8c7f65 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-container.component.ts @@ -0,0 +1,213 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { + WebAppSettingDataService, + NewUrlStateNotificationService, + AjaxExceptionCheckerService, + AnalyticsService, + StoreHelperService, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { AgentCPUChartDataService } from './agent-cpu-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-cpu-chart-container', + templateUrl: './agent-cpu-chart-container.component.html', + styleUrls: ['./agent-cpu-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentCPUChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentCPUChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected parseData(data: number): number | null { + return data === -1 ? null : Number(data.toFixed(2)); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const jvmArr = []; + const systemArr = []; + const maxArr = []; + + const xData = chartData.charts.x; + const cpuJVM = chartData.charts.y['CPU_LOAD_JVM']; + const cpuSystem = chartData.charts.y['CPU_LOAD_SYSTEM']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + maxArr.push(100); + if ( cpuJVM.length === 0 ) { + continue; + } + jvmArr.push(this.parseData(cpuJVM[i][1])); + systemArr.push(this.parseData(cpuSystem[i][1])); + } + return { + x: xArr, + jvm: jvmArr, + system: systemArr + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'JVM', + data: data.jvm, + fill: false, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180)', + backgroundColor: 'rgba(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'System', + data: data.system, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(174, 199, 232)', + backgroundColor: 'rgba(174, 199, 232, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'JVM/System CPU Usage' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : value.yLabel}%`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'CPU Usage (%)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return `${label}%`; + }, + min: 0, + max: this.defaultYMax, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_CPU_USAGE); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-data.service.ts new file mode 100644 index 000000000000..17c0078012b8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-cpu-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class AgentCPUChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/cpuLoad/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.css new file mode 100644 index 000000000000..8488ec0b669d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.css @@ -0,0 +1,29 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; + position: relative; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-text { + flex-basis: 70%; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.html new file mode 100644 index 000000000000..6458cf60f67c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.html @@ -0,0 +1,26 @@ +
+
+

Data Source

+ + + + + +
+ + + + + + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.ts new file mode 100644 index 000000000000..76c6f2c1b5ee --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.ts @@ -0,0 +1,275 @@ +import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { InspectorChartComponent } from './inspector-chart.component'; +import { TranslateService } from '@ngx-translate/core'; +import { filter, skip, tap } from 'rxjs/operators'; +import * as moment from 'moment-timezone'; + +import { + WebAppSettingDataService, + NewUrlStateNotificationService, + AjaxExceptionCheckerService, + AnalyticsService, + StoreHelperService, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { AgentDataSourceChartDataService, IAgentDataSourceChart } from './agent-data-source-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; + +@Component({ + selector: 'pp-agent-data-source-chart-container', + templateUrl: './agent-data-source-chart-container.component.html', + styleUrls: ['./agent-data-source-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentDataSourceChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + @ViewChild(InspectorChartComponent) inspectorChartComponent: InspectorChartComponent; + private checkedSourceDataArr: {[key: string]: any}[]; + + sourceDataArr: {[key: string]: any}[]; + infoTableObj: {[key: string]: any}; + + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentDataSourceChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 10, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initInfoTableObj(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + private initInfoTableObj(): void { + this.infoTableObj = { + activeAvg: '-', + activeMax: '-', + totalMax: '-', + id: '-', + serviceType: '-', + databaseName: '-', + jdbcUrl: '-', + }; + } + + protected initHoveredInfo(): void { + this.hoveredInfo$ = this.storeHelperService.getHoverInfo(this.unsubscribe).pipe( + skip(1), + filter(() => { + return !(!this.chartConfig || this.chartConfig.isDataEmpty); + }), + tap((hoverInfo: IHoveredInfo) => this.updateInfoTable(hoverInfo)), + ); + } + + private updateInfoTable(hoverInfo: IHoveredInfo): void { + if (hoverInfo.index !== -1) { + const activeIndex = hoverInfo.index; // x축 기준 index + const activeElements = this.inspectorChartComponent.getActiveTooltipElements(activeIndex); + const eventCord = {x1: hoverInfo.offsetX, y1: hoverInfo.offsetY}; + const distanceArr = activeElements.map((element) => this.getDistanceBetweenPoints(eventCord, {x2: element._view.x, y2: element._view.y})); + const minDistance = Math.min(...distanceArr); + const elementIndex = distanceArr.indexOf(minDistance); // element들 중 event point에 가장 가까운 element의 index + if (elementIndex !== -1) { + this.infoTableObj = { + activeAvg: this.checkedSourceDataArr[elementIndex].activeAvg[activeIndex], + activeMax: this.checkedSourceDataArr[elementIndex].activeMax[activeIndex], + totalMax: this.checkedSourceDataArr[elementIndex].totalMax[activeIndex], + id: this.checkedSourceDataArr[elementIndex].id, + serviceType: this.checkedSourceDataArr[elementIndex].serviceType, + databaseName: this.checkedSourceDataArr[elementIndex].databaseName, + jdbcUrl: this.checkedSourceDataArr[elementIndex].jdbcUrl, + }; + } + } + } + + private getDistanceBetweenPoints({x1, y1}: {x1: number, y1: number}, {x2, y2}: {x2: number, y2: number}): number { + return Math.hypot(x1 - x2, y1 - y2); + } + + protected getChartData(range: number[]): void { + this.chartDataService.getData(range) + .subscribe( + (data: IAgentDataSourceChart[] | AjaxException) => { + if (this.ajaxExceptionCheckerService.isAjaxException(data)) { + this.setErrObj(data); + } else { + this.chartData = data; + this.sourceDataArr = this.makeChartData(data); + this.setChartConfig(this.sourceDataArr); + } + }, + (err) => { + this.setErrObj(); + } + ); + } + + onCheckedIdChange(checkedIdSet: Set): void { + this.setChartConfig(this.getCheckedSourceDataArr(checkedIdSet)); + } + + private getCheckedSourceDataArr(checkedIdSet: Set): {[key: string]: any}[] { + this.checkedSourceDataArr = this.sourceDataArr.filter((sourceData) => checkedIdSet.has(sourceData.id)); + return this.checkedSourceDataArr; + } + + protected makeChartData(chartDataArr: IAgentDataSourceChart[]): {[key: string]: any}[] { + return chartDataArr.map((chartData: IAgentDataSourceChart) => { + return { + x: chartData.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + activeAvg: chartData.charts.y['ACTIVE_CONNECTION_SIZE'].map((arr: number[]) => this.parseData(arr[2])), + activeMax: chartData.charts.y['ACTIVE_CONNECTION_SIZE'].map((arr: number[]) => this.parseData(arr[1])), + totalMax: chartData.charts.y['MAX_CONNECTION_SIZE'].map((arr: number[]) => this.parseData(arr[1])), + databaseName: chartData.databaseName, + id: chartData.id, + jdbcUrl: chartData.jdbcUrl, + serviceType: chartData.serviceType, + }; + }); + } + + protected makeDataOption(data: {[key: string]: any}[]): {[key: string]: any} { + const colorMap = [ + '#850901', '#969755', '#421416', '#c8814b', '#aa8735', '#cd7af4', '#f6546a', '#1c1a1f', '#127999', '#b7ebd9', + '#f6546a', '#bea87f', '#d1b4b0', '#e0d4ba', '#0795d9', '#43aa83', '#09d05b', '#c26e67', '#ed7575', '#96686a' + ]; + const labels = this.sourceDataArr[0].x; + + return { + labels, + datasets: data.map((obj, i) => { + return { + label: 'ActiveAvg', + data: obj.activeAvg, + fill: false, + borderWidth: 0.5, + borderColor: colorMap[i], + pointRadius: 0, + pointHoverRadius: 3 + }; + }) + }; + } + + protected makeNormalOption(data: {[key: string]: any}[]): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Data Source' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return ''; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Connection (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + max: this.defaultYMax, + padding: 5 + } + }] + }, + legend: { + display: false, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_DATA_SOURCE); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-data.service.ts new file mode 100644 index 000000000000..d4f2faf570ad --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-data.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +export interface IAgentDataSourceChart extends IChartDataFromServer { + databaseName: string; + id: number; + jdbcUrl: string; + serviceType: string; +} + +@Injectable() +export class AgentDataSourceChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/dataSource/chartList.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-infotable.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-infotable.component.css new file mode 100644 index 000000000000..fd4125fa0dd1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-infotable.component.css @@ -0,0 +1,24 @@ +.l-source-info-wrapper { + background-color: #fff; + font-size: 12px; + padding: 10px; +} +.l-source-info-title { + background-color: #f6f8fb; + border-top: 1px solid #ddd; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + padding: 10px; +} +.l-source-info-table { + border: 1px solid #ddd; + width: 100%; +} +.l-source-info-table th, td { + border: 1px solid #ddd; + padding: 4px; + text-align: center; +} +.l-source-info-table th { + width: 20%; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-infotable.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-infotable.component.html new file mode 100644 index 000000000000..b9d9c7c5a61a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-infotable.component.html @@ -0,0 +1,35 @@ +
+

Data Source Info

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Active Avg{{infoTableObj.activeAvg}}
Active Max{{infoTableObj.activeMax}}
Total Max{{infoTableObj.totalMax}}
ID{{infoTableObj.id}}
Type{{infoTableObj.serviceType}}
Database Name{{infoTableObj.databaseName}}
Jdbc URL{{infoTableObj.jdbcUrl}}
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-infotable.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-infotable.component.ts new file mode 100644 index 000000000000..637927d7ff00 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-infotable.component.ts @@ -0,0 +1,14 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'pp-agent-data-source-chart-infotable', + templateUrl: './agent-data-source-chart-infotable.component.html', + styleUrls: ['./agent-data-source-chart-infotable.component.css'] +}) +export class AgentDataSourceChartInfotableComponent implements OnInit { + @Input() isDataEmpty: boolean; + @Input() infoTableObj: { [key: string]: any }; + + constructor() {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-select-source.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-select-source.component.css new file mode 100644 index 000000000000..3890e741da8d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-select-source.component.css @@ -0,0 +1,40 @@ +.l-source-select-text { + cursor: pointer; + text-align: center; + font-weight: 400; +} +.l-source-select-text > .far { + font-size: 15px; + margin-left: 3px; +} +.l-source-select-modal { + position: absolute; + top: 34px; + right: 0px; + border: 1px solid black; + width: 100%; + z-index: 1000; + padding: 15px; + background-color: #fff; + font-weight: 400; +} +.l-select-all-button { + width: 80px; + border: 1px solid #ccc; + border-radius: 3px; + padding: 5px; +} +.l-source-data-list { + margin-top: 10px; + border-top: 1px solid #ddd; + padding: 10px 0; +} +.l-source-data-list-item { + display: inline-block; +} +.l-list-item-label { + margin-right: 20px; +} +.l-list-item-input { + margin-right: 5px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-select-source.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-select-source.component.html new file mode 100644 index 000000000000..6aec8779aa3c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-select-source.component.html @@ -0,0 +1,12 @@ +

Select Source

+
+ +
    +
  • + +
  • +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-select-source.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-select-source.component.ts new file mode 100644 index 000000000000..d2239bf9d736 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-data-source-chart-select-source.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core'; + +@Component({ + selector: 'pp-agent-data-source-chart-select-source', + templateUrl: './agent-data-source-chart-select-source.component.html', + styleUrls: ['./agent-data-source-chart-select-source.component.css'] +}) +export class AgentDataSourceChartSelectSourceComponent implements OnInit, OnChanges { + @Input() isDataEmpty: boolean; + @Input() sourceDataArr: { [key: string]: any }[]; + @Output() outCheckedIdChange: EventEmitter> = new EventEmitter(); + + showSourceSelectModal = false; + checkedIdSet = new Set(); + + constructor() {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + Object.keys(changes).map((propName: string) => { + switch (propName) { + case 'sourceDataArr': + this.initCheckedIdSet(); + break; + } + }); + } + + onSourceSelectClick(): void { + this.showSourceSelectModal = !this.showSourceSelectModal; + } + + onCheckAllBtnClick(): void { + this.initCheckedIdSet(); + } + + onSourceCheckboxChange(id: number): void { + this.toggleCheckedId(id); + } + + private initCheckedIdSet(): void { + this.sourceDataArr.map((data) => this.checkedIdSet.add(data.id)); + this.outCheckedIdChange.emit(this.checkedIdSet); + } + + private toggleCheckedId(id: number): void { + this.checkedIdSet.has(id) ? this.checkedIdSet.delete(id) : this.checkedIdSet.add(id); + this.outCheckedIdChange.emit(this.checkedIdSet); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-chart-data.service.ts new file mode 100644 index 000000000000..941ce135078a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class AgentDirectBufferChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/directBuffer/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component.html new file mode 100644 index 000000000000..3c458cdafe47 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Direct Buffer Count
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component.ts new file mode 100644 index 000000000000..d95c82603910 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component.ts @@ -0,0 +1,178 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { + WebAppSettingDataService, + NewUrlStateNotificationService, + AjaxExceptionCheckerService, + AnalyticsService, + StoreHelperService, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { AgentDirectBufferChartDataService } from './agent-direct-buffer-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-direct-buffer-count-chart-container', + templateUrl: './agent-direct-buffer-count-chart-container.component.html', + styleUrls: ['./agent-direct-buffer-count-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentDirectBufferCountChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentDirectBufferChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + directCount: data.charts.y['DIRECT_COUNT'].map((arr: number[]) => this.parseData(arr[2])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + type: 'line', + label: 'Direct Buffer Count', + data: data.directCount, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180, 0.4)', + backgroundColor: 'rgb(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Direct Buffer Count' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : value.yLabel}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Buffer (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_DIRECT_BUFFER_COUNT); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component.html new file mode 100644 index 000000000000..fa23ce97c70c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Direct Buffer Memory
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component.ts new file mode 100644 index 000000000000..784c8bcf52e0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component.ts @@ -0,0 +1,190 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { AgentDirectBufferChartDataService } from 'app/core/components/inspector-chart/agent-direct-buffer-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-direct-buffer-memory-chart-container', + templateUrl: './agent-direct-buffer-memory-chart-container.component.html', + styleUrls: ['./agent-direct-buffer-memory-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentDirectBufferMemoryChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentDirectBufferChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + directMemoryUsed: data.charts.y['DIRECT_MEMORY_USED'].map((arr: number[]) => this.parseData(arr[2])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + type: 'line', + label: 'Direct Buffer Memory', + data: data.directMemoryUsed, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180, 0.4)', + backgroundColor: 'rgb(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Direct Buffer Memory' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + const hoverInfo: IHoveredInfo = { + index: event.type === 'mouseout' ? -1 : elements[0]._index + }; + if (hoverInfo.index !== -1) { + hoverInfo.offsetX = event.offsetX; + hoverInfo.offsetY = event.offsetY; + } + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts(hoverInfo)); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_DIRECT_BUFFER_MEMORY); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-heap-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-heap-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-heap-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-heap-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-heap-chart-container.component.html new file mode 100644 index 000000000000..211d0ea16cd6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-heap-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Heap Usage
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-heap-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-heap-chart-container.component.ts new file mode 100644 index 000000000000..d8558d1cb5c3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-heap-chart-container.component.ts @@ -0,0 +1,282 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { AgentMemoryChartDataService } from './agent-memory-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-jvm-heap-chart-container', + templateUrl: './agent-jvm-heap-chart-container.component.html', + styleUrls: ['./agent-jvm-heap-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentJVMHeapChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentMemoryChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected setChartConfig(data: {[key: string]: any}): void { + this.chartConfig = { + type: 'bar', + dataConfig: this.makeDataOption(data), + elseConfig: this.makeNormalOption(data), + isDataEmpty: this.isDataEmpty(data) + }; + this.changeDetector.detectChanges(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const usedArr = []; + const fgcTimeArr = []; + const fgcCountArr = []; + + const xData = chartData.charts.x; + const gcOldTime = chartData.charts.y['JVM_GC_OLD_TIME']; + const gcOldCount = chartData.charts.y['JVM_GC_OLD_COUNT']; + const memoryUsed = chartData.charts.y['JVM_MEMORY_HEAP_USED']; + const memoryMax = chartData.charts.y['JVM_MEMORY_HEAP_MAX']; + const dataCount = xData.length; + + let totalSumGCTime = 0; + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if ( memoryMax.length === 0 ) { + continue; + } + maxArr.push(this.parseData(memoryMax[i][1])); + usedArr.push(this.parseData(memoryUsed[i][1])); + + const gcOldCountSumValue = gcOldCount[i][3]; + const gcOldTimeSumValue = gcOldTime[i][3]; + + if ( gcOldTimeSumValue > 0 ) { + totalSumGCTime += gcOldTimeSumValue; + } + if ( gcOldCountSumValue > 0 ) { + fgcTimeArr.push(totalSumGCTime); + fgcCountArr.push(gcOldCountSumValue); + totalSumGCTime = 0; + } else { + fgcTimeArr.push(0); + fgcCountArr.push(0); + } + } + return { + x: xArr, + max: maxArr, + used: usedArr, + fgcTime: fgcTimeArr, + fgcCount: fgcCountArr + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + type: 'line', + label: 'Max', + data: data.max, + fill: false, + borderWidth: 0.5, + borderColor: 'rgb(174, 199, 232)', + backgroundColor: 'rgb(174, 199, 232)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + type: 'line', + label: 'Used', + data: data.used, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180)', + backgroundColor: 'rgba(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + type: 'bar', + label: 'FGC', + data: data.fgcTime, + borderWidth: 1, + borderColor: 'rgb(255, 42, 0)', + backgroundColor: 'rgba(255, 42, 0, 0.3)', + // pointRadius: 0, + // pointHoverRadius: 3, + yAxisID: 'y-axis-2' + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Heap Usage' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + id: 'y-axis-1', + display: true, + position: 'left', + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }, + { + id: 'y-axis-2', + display: true, + position: 'right', + scaleLabel: { + display: true, + labelString: 'Full GC (ms)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_HEAP); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-non-heap-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-non-heap-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-non-heap-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-non-heap-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-non-heap-chart-container.component.html new file mode 100644 index 000000000000..e908e6f83fb3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-non-heap-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Non Heap Usage
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-non-heap-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-non-heap-chart-container.component.ts new file mode 100644 index 000000000000..e1d26871012a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-jvm-non-heap-chart-container.component.ts @@ -0,0 +1,282 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { AgentMemoryChartDataService } from './agent-memory-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-jvm-non-heap-chart-container', + templateUrl: './agent-jvm-non-heap-chart-container.component.html', + styleUrls: ['./agent-jvm-non-heap-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentJVMNonHeapChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentMemoryChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected setChartConfig(data: {[key: string]: any}): void { + this.chartConfig = { + type: 'bar', + dataConfig: this.makeDataOption(data), + elseConfig: this.makeNormalOption(data), + isDataEmpty: this.isDataEmpty(data) + }; + this.changeDetector.detectChanges(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const usedArr = []; + const fgcTimeArr = []; + const fgcCountArr = []; + + const xData = chartData.charts.x; + const gcOldTime = chartData.charts.y['JVM_GC_OLD_TIME']; + const gcOldCount = chartData.charts.y['JVM_GC_OLD_COUNT']; + const memoryUsed = chartData.charts.y['JVM_MEMORY_NON_HEAP_USED']; + const memoryMax = chartData.charts.y['JVM_MEMORY_NON_HEAP_MAX']; + const dataCount = xData.length; + + let totalSumGCTime = 0; + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if ( memoryMax.length === 0 ) { + continue; + } + maxArr.push(this.parseData(memoryMax[i][1])); + usedArr.push(this.parseData(memoryUsed[i][1])); + + const gcOldCountSumValue = gcOldCount[i][3]; + const gcOldTimeSumValue = gcOldTime[i][3]; + + if ( gcOldTimeSumValue > 0 ) { + totalSumGCTime += gcOldTimeSumValue; + } + if ( gcOldCountSumValue > 0 ) { + fgcTimeArr.push(totalSumGCTime); + fgcCountArr.push(gcOldCountSumValue); + totalSumGCTime = 0; + } else { + fgcTimeArr.push(0); + fgcCountArr.push(0); + } + } + return { + x: xArr, + max: maxArr, + used: usedArr, + fgcTime: fgcTimeArr, + fgcCount: fgcCountArr + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + type: 'line', + label: 'Max', + data: data.max, + fill: false, + borderWidth: 0.5, + borderColor: 'rgb(174, 199, 232)', + backgroundColor: 'rgb(174, 199, 232)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + type: 'line', + label: 'Used', + data: data.used, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180)', + backgroundColor: 'rgba(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + type: 'bar', + label: 'FGC', + data: data.fgcTime, + borderWidth: 1, + borderColor: 'rgb(255, 42, 0)', + backgroundColor: 'rgba(255, 42, 0, 0.3)', + // pointRadius: 0, + // pointHoverRadius: 3, + yAxisID: 'y-axis-2' + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Heap Usage' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: any[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + id: 'y-axis-1', + display: true, + position: 'left', + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }, + { + id: 'y-axis-2', + display: true, + position: 'right', + scaleLabel: { + display: true, + labelString: 'Full GC (ms)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_NON_HEAP); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component.html new file mode 100644 index 000000000000..08c6f6f0da5d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Mapped Buffer Count
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component.ts new file mode 100644 index 000000000000..ee07f7dcc86d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component.ts @@ -0,0 +1,171 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { AgentDirectBufferChartDataService } from './agent-direct-buffer-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-mapped-buffer-count-chart-container', + templateUrl: './agent-mapped-buffer-count-chart-container.component.html', + styleUrls: ['./agent-mapped-buffer-count-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentMappedBufferCountChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentDirectBufferChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + mappedCount: data.charts.y['MAPPED_COUNT'].map((arr: number[]) => this.parseData(arr[2])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + type: 'line', + label: 'Mapped Buffer Count', + data: data.mappedCount, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180, 0.4)', + backgroundColor: 'rgb(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Mapped Buffer Count' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : value.yLabel}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Buffer (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_MAPPED_BUFFER_COUNT); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component.html new file mode 100644 index 000000000000..59a32928a273 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Mapped Buffer Memory
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component.ts new file mode 100644 index 000000000000..8cb754a9ad1e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component.ts @@ -0,0 +1,187 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { AgentDirectBufferChartDataService } from './agent-direct-buffer-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-mapped-buffer-memory-chart-container', + templateUrl: './agent-mapped-buffer-memory-chart-container.component.html', + styleUrls: ['./agent-mapped-buffer-memory-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentMappedBufferMemoryChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentDirectBufferChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + mappedMemoryUsed: data.charts.y['MAPPED_MEMORY_USED'].map((arr: number[]) => this.parseData(arr[2])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + type: 'line', + label: 'Mapped Buffer Memory', + data: data.mappedMemoryUsed, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180, 0.4)', + backgroundColor: 'rgb(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Mapped Buffer Memory' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_MAPPED_BUFFER_MEMORY); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-memory-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-memory-chart-data.service.ts new file mode 100644 index 000000000000..a28da256264d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-memory-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class AgentMemoryChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/jvmGc/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component.html new file mode 100644 index 000000000000..b62d6fb4a1a2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Open File Descriptor
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component.ts new file mode 100644 index 000000000000..7b5730252ac0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component.ts @@ -0,0 +1,171 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { AgentOpenFileDescriptorChartDataService } from './agent-open-file-descriptor-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-open-file-descriptor-chart-container', + templateUrl: './agent-open-file-descriptor-chart-container.component.html', + styleUrls: ['./agent-open-file-descriptor-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentOpenFileDescriptorChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentOpenFileDescriptorChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + openFileDescriptorCount: data.charts.y['OPEN_FILE_DESCRIPTOR_COUNT'].map((arr: number[]) => this.parseData(arr[2])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + type: 'line', + label: 'Open File Descriptor', + data: data.openFileDescriptorCount, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180, 0.4)', + backgroundColor: 'rgb(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Open File Descriptor' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : value.yLabel}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'File Descriptor (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_OPEN_FILE_DESCRIPTOR); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-data.service.ts new file mode 100644 index 000000000000..b3de4039e22e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-open-file-descriptor-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class AgentOpenFileDescriptorChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/fileDescriptor/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-container.component.html new file mode 100644 index 000000000000..971df60336df --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Response Time
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-container.component.ts new file mode 100644 index 000000000000..2f2df851c60c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-container.component.ts @@ -0,0 +1,214 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { AgentResponseTimeChartDataService } from './agent-response-time-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-response-time-chart-container', + templateUrl: './agent-response-time-chart-container.component.html', + styleUrls: ['./agent-response-time-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentResponseTimeChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentResponseTimeChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const avgArr = []; + const maxArr = []; + + const xData = chartData.charts.x; + const responseAVG = chartData.charts.y['AVG']; + const responseMAX = chartData.charts.y['MAX']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if ( responseAVG.length === 0 ) { + continue; + } + avgArr.push(this.parseData(responseAVG[i][2])); + maxArr.push(this.parseData(responseMAX[i][1])); + } + return { + x: xArr, + avg: avgArr, + max: maxArr, + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Avg', + data: data.avg, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(44, 160, 44)', + backgroundColor: 'rgba(44, 160, 44, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.max, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(246, 145, 36)', + backgroundColor: 'rgba(246, 145, 36, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + } + ] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Response Time' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Response Time (ms)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + private convertWithUnit(value: number): string { + const unit = ['ms', 'sec', 'min']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_RESPONSE_TIME); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-data.service.ts new file mode 100644 index 000000000000..6b54b84b4003 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-response-time-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class AgentResponseTimeChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/responseTime/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-container.component.html new file mode 100644 index 000000000000..6252a5c933ff --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Transactions Per Second
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-container.component.ts new file mode 100644 index 000000000000..a0c4af7a8c64 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-container.component.ts @@ -0,0 +1,251 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { AgentTPSChartDataService } from './agent-tps-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-agent-tps-chart-container', + templateUrl: './agent-tps-chart-container.component.html', + styleUrls: ['./agent-tps-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentTPSChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: AgentTPSChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 10, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected parseData(data: number): number | null { + return data < 0 ? null : Number(data.toFixed(2)); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const tpsSCArr = []; + const tpsSNArr = []; + const tpsUCArr = []; + const tpsUNArr = []; + const tpsTArr = []; + + const xData = chartData.charts.x; + const tpsSC = chartData.charts.y['TPS_SAMPLED_CONTINUATION']; + const tpsSN = chartData.charts.y['TPS_SAMPLED_NEW']; + const tpsUC = chartData.charts.y['TPS_UNSAMPLED_CONTINUATION']; + const tpsUN = chartData.charts.y['TPS_UNSAMPLED_NEW']; + const tpsT = chartData.charts.y['TPS_TOTAL']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if ( tpsSC.length === 0 ) { + continue; + } + tpsSCArr.push(this.parseData(tpsSC[i][2])); + tpsSNArr.push(this.parseData(tpsSN[i][2])); + tpsUCArr.push(this.parseData(tpsUC[i][2])); + tpsUNArr.push(this.parseData(tpsUN[i][2])); + if ( tpsT ) { + tpsTArr.push(this.parseData(tpsT[i][2])); + } else { + tpsTArr.push(this.parseData((tpsSC[i][2] + tpsSN[i][2] + tpsUC[i][2] + tpsUN[i][2]))); + } + } + return { + x: xArr, + tpsSC: tpsSCArr, + tpsSN: tpsSNArr, + tpsUC: tpsUCArr, + tpsUN: tpsUNArr, + tpsT: tpsTArr + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'S.C', + data: data.tpsSC, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(214, 141, 8)', + backgroundColor: 'rgba(214, 141, 8, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'S.N', + data: data.tpsSN, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(252, 178, 65)', + backgroundColor: 'rgba(252, 178, 65, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'U.C', + data: data.tpsUC, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(90, 103, 166)', + backgroundColor: 'rgba(90, 103, 166, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'U.N', + data: data.tpsUN, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(160, 153, 255)', + backgroundColor: 'rgba(160, 153, 255, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Total', + data: data.tpsT, + fill: false, + borderWidth: 0.5, + // borderColor: 'rgb(31, 119, 180)', + // backgroundColor: 'rgba(31, 119, 180, 0.4)', + borderColor: 'rgba(31, 119, 180, 0)', + backgroundColor: 'rgba(255, 255, 255, 0)', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'TPS' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : value.yLabel.toFixed(1)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + stacked: true, + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + stacked: true, + display: true, + scaleLabel: { + display: true, + labelString: 'Transaction (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (value: number): number => { + const label = Number.isInteger(value) ? value : Number(value.toFixed(1)); + return label; + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.AGENT_TPS); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-data.service.ts new file mode 100644 index 000000000000..cf9ab9c7a108 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/agent-tps-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class AgentTPSChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/transaction/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-container.component.html new file mode 100644 index 000000000000..15084f4f991a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Active Thread
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-container.component.ts new file mode 100644 index 000000000000..518db2213b87 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-container.component.ts @@ -0,0 +1,237 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationActiveThreadChartDataService } from './application-active-thread-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-active-thread-chart-container', + templateUrl: './application-active-thread-chart-container.component.html', + styleUrls: ['./application-active-thread-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationActiveThreadChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationActiveThreadChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 10, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const minArr = []; + const avgArr = []; + const maxAgentIdArr = []; + const minAgentIdArr = []; + + const xData = chartData.charts.x; + const activeTraceCount = chartData.charts.y['ACTIVE_TRACE_COUNT']; + const dataCount = xData.length; + + for (let i = 0; i < dataCount; i++) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if (activeTraceCount[i]) { + minArr.push(this.parseData(activeTraceCount[i][0])); + minAgentIdArr.push(activeTraceCount[i][1]); + maxArr.push(this.parseData(activeTraceCount[i][2])); + maxAgentIdArr.push(activeTraceCount[i][3]); + avgArr.push(this.parseData(activeTraceCount[i][4])); + } + } + return { + x: xArr, + maxArr, + minArr, + avgArr, + minAgentIdArr, + maxAgentIdArr, + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]) => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel) + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Active Thread (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_ACTIVE_THREAD); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-data.service.ts new file mode 100644 index 000000000000..6d62567d73dc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-active-thread-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForApplicationChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class ApplicationActiveThreadChartDataService implements IChartDataService { + private requestURL = 'getApplicationStat/activeTrace/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForApplicationChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-cpu-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-cpu-chart-data.service.ts new file mode 100644 index 000000000000..8ba0b3875e25 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-cpu-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForApplicationChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class ApplicationCPUChartDataService implements IChartDataService { + private requestURL = 'getApplicationStat/cpuLoad/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForApplicationChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-container.component.html new file mode 100644 index 000000000000..3bd19ce8b449 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-container.component.html @@ -0,0 +1,20 @@ +
+
+

Data Source

+ +
+ + + + + + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-container.component.ts new file mode 100644 index 000000000000..d458283ff423 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-container.component.ts @@ -0,0 +1,227 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationDataSourceChartDataService, IApplicationDataSourceChart } from './application-data-source-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; + +@Component({ + selector: 'pp-application-data-source-chart-container', + templateUrl: './application-data-source-chart-container.component.html', + styleUrls: ['./application-data-source-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationDataSourceChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + sourceDataArr: {[key: string]: any}[]; + + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationDataSourceChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 10, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected getChartData(range: number[]): void { + this.chartDataService.getData(range) + .subscribe( + (data: IApplicationDataSourceChart[] | AjaxException) => { + if (this.ajaxExceptionCheckerService.isAjaxException(data)) { + this.setErrObj(data); + } else { + this.chartData = data; + this.sourceDataArr = this.makeChartData(data); + this.setChartConfig(this.sourceDataArr[0]); + } + }, + (err) => { + this.setErrObj(); + } + ); + } + + onSourceDataSelected(index: number): void { + this.setChartConfig(this.sourceDataArr[index]); + } + + protected makeChartData(chartDataArr: IApplicationDataSourceChart[]): {[key: string]: any}[] { + return chartDataArr.map((chartData: IApplicationDataSourceChart) => { + return { + x: chartData.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + minArr: chartData.charts.y['ACTIVE_CONNECTION_SIZE'].map((arr: any[]) => this.parseData(arr[0])), + minAgentIdArr: chartData.charts.y['ACTIVE_CONNECTION_SIZE'].map((arr: any[]) => arr[1]), + maxArr: chartData.charts.y['ACTIVE_CONNECTION_SIZE'].map((arr: any[]) => this.parseData(arr[2])), + maxAgentIdArr: chartData.charts.y['ACTIVE_CONNECTION_SIZE'].map((arr: any[]) => arr[3]), + avgArr: chartData.charts.y['ACTIVE_CONNECTION_SIZE'].map((arr: any[]) => this.parseData(arr[4])), + jdbcUrl: chartData.jdbcUrl, + serviceType: chartData.serviceType + }; + }); + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]) => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : value.yLabel + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Connection (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + max: this.defaultYMax, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_DATA_SOURCE); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-data.service.ts new file mode 100644 index 000000000000..02696dc879db --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-data.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForApplicationChartData } from 'app/core/utils/chart-data-param-maker'; + +export interface IApplicationDataSourceChart extends IChartDataFromServer { + jdbcUrl: string; + serviceType: string; +} + +@Injectable() +export class ApplicationDataSourceChartDataService implements IChartDataService { + private requestURL = 'getApplicationStat/dataSource/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForApplicationChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-soucelist.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-soucelist.component.css new file mode 100644 index 000000000000..6ef246b6f6fc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-soucelist.component.css @@ -0,0 +1,55 @@ +.l-sourcelist-wrapper { + background-color: #fff; + font-size: 12px; + padding: 10px; +} +.l-sourcelist-title { + background-color: #f6f8fb; + border-top: 1px solid #ddd; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + padding: 10px; +} +.l-source-data-list { + border: 1px solid #ddd; +} +.l-source-data-list-item { + cursor: pointer; + color: #ddd; + display: flex; + align-items: center; +} +.l-source-data-list-item:hover { + background-color: #ddd; + color: #fff; +} +.l-selected-item { + color: #000; + font-weight: 600; +} +.l-selected-item:hover { + background-color: #fff; + color: #000; +} +.l-source-data-list-item .fas { + width: 7%; + text-align: center; +} +.l-source-data-wrapper { + display: flex; + justify-content: space-evenly; + align-items: center; + border-left: 1px solid #ddd; + padding: 7px 0 7px 7px; + width: 93%; +} +.l-service-type-wrapper { + display: inline-block; + width: 10%; + overflow-wrap: break-word; +} +.l-jdbc-url-wrapper { + display: inline-block; + width: 85%; + overflow-wrap: break-word; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-soucelist.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-soucelist.component.html new file mode 100644 index 000000000000..7cf145ba3eed --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-soucelist.component.html @@ -0,0 +1,14 @@ +
+

Data Source List

+
    +
  • + +
    + {{sourceData.serviceType}} + {{sourceData.jdbcUrl}} +
    +
  • +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-soucelist.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-soucelist.component.ts new file mode 100644 index 000000000000..bbd9bb7849c5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-data-source-chart-soucelist.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core'; + +@Component({ + selector: 'pp-application-data-source-chart-sourcelist', + templateUrl: './application-data-source-chart-soucelist.component.html', + styleUrls: ['./application-data-source-chart-soucelist.component.css'] +}) +export class ApplicationDataSourceChartSourcelistComponent implements OnInit, OnChanges { + @Input() isDataEmpty: boolean; + @Input() sourceDataArr: { [key: string]: any }[]; + @Output() outSourceDataSelected: EventEmitter = new EventEmitter(); + + private selectedIndex = 0; + + constructor() {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + Object.keys(changes).map((propName: string) => { + switch (propName) { + case 'sourceDataArr': + this.initSelectedIndex(); + break; + } + }); + } + + private initSelectedIndex(): void { + this.selectedIndex = 0; + } + + isItemSelected(index: number): boolean { + return index === this.selectedIndex; + } + + private selectSource(index: number): void { + this.selectedIndex = index; + this.outSourceDataSelected.emit(this.selectedIndex); + } + + onClickSourceList(index: number): void { + if (index !== this.selectedIndex) { + this.selectSource(index); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-chart-data.service.ts new file mode 100644 index 000000000000..39904b93c3bd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForApplicationChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class ApplicationDirectBufferChartDataService implements IChartDataService { + private requestURL = 'getApplicationStat/directBuffer/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForApplicationChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component.html new file mode 100644 index 000000000000..3f86665e24aa --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Direct Buffer Count
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component.ts new file mode 100644 index 000000000000..a0b27c55ca0c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component.ts @@ -0,0 +1,201 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationDirectBufferChartDataService } from './application-direct-buffer-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-direct-buffer-count-chart-container', + templateUrl: './application-direct-buffer-count-chart-container.component.html', + styleUrls: ['./application-direct-buffer-count-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationDirectBufferCountChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationDirectBufferChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + minArr: data.charts.y['DIRECT_COUNT'].map((arr: any[]) => this.parseData(arr[0])), + minAgentIdArr: data.charts.y['DIRECT_COUNT'].map((arr: any[]) => arr[1]), + maxArr: data.charts.y['DIRECT_COUNT'].map((arr: any[]) => this.parseData(arr[2])), + maxAgentIdArr: data.charts.y['DIRECT_COUNT'].map((arr: any[]) => arr[3]), + avgArr: data.charts.y['DIRECT_COUNT'].map((arr: any[]) => this.parseData(arr[4])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Direct Buffer Count' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : value.yLabel + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Buffer (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_DIRECT_BUFFER_COUNT); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component.html new file mode 100644 index 000000000000..67cc6afb6bc1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Direct Buffer Memory
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component.ts new file mode 100644 index 000000000000..354ea3fc18ac --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component.ts @@ -0,0 +1,217 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationDirectBufferChartDataService } from './application-direct-buffer-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-direct-buffer-memory-chart-container', + templateUrl: './application-direct-buffer-memory-chart-container.component.html', + styleUrls: ['./application-direct-buffer-memory-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationDirectBufferMemoryChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationDirectBufferChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + minArr: data.charts.y['DIRECT_MEMORY_USED'].map((arr: any[]) => this.parseData(arr[0])), + minAgentIdArr: data.charts.y['DIRECT_MEMORY_USED'].map((arr: any[]) => arr[1]), + maxArr: data.charts.y['DIRECT_MEMORY_USED'].map((arr: any[]) => this.parseData(arr[2])), + maxAgentIdArr: data.charts.y['DIRECT_MEMORY_USED'].map((arr: any[]) => arr[3]), + avgArr: data.charts.y['DIRECT_MEMORY_USED'].map((arr: any[]) => this.parseData(arr[4])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Direct Buffer Memory' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel) + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_DIRECT_BUFFER_MEMORY); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-cpu-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-cpu-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-cpu-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-cpu-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-cpu-chart-container.component.html new file mode 100644 index 000000000000..d2b8171c754f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-cpu-chart-container.component.html @@ -0,0 +1,10 @@ +
+
JVM CPU Usage
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-cpu-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-cpu-chart-container.component.ts new file mode 100644 index 000000000000..3a974a0b35a9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-cpu-chart-container.component.ts @@ -0,0 +1,224 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationCPUChartDataService } from './application-cpu-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-jvm-cpu-chart-container', + templateUrl: './application-jvm-cpu-chart-container.component.html', + styleUrls: ['./application-jvm-cpu-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationJVMCPUChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationCPUChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const minArr = []; + const avgArr = []; + const maxAgentIdArr = []; + const minAgentIdArr = []; + + const xData = chartData.charts.x; + const cpuLoadJVM = chartData.charts.y['CPU_LOAD_JVM']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if (cpuLoadJVM[i]) { + minArr.push(this.parseData(cpuLoadJVM[i][0])); + minAgentIdArr.push(cpuLoadJVM[i][1]); + maxArr.push(this.parseData(cpuLoadJVM[i][2])); + maxAgentIdArr.push(cpuLoadJVM[i][3]); + avgArr.push(this.parseData(cpuLoadJVM[i][4])); + } + } + return { + x: xArr, + maxArr, + minArr, + avgArr, + minAgentIdArr, + maxAgentIdArr, + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]) => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : value.yLabel + `% ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'CPU Usage (%)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return `${label}%`; + }, + min: 0, + max: this.defaultYMax, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_JVM_CPU_USAGE); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-heap-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-heap-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-heap-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-heap-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-heap-chart-container.component.html new file mode 100644 index 000000000000..211d0ea16cd6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-heap-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Heap Usage
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-heap-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-heap-chart-container.component.ts new file mode 100644 index 000000000000..ff9f6a1d0efe --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-heap-chart-container.component.ts @@ -0,0 +1,239 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationMemoryChartDataService } from './application-memory-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-jvm-heap-chart-container', + templateUrl: './application-jvm-heap-chart-container.component.html', + styleUrls: ['./application-jvm-heap-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationJVMHeapChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationMemoryChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100000, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const minArr = []; + const avgArr = []; + const maxAgentIdArr = []; + const minAgentIdArr = []; + + const xData = chartData.charts.x; + const memoryHeap = chartData.charts.y['MEMORY_HEAP']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if (memoryHeap[i]) { + minArr.push(this.parseData(memoryHeap[i][0])); + minAgentIdArr.push(memoryHeap[i][1]); + maxArr.push(this.parseData(memoryHeap[i][2])); + maxAgentIdArr.push(memoryHeap[i][3]); + avgArr.push(this.parseData(memoryHeap[i][4])); + } + } + + return { + x: xArr, + maxArr, + minArr, + avgArr, + minAgentIdArr, + maxAgentIdArr, + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]) => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel) + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_HEAP); + } +} + diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-non-heap-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-non-heap-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-non-heap-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-non-heap-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-non-heap-chart-container.component.html new file mode 100644 index 000000000000..e908e6f83fb3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-non-heap-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Non Heap Usage
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-non-heap-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-non-heap-chart-container.component.ts new file mode 100644 index 000000000000..4c8e5625c745 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-jvm-non-heap-chart-container.component.ts @@ -0,0 +1,237 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationMemoryChartDataService } from './application-memory-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-jvm-non-heap-chart-container', + templateUrl: './application-jvm-non-heap-chart-container.component.html', + styleUrls: ['./application-jvm-non-heap-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationJVMNonHeapChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationMemoryChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100000, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const minArr = []; + const avgArr = []; + const maxAgentIdArr = []; + const minAgentIdArr = []; + + const xData = chartData.charts.x; + const memoryNonHeap = chartData.charts.y['MEMORY_NON_HEAP']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if (memoryNonHeap[i]) { + minArr.push(this.parseData(memoryNonHeap[i][0])); + minAgentIdArr.push(memoryNonHeap[i][1]); + maxArr.push(this.parseData(memoryNonHeap[i][2])); + maxAgentIdArr.push(memoryNonHeap[i][3]); + avgArr.push(this.parseData(memoryNonHeap[i][4])); + } + } + return { + x: xArr, + maxArr, + minArr, + avgArr, + minAgentIdArr, + maxAgentIdArr, + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]) => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel) + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_NON_HEAP); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component.html new file mode 100644 index 000000000000..879857bea4b6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Mapped Buffer Count
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component.ts new file mode 100644 index 000000000000..8538ca9bbdf9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component.ts @@ -0,0 +1,201 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationDirectBufferChartDataService } from './application-direct-buffer-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-mapped-buffer-count-chart-container', + templateUrl: './application-mapped-buffer-count-chart-container.component.html', + styleUrls: ['./application-mapped-buffer-count-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationMappedBufferCountChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationDirectBufferChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + minArr: data.charts.y['MAPPED_COUNT'].map((arr: any[]) => this.parseData(arr[0])), + minAgentIdArr: data.charts.y['MAPPED_COUNT'].map((arr: any[]) => arr[1]), + maxArr: data.charts.y['MAPPED_COUNT'].map((arr: any[]) => this.parseData(arr[2])), + maxAgentIdArr: data.charts.y['MAPPED_COUNT'].map((arr: any[]) => arr[3]), + avgArr: data.charts.y['MAPPED_COUNT'].map((arr: any[]) => this.parseData(arr[4])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Mapped Buffer Count' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : value.yLabel + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Buffer (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_MAPPED_BUFFER_COUNT); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component.html new file mode 100644 index 000000000000..afb8bb19c95f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Mapped Buffer Memory
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component.ts new file mode 100644 index 000000000000..1b77c92b5279 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component.ts @@ -0,0 +1,217 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationDirectBufferChartDataService } from './application-direct-buffer-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-mapped-buffer-memory-chart-container', + templateUrl: './application-mapped-buffer-memory-chart-container.component.html', + styleUrls: ['./application-mapped-buffer-memory-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationMappedBufferMemoryChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationDirectBufferChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + minArr: data.charts.y['MAPPED_MEMORY_USED'].map((arr: any[]) => this.parseData(arr[0])), + minAgentIdArr: data.charts.y['MAPPED_MEMORY_USED'].map((arr: any[]) => arr[1]), + maxArr: data.charts.y['MAPPED_MEMORY_USED'].map((arr: any[]) => this.parseData(arr[2])), + maxAgentIdArr: data.charts.y['MAPPED_MEMORY_USED'].map((arr: any[]) => arr[3]), + avgArr: data.charts.y['MAPPED_MEMORY_USED'].map((arr: any[]) => this.parseData(arr[4])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Mapped Buffer Memory' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel) + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_MAPPED_BUFFER_MEMORY); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-memory-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-memory-chart-data.service.ts new file mode 100644 index 000000000000..45aec21ffb32 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-memory-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForApplicationChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class ApplicationMemoryChartDataService implements IChartDataService { + private requestURL = 'getApplicationStat/memory/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForApplicationChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component.html new file mode 100644 index 000000000000..b62d6fb4a1a2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Open File Descriptor
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component.ts new file mode 100644 index 000000000000..f667613a5b7b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component.ts @@ -0,0 +1,201 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationOpenFileDescriptorChartDataService } from './application-open-file-descriptor-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-open-file-descriptor-chart-container', + templateUrl: './application-open-file-descriptor-chart-container.component.html', + styleUrls: ['./application-open-file-descriptor-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationOpenFileDescriptorChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationOpenFileDescriptorChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(data: IChartDataFromServer): {[key: string]: any} { + return { + x: data.charts.x.map((time: number) => moment(time).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(time).tz(this.timezone).format(this.dateFormat[1])), + minArr: data.charts.y['OPEN_FILE_DESCRIPTOR_COUNT'].map((arr: any[]) => this.parseData(arr[0])), + minAgentIdArr: data.charts.y['OPEN_FILE_DESCRIPTOR_COUNT'].map((arr: any[]) => arr[1]), + maxArr: data.charts.y['OPEN_FILE_DESCRIPTOR_COUNT'].map((arr: any[]) => this.parseData(arr[2])), + maxAgentIdArr: data.charts.y['OPEN_FILE_DESCRIPTOR_COUNT'].map((arr: any[]) => arr[3]), + avgArr: data.charts.y['OPEN_FILE_DESCRIPTOR_COUNT'].map((arr: any[]) => this.parseData(arr[4])), + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false, + text: 'Open File Descriptor' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : value.yLabel + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'File Descriptor (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_OPEN_FILE_DESCRIPTOR); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-data.service.ts new file mode 100644 index 000000000000..61fd5a42f510 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-open-file-descriptor-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForApplicationChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class ApplicationOpenFileDescriptorChartDataService implements IChartDataService { + private requestURL = 'getApplicationStat/fileDescriptor/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForApplicationChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-container.component.html new file mode 100644 index 000000000000..971df60336df --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Response Time
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-container.component.ts new file mode 100644 index 000000000000..60f92a3025d0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-container.component.ts @@ -0,0 +1,237 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationResponseTimeChartDataService } from './application-response-time-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-response-time-chart-container', + templateUrl: './application-response-time-chart-container.component.html', + styleUrls: ['./application-response-time-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationResponseTimeChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationResponseTimeChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const minArr = []; + const avgArr = []; + const maxAgentIdArr = []; + const minAgentIdArr = []; + + const xData = chartData.charts.x; + const responseTime = chartData.charts.y['RESPONSE_TIME']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if (responseTime[i]) { + minArr.push(this.parseData(responseTime[i][0])); + minAgentIdArr.push(responseTime[i][1]); + maxArr.push(this.parseData(responseTime[i][2])); + maxAgentIdArr.push(responseTime[i][3]); + avgArr.push(this.parseData(responseTime[i][4])); + } + } + return { + x: xArr, + maxArr: maxArr, + minArr: minArr, + avgArr: avgArr, + minAgentIdArr, + maxAgentIdArr, + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]) => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel) + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Response Time (ms)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + private convertWithUnit(value: number): string { + const unit = ['ms', 'sec', 'min']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_RESPONSE_TIME); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-data.service.ts new file mode 100644 index 000000000000..4d02852df4b2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-response-time-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForApplicationChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class ApplicationResponseTimeChartDataService implements IChartDataService { + private requestURL = 'getApplicationStat/responseTime/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForApplicationChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-system-cpu-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-system-cpu-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-system-cpu-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-system-cpu-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-system-cpu-chart-container.component.html new file mode 100644 index 000000000000..b778cd386178 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-system-cpu-chart-container.component.html @@ -0,0 +1,10 @@ +
+
System CPU Usage
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-system-cpu-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-system-cpu-chart-container.component.ts new file mode 100644 index 000000000000..9543c7ea8ea9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-system-cpu-chart-container.component.ts @@ -0,0 +1,224 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationCPUChartDataService } from './application-cpu-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-system-cpu-chart-container', + templateUrl: './application-system-cpu-chart-container.component.html', + styleUrls: ['./application-system-cpu-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationSystemCPUChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationCPUChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const minArr = []; + const avgArr = []; + const maxAgentIdArr = []; + const minAgentIdArr = []; + + const xData = chartData.charts.x; + const cpuLoadSystem = chartData.charts.y['CPU_LOAD_SYSTEM']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if (cpuLoadSystem[i]) { + minArr.push(this.parseData(cpuLoadSystem[i][0])); + minAgentIdArr.push(cpuLoadSystem[i][1]); + maxArr.push(this.parseData(cpuLoadSystem[i][2])); + maxAgentIdArr.push(cpuLoadSystem[i][3]); + avgArr.push(this.parseData(cpuLoadSystem[i][4])); + } + } + return { + x: xArr, + maxArr: maxArr, + minArr: minArr, + avgArr: avgArr, + minAgentIdArr, + maxAgentIdArr, + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]) => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : value.yLabel + `% ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'CPU Usage (%)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return `${label}%`; + }, + min: 0, + max: this.defaultYMax, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_SYSTEM_CPU_USAGE); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-container.component.css new file mode 100644 index 000000000000..7e076f488d6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-container.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + width: calc(50% - 20px); + margin: 10px; +} +.l-content-item { + width: 100%; + margin: 0px; +} +.l-title-group { + height:34px; + font-size:13px; + font-weight:600; + padding:0 20px; + color:#333; + display: flex; + align-items: center; + justify-content:space-between; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color:#a8acb5; + cursor:pointer; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-container.component.html new file mode 100644 index 000000000000..6252a5c933ff --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-container.component.html @@ -0,0 +1,10 @@ +
+
Transactions Per Second
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-container.component.ts new file mode 100644 index 000000000000..b78553df5255 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-container.component.ts @@ -0,0 +1,237 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { ApplicationTPSChartDataService } from './application-tps-chart-data.service'; +import { HELP_VIEWER_LIST } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { InspectorChartContainer } from 'app/core/components/inspector-chart/inspector-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-application-tps-chart-container', + templateUrl: './application-tps-chart-container.component.html', + styleUrls: ['./application-tps-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationTPSChartContainerComponent extends InspectorChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: ApplicationTPSChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + analyticsService: AnalyticsService, + dynamicPopupService: DynamicPopupService + ) { + super( + 10, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + analyticsService, + dynamicPopupService + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initTimezoneAndDateFormat(); + this.initChartData(); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const minArr = []; + const avgArr = []; + const maxAgentIdArr = []; + const minAgentIdArr = []; + + const xData = chartData.charts.x; + const transactionCount = chartData.charts.y['TRANSACTION_COUNT']; + const dataCount = xData.length; + + for (let i = 0; i < dataCount; i++) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if (transactionCount[i]) { + minArr.push(this.parseData(transactionCount[i][0])); + minAgentIdArr.push(transactionCount[i][1]); + maxArr.push(this.parseData(transactionCount[i][2])); + maxAgentIdArr.push(transactionCount[i][3]); + avgArr.push(this.parseData(transactionCount[i][4])); + } + } + return { + x: xArr, + maxArr: maxArr, + minArr: minArr, + avgArr: avgArr, + minAgentIdArr, + maxAgentIdArr, + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'Min', + data: data.minArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#66B2FF', + backgroundColor: '#66B2FF', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Avg', + data: data.avgArr, + fill: false, + borderWidth: 1.5, + borderColor: '#4C0099', + backgroundColor: '#4C0099', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'Max', + data: data.maxArr, + fill: false, + borderDash: [2, 2], + borderWidth: 1.5, + borderColor: '#0000CC', + backgroundColor: '#0000CC', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + title: { + display: false + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]) => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + const label = d.datasets[value.datasetIndex].label; + const index = value.index; + + return `${label}: ${isNaN(value.yLabel) ? `-` : value.yLabel.toFixed(1) + ` ` + this.getAgentId(data.minAgentIdArr, data.maxAgentIdArr, label, index)}`; + } + } + }, + hover: { + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (!this.isDataEmpty(data)) { + this.storeHelperService.dispatch(new Actions.ChangeHoverOnInspectorCharts({ + index: event.type === 'mouseout' ? -1 : elements[0]._index, + offsetX: event.offsetX, + offsetY: event.offsetY + })); + } + }, + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 4, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Transaction (count)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 50, + padding: 10 + } + } + }; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(1)); + return result + unit[index]; + } + + private getAgentId(minAgentIdArr: string[], maxAgentIdArr: string[], label: string, index: number): string { + return label === 'Avg' ? '' : `(${label === 'Min' ? minAgentIdArr[index] : maxAgentIdArr[index]})`; + } + + onShowHelp($event: MouseEvent): void { + super.onShowHelp($event, HELP_VIEWER_LIST.APPLICATION_TPS); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-data.service.ts new file mode 100644 index 000000000000..9395254945b9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/application-tps-chart-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForApplicationChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class ApplicationTPSChartDataService implements IChartDataService { + private requestURL = 'getApplicationStat/transaction/chart.pinpoint'; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + return this.http.get(this.requestURL, + getParamForApplicationChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(), range) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/chart-data.service.ts new file mode 100644 index 000000000000..037c87659b71 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/chart-data.service.ts @@ -0,0 +1,17 @@ +import { Observable } from 'rxjs'; + +export interface IChartDataFromServer { + charts: { + schema: { + [key: string]: string[] | string; + }, + x: number[]; + y: { + [key: string]: number[][]; + } + }; +} + +export interface IChartDataService { + getData(range: number[]): Observable; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/index.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/index.ts new file mode 100644 index 000000000000..ca9686e45f75 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/index.ts @@ -0,0 +1,153 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { RetryComponent } from './retry.component'; +import { NoDataComponent } from './no-data.component'; +import { InspectorChartComponent } from './inspector-chart.component'; +import { AgentActiveThreadChartContainerComponent } from './agent-active-thread-chart-container.component'; +import { AgentCPUChartContainerComponent } from './agent-cpu-chart-container.component'; +import { AgentDataSourceChartContainerComponent } from './agent-data-source-chart-container.component'; +import { AgentJVMHeapChartContainerComponent } from './agent-jvm-heap-chart-container.component'; +import { AgentJVMNonHeapChartContainerComponent } from './agent-jvm-non-heap-chart-container.component'; +import { AgentResponseTimeChartContainerComponent } from './agent-response-time-chart-container.component'; +import { AgentTPSChartContainerComponent } from './agent-tps-chart-container.component'; +import { AgentDataSourceChartInfotableComponent } from './agent-data-source-chart-infotable.component'; +import { AgentDataSourceChartSelectSourceComponent } from './agent-data-source-chart-select-source.component'; +import { AgentOpenFileDescriptorChartContainerComponent } from 'app/core/components/inspector-chart/agent-open-file-descriptor-chart-container.component'; +import { AgentDirectBufferCountChartContainerComponent } from 'app/core/components/inspector-chart/agent-direct-buffer-count-chart-container.component'; +import { AgentDirectBufferMemoryChartContainerComponent } from 'app/core/components/inspector-chart/agent-direct-buffer-memory-chart-container.component'; +import { AgentMappedBufferCountChartContainerComponent } from 'app/core/components/inspector-chart/agent-mapped-buffer-count-chart-container.component'; +import { AgentMappedBufferMemoryChartContainerComponent } from 'app/core/components/inspector-chart/agent-mapped-buffer-memory-chart-container.component'; +import { ApplicationActiveThreadChartContainerComponent } from './application-active-thread-chart-container.component'; +import { ApplicationJVMCPUChartContainerComponent } from './application-jvm-cpu-chart-container.component'; +import { ApplicationJVMHeapChartContainerComponent } from './application-jvm-heap-chart-container.component'; +import { ApplicationJVMNonHeapChartContainerComponent } from './application-jvm-non-heap-chart-container.component'; +import { ApplicationResponseTimeChartContainerComponent } from './application-response-time-chart-container.component'; +import { ApplicationSystemCPUChartContainerComponent } from './application-system-cpu-chart-container.component'; +import { ApplicationDataSourceChartContainerComponent } from 'app/core/components/inspector-chart/application-data-source-chart-container.component'; +import { ApplicationTPSChartContainerComponent } from './application-tps-chart-container.component'; +import { ApplicationOpenFileDescriptorChartContainerComponent } from 'app/core/components/inspector-chart/application-open-file-descriptor-chart-container.component'; +import { ApplicationDataSourceChartSourcelistComponent } from './application-data-source-chart-soucelist.component'; +import { ApplicationDirectBufferCountChartContainerComponent } from 'app/core/components/inspector-chart/application-direct-buffer-count-chart-container.component'; +import { ApplicationDirectBufferMemoryChartContainerComponent } from 'app/core/components/inspector-chart/application-direct-buffer-memory-chart-container.component'; +import { ApplicationMappedBufferCountChartContainerComponent } from 'app/core/components/inspector-chart/application-mapped-buffer-count-chart-container.component'; +import { ApplicationMappedBufferMemoryChartContainerComponent } from 'app/core/components/inspector-chart/application-mapped-buffer-memory-chart-container.component'; +import { TransactionViewJVMHeapChartContainerComponent } from './transaction-view-jvm-heap-chart-container.component'; +import { TransactionViewJVMNonHeapChartContainerComponent } from './transaction-view-jvm-non-heap-chart-container.component'; +import { TransactionViewCPUChartContainerComponent } from './transaction-view-cpu-chart-container.component'; + +import { AgentActiveThreadChartDataService } from './agent-active-thread-chart-data.service'; +import { AgentCPUChartDataService } from './agent-cpu-chart-data.service'; +import { AgentDataSourceChartDataService } from './agent-data-source-chart-data.service'; +import { AgentMemoryChartDataService } from './agent-memory-chart-data.service'; +import { AgentTPSChartDataService } from './agent-tps-chart-data.service'; +import { ApplicationCPUChartDataService } from './application-cpu-chart-data.service'; +import { ApplicationActiveThreadChartDataService } from './application-active-thread-chart-data.service'; +import { ApplicationDataSourceChartDataService } from './application-data-source-chart-data.service'; +import { ApplicationMemoryChartDataService } from './application-memory-chart-data.service'; +import { ApplicationTPSChartDataService } from './application-tps-chart-data.service'; +import { ApplicationResponseTimeChartDataService } from './application-response-time-chart-data.service'; +import { AgentResponseTimeChartDataService } from './agent-response-time-chart-data.service'; +import { TransactionViewMemoryChartDataService } from './transaction-view-memory-chart-data.service'; +import { TransactionViewCPUChartDataService } from './transaction-view-cpu-chart-data.service'; +import { AgentOpenFileDescriptorChartDataService } from 'app/core/components/inspector-chart/agent-open-file-descriptor-chart-data.service'; +import { ApplicationOpenFileDescriptorChartDataService } from 'app/core/components/inspector-chart/application-open-file-descriptor-chart-data.service'; +import { AgentDirectBufferChartDataService } from 'app/core/components/inspector-chart/agent-direct-buffer-chart-data.service'; +import { ApplicationDirectBufferChartDataService } from 'app/core/components/inspector-chart/application-direct-buffer-chart-data.service'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + RetryComponent, + NoDataComponent, + InspectorChartComponent, + AgentActiveThreadChartContainerComponent, + AgentCPUChartContainerComponent, + AgentDataSourceChartContainerComponent, + AgentJVMHeapChartContainerComponent, + AgentJVMNonHeapChartContainerComponent, + AgentResponseTimeChartContainerComponent, + AgentTPSChartContainerComponent, + ApplicationActiveThreadChartContainerComponent, + ApplicationJVMCPUChartContainerComponent, + ApplicationJVMHeapChartContainerComponent, + ApplicationJVMNonHeapChartContainerComponent, + ApplicationResponseTimeChartContainerComponent, + ApplicationSystemCPUChartContainerComponent, + ApplicationTPSChartContainerComponent, + ApplicationDataSourceChartContainerComponent, + AgentDataSourceChartInfotableComponent, + AgentDataSourceChartSelectSourceComponent, + ApplicationDataSourceChartSourcelistComponent, + TransactionViewJVMHeapChartContainerComponent, + TransactionViewJVMNonHeapChartContainerComponent, + TransactionViewCPUChartContainerComponent, + AgentOpenFileDescriptorChartContainerComponent, + ApplicationOpenFileDescriptorChartContainerComponent, + AgentDirectBufferCountChartContainerComponent, + AgentDirectBufferMemoryChartContainerComponent, + AgentMappedBufferCountChartContainerComponent, + AgentMappedBufferMemoryChartContainerComponent, + ApplicationDirectBufferCountChartContainerComponent, + ApplicationDirectBufferMemoryChartContainerComponent, + ApplicationMappedBufferCountChartContainerComponent, + ApplicationMappedBufferMemoryChartContainerComponent + ], + imports: [ + SharedModule, + HelpViewerPopupModule + ], + exports: [ + AgentActiveThreadChartContainerComponent, + AgentCPUChartContainerComponent, + AgentDataSourceChartContainerComponent, + AgentJVMHeapChartContainerComponent, + AgentJVMNonHeapChartContainerComponent, + AgentResponseTimeChartContainerComponent, + AgentTPSChartContainerComponent, + ApplicationActiveThreadChartContainerComponent, + ApplicationJVMCPUChartContainerComponent, + ApplicationJVMHeapChartContainerComponent, + ApplicationJVMNonHeapChartContainerComponent, + ApplicationResponseTimeChartContainerComponent, + ApplicationSystemCPUChartContainerComponent, + ApplicationTPSChartContainerComponent, + ApplicationDataSourceChartContainerComponent, + AgentOpenFileDescriptorChartContainerComponent, + ApplicationOpenFileDescriptorChartContainerComponent, + AgentDirectBufferCountChartContainerComponent, + AgentDirectBufferMemoryChartContainerComponent, + AgentMappedBufferCountChartContainerComponent, + AgentMappedBufferMemoryChartContainerComponent, + ApplicationDirectBufferCountChartContainerComponent, + ApplicationDirectBufferMemoryChartContainerComponent, + ApplicationMappedBufferCountChartContainerComponent, + ApplicationMappedBufferMemoryChartContainerComponent + ], + entryComponents: [ + TransactionViewJVMHeapChartContainerComponent, + TransactionViewJVMNonHeapChartContainerComponent, + TransactionViewCPUChartContainerComponent + ], + providers: [ + AgentActiveThreadChartDataService, + AgentCPUChartDataService, + AgentDataSourceChartDataService, + AgentMemoryChartDataService, + AgentTPSChartDataService, + AgentResponseTimeChartDataService, + AgentOpenFileDescriptorChartDataService, + AgentDirectBufferChartDataService, + ApplicationCPUChartDataService, + ApplicationActiveThreadChartDataService, + ApplicationDataSourceChartDataService, + ApplicationMemoryChartDataService, + ApplicationTPSChartDataService, + ApplicationResponseTimeChartDataService, + ApplicationOpenFileDescriptorChartDataService, + ApplicationDirectBufferChartDataService, + TransactionViewMemoryChartDataService, + TransactionViewCPUChartDataService + ] +}) +export class InspectorChartModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart-container.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart-container.ts new file mode 100644 index 000000000000..b84caafe9af8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart-container.ts @@ -0,0 +1,161 @@ +import { ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; +import { Subject, Observable, combineLatest } from 'rxjs'; +import { filter, map, skip, takeUntil } from 'rxjs/operators'; + +import { II18nText, IChartConfig, IErrObj } from 'app/core/components/inspector-chart/inspector-chart.component'; +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, AnalyticsService, TRACKED_EVENT_LIST, StoreHelperService, DynamicPopupService } from 'app/shared/services'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +export abstract class InspectorChartContainer { + private previousRange: number[]; + + protected chartData: IChartDataFromServer | IChartDataFromServer[]; + protected timezone: string; + protected dateFormat: string[]; + protected unsubscribe = new Subject(); + + i18nText$: Observable; + chartConfig: IChartConfig; + errObj: IErrObj; + hoveredInfo$: Observable; + + constructor( + protected defaultYMax: number, + protected storeHelperService: StoreHelperService, + protected changeDetector: ChangeDetectorRef, + protected webAppSettingDataService: WebAppSettingDataService, + protected newUrlStateNotificationService: NewUrlStateNotificationService, + protected chartDataService: IChartDataService, + protected translateService: TranslateService, + protected ajaxExceptionCheckerService: AjaxExceptionCheckerService, + protected analyticsService: AnalyticsService, + protected dynamicPopupService: DynamicPopupService + ) {} + + protected initI18nText(): void { + this.i18nText$ = combineLatest( + this.translateService.get('INSPECTOR.FAILED_TO_FETCH_DATA'), + this.translateService.get('INSPECTOR.NO_DATA_COLLECTED'), + ).pipe( + map(([FAILED_TO_FETCH_DATA, NO_DATA_COLLECTED]: string[]) => { + return { FAILED_TO_FETCH_DATA, NO_DATA_COLLECTED }; + }) + ); + } + + protected initHoveredInfo(): void { + this.hoveredInfo$ = this.storeHelperService.getHoverInfo(this.unsubscribe).pipe( + skip(1), + filter(() => { + return !(!this.chartConfig || this.chartConfig.isDataEmpty); + }) + ); + } + + protected initTimezoneAndDateFormat(): void { + combineLatest( + this.storeHelperService.getTimezone(this.unsubscribe), + this.storeHelperService.getDateFormatArray(this.unsubscribe, 3, 4) + ).subscribe(([timezone, dateFormat]: [string, string[]]) => { + this.timezone = timezone; + this.dateFormat = dateFormat; + if (this.chartData) { + const xDataArr = Array.isArray(this.chartData) ? this.chartData[0].charts.x : this.chartData.charts.x; + + this.chartConfig = {...this.chartConfig}; + this.chartConfig.dataConfig.labels = this.getNewFormattedLabels(xDataArr); + this.changeDetector.detectChanges(); + } + }); + } + + private getNewFormattedLabels(xDataArr: number[]): string[] { + return xDataArr.map((xData: number) => { + return `${moment(xData).tz(this.timezone).format(this.dateFormat[0])}#${moment(xData).tz(this.timezone).format(this.dateFormat[1])}`; + }); + } + + protected initChartData(): void { + this.storeHelperService.getInspectorTimelineSelectionRange(this.unsubscribe).pipe( + filter((range: number[]) => { + if (this.previousRange) { + return !(this.previousRange[0] === range[0] && this.previousRange[1] === range[1]); + } + return true; + }) + ).subscribe((range: number[]) => { + this.previousRange = range; + this.getChartData(range); + }); + } + + onRetryGetChartData(): void { + this.getChartData(this.previousRange); + } + + protected getChartData(range: number[]): void { + this.chartDataService.getData(range).pipe( + takeUntil(this.unsubscribe) + ).subscribe((data: IChartDataFromServer | IChartDataFromServer[] | AjaxException) => { + if (this.ajaxExceptionCheckerService.isAjaxException(data)) { + this.setErrObj(data); + } else { + this.chartData = data; + this.setChartConfig(this.makeChartData(data)); + } + }, + (err) => { + this.setErrObj(); + } + ); + } + + protected setChartConfig(data: {[key: string]: any} | {[key: string]: any}[]): void { + this.chartConfig = { + type: 'line', + dataConfig: this.makeDataOption(data), + elseConfig: this.makeNormalOption(data), + isDataEmpty: this.isDataEmpty(data) + }; + this.changeDetector.detectChanges(); + } + + protected setErrObj(data?: AjaxException): void { + this.errObj = { + errType: data ? 'EXCEPTION' : 'ELSE', + errMessage: data ? data.exception.message : null + }; + this.changeDetector.detectChanges(); + } + + protected isDataEmpty(data: {[key: string]: any} | {[key: string]: any}[]): boolean { + const emptyCheckFunc = (d: {[key: string]: any}) => Object.getOwnPropertyNames(d).filter((prop) => prop !== 'x' && Array.isArray(d[prop])).map((yProp) => d[yProp].length).every((l) => l === 0); + + return Array.isArray(data) ? data.length === 0 || data.every((obj) => emptyCheckFunc(obj)) + : emptyCheckFunc(data); + } + + protected parseData(data: number): number | null { + return data === -1 ? null : data; + } + + protected abstract makeChartData(chartData: IChartDataFromServer | IChartDataFromServer[]): {[key: string]: any} | {[key: string]: any}[]; + protected abstract makeDataOption(data: {[key: string]: any} | {[key: string]: any}[]): {[key: string]: any}; + protected abstract makeNormalOption(data: {[key: string]: any} | {[key: string]: any}[]): {[key: string]: any}; + onShowHelp($event: MouseEvent, key: HELP_VIEWER_LIST): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, key); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: key, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart.component.css new file mode 100644 index 000000000000..ceda45587477 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart.component.css @@ -0,0 +1,25 @@ +:host { + width: 100%; + display: flex; + align-items: flex-end; +} +.l-chart-section { + background-color: #fff; + padding: 15px 20px; + position: relative; + width: 100%; +} +.show-chart { + visibility: visible; + opacity: 1; + transition: all 1s; +} +.shady-chart { + opacity: 0.5; + pointer-events: none; + transition: all 0.5s; +} +.hide-chart { + visibility: hidden; + opacity: 0; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart.component.html new file mode 100644 index 000000000000..f2d26bcd9ce9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart.component.html @@ -0,0 +1,6 @@ +
+ + + + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart.component.ts new file mode 100644 index 000000000000..d09994a184a7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/inspector-chart.component.ts @@ -0,0 +1,188 @@ +import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ViewChild, ElementRef, SimpleChanges, ChangeDetectorRef } from '@angular/core'; +import { Chart } from 'chart.js'; + +export interface IChartConfig { + type: string; + dataConfig: {[key: string]: any}; + elseConfig: {[key: string]: any}; + isDataEmpty: boolean; +} + +export interface II18nText { + FAILED_TO_FETCH_DATA: string; + NO_DATA_COLLECTED: string; +} + +export interface IErrObj { + errType: string; // EXCEPTION or ELSE + errMessage: string; // data.exception.message or null +} + +@Component({ + selector: 'pp-inspector-chart', + templateUrl: './inspector-chart.component.html', + styleUrls: ['./inspector-chart.component.css'] +}) +export class InspectorChartComponent implements OnInit, OnChanges { + @ViewChild('chartElement') el: ElementRef; + @Input() xRawData: number[]; + @Input() chartConfig: IChartConfig; + @Input() i18nText: II18nText; + @Input() errObj: IErrObj; + @Input() hoveredInfo: IHoveredInfo; + @Input() height: string; + @Output() outRetryGetChartData: EventEmitter = new EventEmitter(); + + retryMessage: string; + chartVisibility = {}; + chartSectionLayers = { + loading: true, + chart: false, + retry: false + }; + + private chartObj: any; + + constructor( + private changeDetector: ChangeDetectorRef, + ) {} + + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + Object.keys(changes) + .filter((propName: string) => { + return changes[propName].currentValue; + }) + .forEach((propName: string) => { + const changedProp = changes[propName]; + switch (propName) { + case 'chartConfig': + this.setChartSectionVisibility('loading'); + this.initChart(); + break; + case 'errObj': + this.setChartSectionVisibility('loading'); + this.setRetryMessage(changedProp.currentValue); + this.setChartSectionVisibility('retry'); + break; + case 'hoveredInfo': + this.syncHoverOnChart(changedProp.currentValue); + break; + } + }); + } + + getHeightConfig(): {[key: string]: any} { + return { + height: this.height, + setHeightAuto: this.chartSectionLayers.chart, + ratio: 1.92 + }; + } + + private syncHoverOnChart(hoverInfo: IHoveredInfo): void { + let activeElements; + if (hoverInfo.index === -1) { + if (hoverInfo.time) { + activeElements = this.getActiveTooltipElementsByTime(hoverInfo.time); + } else { + activeElements = []; + } + } else { + activeElements = this.getActiveTooltipElements(hoverInfo.index); + } + + this.chartObj.tooltip._active = activeElements; + this.chartObj.tooltip.update(true); + this.chartObj.draw(); + } + getActiveTooltipElementsByTime(time: number): any[] { + let index = -1; + const len = this.xRawData.length; + for (let i = 0 ; i < len ; i++) { + const t = this.xRawData[i]; + if (t === time) { + index = i; + break; + } else if (t > time) { + if (i + 1 === len) { + index = i; + } else { + if (this.xRawData[i] - time >= this.xRawData[i - 1] - time) { + index = i - 1; + } else { + index = i; + } + } + break; + } + } + return this.getActiveTooltipElements(index); + } + getActiveTooltipElements(index: number): any[] { + return this.chartObj.data.datasets.map((val: any, i: number) => this.chartObj.getDatasetMeta(i).data[index]); + } + + private initChart(): void { + if (this.chartObj) { + this.chartObj.data = this.chartConfig.dataConfig; + this.chartObj.options.tooltips.callbacks.label = this.chartConfig.elseConfig.tooltips.callbacks.label; + this.chartObj.options.scales.yAxes[0].ticks.max = this.chartConfig.elseConfig.scales.yAxes[0].ticks.max; + this.chartObj.update(); + } else { + this.chartObj = new Chart(this.el.nativeElement.getContext('2d'), { + type: this.chartConfig.type, + data: this.chartConfig.dataConfig, + options: this.chartConfig.elseConfig, + plugins: [{ + afterRender: (chart, options) => { + this.finishLoading(); + } + }], + }); + } + } + + private finishLoading(): void { + this.setChartSectionVisibility('chart'); + } + + private setRetryMessage(errObj: IErrObj): void { + this.retryMessage = errObj.errType === 'EXCEPTION' ? errObj.errMessage : this.i18nText['FAILED_TO_FETCH_DATA']; + } + + private setChartSectionVisibility(layer: string): void { + this.setChartSectionLayerAs(layer); + this.setChartVisibility(this.chartSectionLayers['chart'], this.chartObj); + this.notifyChanges(); + } + + private setChartSectionLayerAs(whichLayerToShow: string): void { + Object.keys(this.chartSectionLayers).forEach((layer) => { + this.chartSectionLayers[layer] = whichLayerToShow === layer; + }); + } + + private setChartVisibility(showChart: boolean, chartObj: Chart): void { + this.chartVisibility = { + 'show-chart': showChart, + 'shady-chart': !showChart && chartObj !== undefined, + 'hide-chart': !showChart && chartObj === undefined + }; + } + + retryGetChartData(): void { + this.setChartSectionVisibility('loading'); + this.outRetryGetChartData.emit(); + } + + showNoData(): boolean { + return this.chartSectionLayers.chart && this.chartConfig.isDataEmpty; + } + + private notifyChanges(): void { + if (!this.changeDetector['destroyed']) { + this.changeDetector.detectChanges(); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/no-data.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/no-data.component.css new file mode 100644 index 000000000000..3c65ae892265 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/no-data.component.css @@ -0,0 +1,16 @@ +:host { + display: block; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; +} + +.l-no-data-text { + position: absolute; + top: 42%; + left: 37%; + padding: 5px 10px; + background-color: #e3e5e8; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/no-data.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/no-data.component.html new file mode 100644 index 000000000000..1856dec2caeb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/no-data.component.html @@ -0,0 +1 @@ +

{{message}}

diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/no-data.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/no-data.component.ts new file mode 100644 index 000000000000..ecf627e87d6a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/no-data.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { style, animate, transition, trigger } from '@angular/animations'; + +@Component({ + selector: 'pp-no-data', + animations: [ + trigger('fadeIn', [ + transition(':enter', [ // is alias to 'void => *' + style({opacity: 0}), + animate(1000, style({opacity: 1})) + ]), + ]) + ], + templateUrl: './no-data.component.html', + styleUrls: ['./no-data.component.css'] +}) +export class NoDataComponent implements OnInit { + @Input() message: string; + @Input() showNoData: boolean; + constructor() { } + + ngOnInit() { + } + +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/retry.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/retry.component.css new file mode 100644 index 000000000000..acfb236b3df4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/retry.component.css @@ -0,0 +1,9 @@ +.l-retry { + width: 92%; + position: absolute; + top:40%; + text-align: center; +} +.l-retry-message { + margin-bottom: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/retry.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/retry.component.html new file mode 100644 index 000000000000..cd491543cc39 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/retry.component.html @@ -0,0 +1,4 @@ +
+

{{message}}

+ +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/retry.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/retry.component.ts new file mode 100644 index 000000000000..e9547ca95813 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/retry.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { style, animate, transition, trigger } from '@angular/animations'; + +@Component({ + selector: 'pp-retry', + animations: [ + trigger('fadeIn', [ + transition(':enter', [ // is alias to 'void => *' + style({opacity: 0}), + animate(2000, style({opacity: 1})) + ]), + ]) + ], + templateUrl: './retry.component.html', + styleUrls: ['./retry.component.css'] +}) +export class RetryComponent implements OnInit { + @Input() showRetry: boolean; + @Input() message: string; + @Output() outRetryGetChartData: EventEmitter = new EventEmitter(); + constructor() { } + + ngOnInit() { + } + + retryGetChartData(): void { + this.outRetryGetChartData.emit(); + } + +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-chart-container.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-chart-container.ts new file mode 100644 index 000000000000..9866c40d4fda --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-chart-container.ts @@ -0,0 +1,148 @@ +import { ChangeDetectorRef, ElementRef } from '@angular/core'; +import { Subject, Observable, combineLatest } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { II18nText, IChartConfig, IErrObj } from 'app/core/components/inspector-chart/inspector-chart.component'; +import { StoreHelperService, WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, GutterEventService } from 'app/shared/services'; +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { UrlPathId } from 'app/shared/models'; + +export abstract class TransactionViewChartContainer { + protected chartData: IChartDataFromServer; + protected timezone: string; + protected dateFormat: string[]; + protected unsubscribe = new Subject(); + + i18nText$: Observable; + height$: Observable; + chartConfig: IChartConfig; + errObj: IErrObj; + hoveredInfo$: Observable; + xRawData: number[]; + + constructor( + protected defaultYMax: number, + protected storeHelperService: StoreHelperService, + protected changeDetector: ChangeDetectorRef, + protected webAppSettingDataService: WebAppSettingDataService, + protected newUrlStateNotificationService: NewUrlStateNotificationService, + protected chartDataService: IChartDataService, + protected translateService: TranslateService, + protected ajaxExceptionCheckerService: AjaxExceptionCheckerService, + protected gutterEventService: GutterEventService, + protected el: ElementRef + ) {} + + protected initI18nText(): void { + this.i18nText$ = combineLatest( + this.translateService.get('INSPECTOR.FAILED_TO_FETCH_DATA'), + this.translateService.get('INSPECTOR.NO_DATA_COLLECTED'), + ).pipe( + map(([FAILED_TO_FETCH_DATA, NO_DATA_COLLECTED]: string[]) => { + return { FAILED_TO_FETCH_DATA, NO_DATA_COLLECTED }; + }) + ); + } + protected initHoveredInfo(): void { + this.hoveredInfo$ = this.storeHelperService.getHoverInfo(this.unsubscribe).pipe( + filter(() => { + return !(!this.chartConfig || this.chartConfig.isDataEmpty); + }) + ); + } + protected initTimezoneAndDateFormat(): void { + combineLatest( + this.storeHelperService.getTimezone(this.unsubscribe), + this.storeHelperService.getDateFormatArray(this.unsubscribe, 3, 4) + ).subscribe((data: [string, string[]]) => { + this.timezone = data[0]; + this.dateFormat = data[1]; + if (this.chartData) { + const xDataArr = Array.isArray(this.chartData) ? this.chartData[0].charts.x : this.chartData.charts.x; + + this.chartConfig = {...this.chartConfig}; + this.chartConfig.dataConfig.labels = this.getNewFormattedLabels(xDataArr); + this.changeDetector.detectChanges(); + } + }); + } + + protected initHeight(): void { + // TODO: angular-split라이브러리에 minSize옵션 추가되면, filter오퍼레이터 제거. + this.height$ = this.gutterEventService.onGutterResized$.pipe( + map((ratioArr: number[]) => ratioArr[0]), + filter((ratio: number) => ratio >= 30 && ratio <= 55), // 30, 50: 차트가 포함되어 있는 split-area의 최소, 최대 사이즈(비율) + map(() => this.el.nativeElement.offsetHeight + 'px') + ); + } + + private getNewFormattedLabels(xDataArr: number[]): string[] { + return xDataArr.map((xData: number) => { + return `${moment(xData).tz(this.timezone).format(this.dateFormat[0])}#${moment(xData).tz(this.timezone).format(this.dateFormat[1])}`; + }); + } + + protected getTimeRange(): number[] { + const focusTime = Number(this.newUrlStateNotificationService.getPathValue(UrlPathId.FOCUS_TIMESTAMP)); + const range = 600000; + + return [focusTime - range, focusTime + range]; + } + + onRetryGetChartData(): void { + this.getChartData(this.getTimeRange()); + } + + protected getChartData(range: number[]): void { + this.chartDataService.getData(range) + .subscribe( + (data: IChartDataFromServer | AjaxException) => { + if (this.ajaxExceptionCheckerService.isAjaxException(data)) { + this.setErrObj(data); + } else { + this.xRawData = data.charts.x; + this.chartData = data; + this.setChartConfig(this.makeChartData(data)); + } + }, + (err) => { + this.setErrObj(); + } + ); + } + + protected setChartConfig(data: {[key: string]: any} | {[key: string]: any}[]): void { + this.chartConfig = { + type: 'line', + dataConfig: this.makeDataOption(data), + elseConfig: this.makeNormalOption(data), + isDataEmpty: this.isDataEmpty(data) + }; + this.changeDetector.detectChanges(); + } + + protected setErrObj(data?: AjaxException): void { + this.errObj = { + errType: data ? 'EXCEPTION' : 'ELSE', + errMessage: data ? data.exception.message : null + }; + this.changeDetector.detectChanges(); + } + + protected isDataEmpty(data: {[key: string]: any} | {[key: string]: any}[]): boolean { + const emptyCheckFunc = (d: {[key: string]: any}) => Object.getOwnPropertyNames(d).filter((prop) => prop !== 'x' && Array.isArray(d[prop])).map((yProp) => d[yProp].length).every((l) => l === 0); + + return Array.isArray(data) ? data.length === 0 || data.every((obj) => emptyCheckFunc(obj)) + : emptyCheckFunc(data); + } + + protected parseData(data: number): number | null { + return data === -1 ? null : data; + } + + protected abstract makeChartData(chartData: IChartDataFromServer): {[key: string]: any} | {[key: string]: any}[]; + protected abstract makeDataOption(data: {[key: string]: any} | {[key: string]: any}[]): {[key: string]: any}; + protected abstract makeNormalOption(data: {[key: string]: any} | {[key: string]: any}[]): {[key: string]: any}; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-container.component.css new file mode 100644 index 000000000000..8bc6555c724d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-container.component.css @@ -0,0 +1,8 @@ +:host { + display: block; + height: calc(100% - 45px); +} +.l-content-item { + width: 100%; + margin: 0px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-container.component.html new file mode 100644 index 000000000000..ba6a1c2f48a4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-container.component.html @@ -0,0 +1,11 @@ +
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-container.component.ts new file mode 100644 index 000000000000..378d205da533 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-container.component.ts @@ -0,0 +1,189 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, ElementRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, GutterEventService, StoreHelperService } from 'app/shared/services'; +import { TransactionViewCPUChartDataService } from './transaction-view-cpu-chart-data.service'; +import { TransactionViewChartContainer } from 'app/core/components/inspector-chart/transaction-view-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-transaction-view-cpu-chart-container', + templateUrl: './transaction-view-cpu-chart-container.component.html', + styleUrls: ['./transaction-view-cpu-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransactionViewCPUChartContainerComponent extends TransactionViewChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: TransactionViewCPUChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + gutterEventService: GutterEventService, + el: ElementRef + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + gutterEventService, + el + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initHeight(); + this.initTimezoneAndDateFormat(); + this.getChartData(this.getTimeRange()); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected parseData(data: number): number | null { + return data === -1 ? null : Number(data.toFixed(2)); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const jvmArr = []; + const systemArr = []; + const maxArr = []; + + const xData = chartData.charts.x; + const cpuJVM = chartData.charts.y['CPU_LOAD_JVM']; + const cpuSystem = chartData.charts.y['CPU_LOAD_SYSTEM']; + const dataCount = xData.length; + + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + maxArr.push(100); + if ( cpuJVM.length === 0 ) { + continue; + } + jvmArr.push(this.parseData(cpuJVM[i][1])); + systemArr.push(this.parseData(cpuSystem[i][1])); + } + return { + x: xArr, + jvm: jvmArr, + system: systemArr + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + label: 'JVM', + data: data.jvm, + fill: false, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180)', + backgroundColor: 'rgba(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + label: 'System', + data: data.system, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(174, 199, 232)', + backgroundColor: 'rgba(174, 199, 232, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + maintainAspectRatio: false, + title: { + display: false, + text: 'JVM/System CPU Usage' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : value.yLabel + `%`}`; + } + } + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 7, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'CPU Usage (%)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return `${label}%`; + }, + min: 0, + max: this.defaultYMax, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-data.service.ts new file mode 100644 index 000000000000..f3b36a0e01cc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-cpu-chart-data.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { shareReplay } from 'rxjs/operators'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class TransactionViewCPUChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/cpuLoad/chart.pinpoint'; + private cache$: Observable; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + if (!this.cache$) { + const httpRequest$ = this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range)); + + this.cache$ = httpRequest$.pipe( + shareReplay(1) + ); + } + + return this.cache$; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component.css new file mode 100644 index 000000000000..c0f04e531812 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component.css @@ -0,0 +1,8 @@ +:host { + display: block; + height: calc(100% - 45px); +} +l-content-item { + width: 100%; + margin: 0px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component.html new file mode 100644 index 000000000000..ba6a1c2f48a4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component.html @@ -0,0 +1,11 @@ +
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component.ts new file mode 100644 index 000000000000..6f1ddb2888c8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component.ts @@ -0,0 +1,265 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, ElementRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, GutterEventService, StoreHelperService } from 'app/shared/services'; +import { TransactionViewMemoryChartDataService } from './transaction-view-memory-chart-data.service'; +import { TransactionViewChartContainer } from 'app/core/components/inspector-chart/transaction-view-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-trasaction-view-jvm-heap-chart-container', + templateUrl: './transaction-view-jvm-heap-chart-container.component.html', + styleUrls: ['./transaction-view-jvm-heap-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransactionViewJVMHeapChartContainerComponent extends TransactionViewChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: TransactionViewMemoryChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + gutterEventService: GutterEventService, + el: ElementRef + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + gutterEventService, + el + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initHeight(); + this.initTimezoneAndDateFormat(); + this.getChartData(this.getTimeRange()); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected setChartConfig(data: {[key: string]: any}): void { + this.chartConfig = { + type: 'bar', + dataConfig: this.makeDataOption(data), + elseConfig: this.makeNormalOption(data), + isDataEmpty: this.isDataEmpty(data) + }; + this.changeDetector.detectChanges(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const usedArr = []; + const fgcTimeArr = []; + const fgcCountArr = []; + + const xData = chartData.charts.x; + const gcOldTime = chartData.charts.y['JVM_GC_OLD_TIME']; + const gcOldCount = chartData.charts.y['JVM_GC_OLD_COUNT']; + const memoryUsed = chartData.charts.y['JVM_MEMORY_HEAP_USED']; + const memoryMax = chartData.charts.y['JVM_MEMORY_HEAP_MAX']; + const dataCount = xData.length; + + let totalSumGCTime = 0; + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if ( memoryMax.length === 0 ) { + continue; + } + maxArr.push(this.parseData(memoryMax[i][1])); + usedArr.push(this.parseData(memoryUsed[i][1])); + + const gcOldCountSumValue = gcOldCount[i][3]; + const gcOldTimeSumValue = gcOldTime[i][3]; + + if ( gcOldTimeSumValue > 0 ) { + totalSumGCTime += gcOldTimeSumValue; + } + if ( gcOldCountSumValue > 0 ) { + fgcTimeArr.push(gcOldCountSumValue); + fgcCountArr.push(totalSumGCTime); + totalSumGCTime = 0; + } else { + fgcTimeArr.push(0); + fgcCountArr.push(0); + } + } + return { + x: xArr, + max: maxArr, + used: usedArr, + fgcTime: fgcTimeArr, + fgcCount: fgcCountArr + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + type: 'line', + label: 'Max', + data: data.max, + fill: false, + borderWidth: 0.5, + borderColor: 'rgb(174, 199, 232)', + backgroundColor: 'rgb(174, 199, 232)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + type: 'line', + label: 'Used', + data: data.used, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180)', + backgroundColor: 'rgba(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + type: 'bar', + label: 'FGC', + data: data.fgcTime, + borderWidth: 1, + borderColor: 'rgb(255, 42, 0)', + backgroundColor: 'rgba(255, 42, 0, 0.3)', + // pointRadius: 0, + // pointHoverRadius: 3, + yAxisID: 'y-axis-2' + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + maintainAspectRatio: false, + title: { + display: false, + text: 'Heap Usage' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel)}`; + } + } + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 7, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + id: 'y-axis-1', + display: true, + position: 'left', + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }, + { + id: 'y-axis-2', + display: true, + position: 'right', + scaleLabel: { + display: true, + labelString: 'Full GC (ms)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component.css new file mode 100644 index 000000000000..734f4f3d0fa2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component.css @@ -0,0 +1,9 @@ +:host { + display: block; + height: calc(100% - 45px); +} +.l-content-item { + width: 100%; + height: 100%; + margin: 0px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component.html new file mode 100644 index 000000000000..ba6a1c2f48a4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component.html @@ -0,0 +1,11 @@ +
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component.ts new file mode 100644 index 000000000000..5d405e08de6c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component.ts @@ -0,0 +1,265 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, ElementRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import * as moment from 'moment-timezone'; + +import { WebAppSettingDataService, NewUrlStateNotificationService, AjaxExceptionCheckerService, GutterEventService, StoreHelperService } from 'app/shared/services'; +import { TransactionViewMemoryChartDataService } from './transaction-view-memory-chart-data.service'; +import { TransactionViewChartContainer } from 'app/core/components/inspector-chart/transaction-view-chart-container'; +import { IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; + +@Component({ + selector: 'pp-transaction-view-jvm-non-heap-chart-container', + templateUrl: './transaction-view-jvm-non-heap-chart-container.component.html', + styleUrls: ['./transaction-view-jvm-non-heap-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransactionViewJVMNonHeapChartContainerComponent extends TransactionViewChartContainer implements OnInit, OnDestroy { + constructor( + storeHelperService: StoreHelperService, + changeDetector: ChangeDetectorRef, + webAppSettingDataService: WebAppSettingDataService, + newUrlStateNotificationService: NewUrlStateNotificationService, + chartDataService: TransactionViewMemoryChartDataService, + translateService: TranslateService, + ajaxExceptionCheckerService: AjaxExceptionCheckerService, + gutterEventService: GutterEventService, + el: ElementRef + ) { + super( + 100, + storeHelperService, + changeDetector, + webAppSettingDataService, + newUrlStateNotificationService, + chartDataService, + translateService, + ajaxExceptionCheckerService, + gutterEventService, + el + ); + } + + ngOnInit() { + this.initI18nText(); + this.initHoveredInfo(); + this.initHeight(); + this.initTimezoneAndDateFormat(); + this.getChartData(this.getTimeRange()); + } + + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + protected setChartConfig(data: {[key: string]: any}): void { + this.chartConfig = { + type: 'bar', + dataConfig: this.makeDataOption(data), + elseConfig: this.makeNormalOption(data), + isDataEmpty: this.isDataEmpty(data) + }; + this.changeDetector.detectChanges(); + } + + protected makeChartData(chartData: IChartDataFromServer): {[key: string]: any} { + const xArr = []; + const maxArr = []; + const usedArr = []; + const fgcTimeArr = []; + const fgcCountArr = []; + + const xData = chartData.charts.x; + const gcOldTime = chartData.charts.y['JVM_GC_OLD_TIME']; + const gcOldCount = chartData.charts.y['JVM_GC_OLD_COUNT']; + const memoryUsed = chartData.charts.y['JVM_MEMORY_NON_HEAP_USED']; + const memoryMax = chartData.charts.y['JVM_MEMORY_NON_HEAP_MAX']; + const dataCount = xData.length; + + let totalSumGCTime = 0; + for ( let i = 0 ; i < dataCount ; i++ ) { + xArr.push(moment(xData[i]).tz(this.timezone).format(this.dateFormat[0]) + '#' + moment(xData[i]).tz(this.timezone).format(this.dateFormat[1])); + if ( memoryMax.length === 0 ) { + continue; + } + maxArr.push(this.parseData(memoryMax[i][1])); + usedArr.push(this.parseData(memoryUsed[i][1])); + + const gcOldCountSumValue = gcOldCount[i][3]; + const gcOldTimeSumValue = gcOldTime[i][3]; + + if ( gcOldTimeSumValue > 0 ) { + totalSumGCTime += gcOldTimeSumValue; + } + if ( gcOldCountSumValue > 0 ) { + fgcTimeArr.push(gcOldCountSumValue); + fgcCountArr.push(totalSumGCTime); + totalSumGCTime = 0; + } else { + fgcTimeArr.push(0); + fgcCountArr.push(0); + } + } + return { + x: xArr, + max: maxArr, + used: usedArr, + fgcTime: fgcTimeArr, + fgcCount: fgcCountArr + }; + } + + protected makeDataOption(data: {[key: string]: any}): {[key: string]: any} { + return { + labels: data.x, + datasets: [{ + type: 'line', + label: 'Max', + data: data.max, + fill: false, + borderWidth: 0.5, + borderColor: 'rgb(174, 199, 232)', + backgroundColor: 'rgb(174, 199, 232)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + type: 'line', + label: 'Used', + data: data.used, + fill: true, + borderWidth: 0.5, + borderColor: 'rgb(31, 119, 180)', + backgroundColor: 'rgba(31, 119, 180, 0.4)', + pointRadius: 0, + pointHoverRadius: 3 + }, { + type: 'bar', + label: 'FGC', + data: data.fgcTime, + borderWidth: 1, + borderColor: 'rgb(255, 42, 0)', + backgroundColor: 'rgba(255, 42, 0, 0.3)', + // pointRadius: 0, + // pointHoverRadius: 3, + yAxisID: 'y-axis-2' + }] + }; + } + + protected makeNormalOption(data: {[key: string]: any}): {[key: string]: any} { + return { + responsive: true, + maintainAspectRatio: false, + title: { + display: false, + text: 'Heap Usage' + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + title: (value: {[key: string]: any}[]): string => { + return value[0].xLabel.join(' '); + }, + label: (value: {[key: string]: any}, d: {[key: string]: any}): string => { + return `${d.datasets[value.datasetIndex].label}: ${isNaN(value.yLabel) ? `-` : this.convertWithUnit(value.yLabel)}`; + } + } + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: false + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + maxTicksLimit: 7, + callback: (label: string): string[] => { + return label.split('#'); + }, + maxRotation: 0, + minRotation: 0, + fontSize: 11, + padding: 5 + } + }], + yAxes: [{ + id: 'y-axis-1', + display: true, + position: 'left', + scaleLabel: { + display: true, + labelString: 'Memory (bytes)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + callback: (label: number): string => { + return this.convertWithUnit(label); + }, + min: 0, + max: this.isDataEmpty(data) ? this.defaultYMax : undefined, + padding: 5 + } + }, + { + id: 'y-axis-2', + display: true, + position: 'right', + scaleLabel: { + display: true, + labelString: 'Full GC (ms)', + fontSize: 14, + fontStyle: 'bold' + }, + gridLines: { + color: 'rgb(0, 0, 0)', + lineWidth: 0.5, + drawBorder: true, + drawOnChartArea: false + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 5, + min: 0, + padding: 5 + } + }] + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + } + } + }; + } + + private convertWithUnit(value: number): string { + const unit = ['', 'K', 'M', 'G']; + let result = value; + let index = 0; + while ( result >= 1000 ) { + index++; + result /= 1000; + } + + result = Number.isInteger(result) ? result : Number(result.toFixed(2)); + return result + unit[index]; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-memory-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-memory-chart-data.service.ts new file mode 100644 index 000000000000..ba4034250382 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/inspector-chart/transaction-view-memory-chart-data.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { shareReplay } from 'rxjs/operators'; + +import { IChartDataService, IChartDataFromServer } from 'app/core/components/inspector-chart/chart-data.service'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { getParamForAgentChartData } from 'app/core/utils/chart-data-param-maker'; + +@Injectable() +export class TransactionViewMemoryChartDataService implements IChartDataService { + private requestURL = 'getAgentStat/jvmGc/chart.pinpoint'; + private cache$: Observable; + + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + + getData(range: number[]): Observable { + if (!this.cache$) { + const httpRequest$ = this.http.get(this.requestURL, + getParamForAgentChartData(this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), range)); + + this.cache$ = httpRequest$.pipe( + shareReplay(1) + ); + } + + return this.cache$; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/link-context-popup/index.ts b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/index.ts new file mode 100644 index 000000000000..89adf547f46d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/index.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { LinkContextPopupContainerComponent } from 'app/core/components/link-context-popup/link-context-popup-container.component'; +import { LinkContextPopupComponent } from 'app/core/components/link-context-popup/link-context-popup.component'; +import { FilterTransactionWizardPopupModule } from 'app/core/components/filter-transaction-wizard-popup'; + +@NgModule({ + declarations: [ + LinkContextPopupContainerComponent, + LinkContextPopupComponent + ], + imports: [ + SharedModule, + FilterTransactionWizardPopupModule + ], + exports: [], + entryComponents: [ + LinkContextPopupContainerComponent + ], + providers: [] +}) +export class LinkContextPopupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup-container.component.css b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup-container.component.css new file mode 100644 index 000000000000..7f26ddcb5503 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup-container.component.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup-container.component.html b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup-container.component.html new file mode 100644 index 000000000000..090c469aaea0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup-container.component.html @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup-container.component.ts new file mode 100644 index 000000000000..8c14682a6bbb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup-container.component.ts @@ -0,0 +1,78 @@ +import { Component, OnInit, Input, Output, EventEmitter, AfterViewInit } from '@angular/core'; + +import { + UrlRouteManagerService, + NewUrlStateNotificationService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService, + DynamicPopup +} from 'app/shared/services'; +import { Filter } from 'app/core/models/filter'; +import { UrlPathId } from 'app/shared/models'; +import { FilterTransactionWizardPopupContainerComponent } from 'app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component'; + +@Component({ + selector: 'pp-link-context-popup-container', + templateUrl: './link-context-popup-container.component.html', + styleUrls: ['./link-context-popup-container.component.css'], +}) +export class LinkContextPopupContainerComponent implements OnInit, AfterViewInit, DynamicPopup { + @Input() data: any; + @Input() coord: ICoordinate; + @Output() outCreated = new EventEmitter(); + @Output() outClose = new EventEmitter(); + + constructor( + private urlRouteManagerService: UrlRouteManagerService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private dynamicPopupService: DynamicPopupService, + private analyticsService: AnalyticsService, + ) {} + + ngOnInit() {} + ngAfterViewInit() { + this.outCreated.emit(this.coord); + } + + onInputChange({coord}: {coord: ICoordinate}): void { + this.outCreated.emit(coord); + } + + onClickFilterTransaction(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_FILTER_TRANSACTION); + this.outClose.emit(); + const isBothWas = this.data.sourceInfo.isWas && this.data.targetInfo.isWas; + this.urlRouteManagerService.openPage( + this.urlRouteManagerService.makeFilterMapUrl({ + applicationName: this.data.filterApplicationName, + serviceType: this.data.filterApplicationServiceTypeName, + periodStr: this.newUrlStateNotificationService.hasValue(UrlPathId.PERIOD) ? this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime() : '', + timeStr: this.newUrlStateNotificationService.hasValue(UrlPathId.END_TIME) ? this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() : '', + filterStr: this.newUrlStateNotificationService.hasValue(UrlPathId.FILTER) ? this.newUrlStateNotificationService.getPathValue(UrlPathId.FILTER) : '', + hintStr: this.newUrlStateNotificationService.hasValue(UrlPathId.HINT) ? this.newUrlStateNotificationService.getPathValue(UrlPathId.HINT) : '', + addedFilter: new Filter( + this.data.sourceInfo.applicationName, + this.data.sourceInfo.serviceType, + this.data.targetInfo.applicationName, + this.data.targetInfo.serviceType + ), + addedHint: (isBothWas ? { + [this.data.targetInfo.applicationName]: this.data.filterTargetRpcList + } : null) + }) + ); + } + + onClickFilterTransactionWizard(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_FILTER_TRANSACTION_WIZARD); + this.dynamicPopupService.openPopup({ + data: this.data, + component: FilterTransactionWizardPopupContainerComponent + }); + } + + onClickOutside(): void { + this.outClose.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup.component.css b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup.component.css new file mode 100644 index 000000000000..829090e403ac --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup.component.css @@ -0,0 +1,37 @@ +:host { + display: block; +} +.l-one-depth-list { + width: 100%; + margin: 0 0; + padding: 0 0; + list-style-type: none; +} +.l-one-depth-list > li { + border-bottom: 1px solid #E5E5E5; + padding: 10px; +} +.l-one-depth-list > li:last-of-type { + border-bottom: none; +} +li.l-selectable-command:first-of-type .fa-filter { + color:#4b99e3; +} +li.l-selectable-command:last-of-type .fa-filter { + color: #e95459; +} +li.l-selectable-command { + cursor: pointer; +} +li.l-selectable-command:hover { + background-color: #e4f3eb; +} +.l-two-depth-list { + padding-top: 10px; +} +.l-two-depth-list li { + padding: 4px; +} +.l-fas { + margin-right: 4px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup.component.html b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup.component.html new file mode 100644 index 000000000000..2f88aa450360 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup.component.html @@ -0,0 +1,4 @@ +
    +
  • Filter Transaction
  • +
  • Filter Transaction Wizard
  • +
diff --git a/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup.component.ts b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup.component.ts new file mode 100644 index 000000000000..c88fdb9d4079 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/link-context-popup/link-context-popup.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-link-context-popup', + templateUrl: './link-context-popup.component.html', + styleUrls: ['./link-context-popup.component.css'] +}) +export class LinkContextPopupComponent implements OnInit { + @Output() outClickFilterTransaction = new EventEmitter(); + @Output() outClickFilterTransactionWizard = new EventEmitter(); + + constructor() {} + ngOnInit() {} + onClickFilterTransaction(): void { + this.outClickFilterTransaction.emit(); + } + + onClickFilterTransactionWizard(): void { + this.outClickFilterTransactionWizard.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/index.ts b/web/src/main/webapp/v2/src/app/core/components/load-chart/index.ts new file mode 100644 index 000000000000..4bb052142f0f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/index.ts @@ -0,0 +1,29 @@ + +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { LoadChartComponent } from './load-chart.component'; +import { LoadChartForSideBarContainerComponent } from './load-chart-for-side-bar-container.component'; +import { LoadChartForInfoPerServerContainerComponent } from './load-chart-for-info-per-server-container.component'; +import { LoadChartForFilteredMapSideBarContainerComponent } from './load-chart-for-filtered-map-side-bar-container.component'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + LoadChartComponent, + LoadChartForSideBarContainerComponent, + LoadChartForFilteredMapSideBarContainerComponent, + LoadChartForInfoPerServerContainerComponent + ], + imports: [ + SharedModule, + HelpViewerPopupModule + ], + exports: [ + LoadChartForSideBarContainerComponent, + LoadChartForFilteredMapSideBarContainerComponent, + LoadChartForInfoPerServerContainerComponent + ], + providers: [] +}) +export class LoadChartModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-filtered-map-side-bar-container.component.css b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-filtered-map-side-bar-container.component.css new file mode 100644 index 000000000000..0919f4cddd72 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-filtered-map-side-bar-container.component.css @@ -0,0 +1,34 @@ +:host { + display: block; + position: relative; +} +.l-chart-item { + height: 253px; +} +.l-tool-box { + font-size: 14px; + color: #b3b5b9; + text-align: right; + position: relative; + padding: 16px 25px 0; +} +.l-tool-box .l-title { + float: left; + text-align: left; + font-size: 16px; + color: #333; + font-weight: 600; + margin: 0 0 23px; +} +.l-tool-box button { + font-size: 18px; + margin: 0; +} +.l-content-section { + padding: 0 25px 0; +} +.l-no-data { + padding-top: 60px; + text-align: center; + font-weight: 600; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-filtered-map-side-bar-container.component.html b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-filtered-map-side-bar-container.component.html new file mode 100644 index 000000000000..8f52d2296740 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-filtered-map-side-bar-container.component.html @@ -0,0 +1,20 @@ +
+
+

Load

+ +
+
+ + +
+ {{i18nText.NO_DATA}} +
+
+
+ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-filtered-map-side-bar-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-filtered-map-side-bar-container.component.ts new file mode 100644 index 000000000000..4d24f3fc80cc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-filtered-map-side-bar-container.component.ts @@ -0,0 +1,157 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, StoreHelperService, AgentHistogramDataService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { ServerMapData } from 'app/core/components/server-map/class/server-map-data.class'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-load-chart-for-filtered-map-side-bar-container', + templateUrl: './load-chart-for-filtered-map-side-bar-container.component.html', + styleUrls: ['./load-chart-for-filtered-map-side-bar-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class LoadChartForFilteredMapSideBarContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private timezone: string; + private dateFormatMonth: string; + private dateFormatDay: string; + hiddenComponent = false; + hiddenChart = false; + yMax = -1; + selectedTarget: ISelectedTarget; + selectedAgent = ''; + serverMapData: ServerMapData; + useDisable = false; + showLoading = false; + i18nText = { + NO_DATA: '' + }; + chartData: IHistogram[]; + chartColors: string[]; + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private webAppSettingDataService: WebAppSettingDataService, + private agentHistogramDataService: AgentHistogramDataService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.chartColors = this.webAppSettingDataService.getColorByRequest(); + this.translateService.get('COMMON.NO_DATA').subscribe((txt: string) => { + this.i18nText.NO_DATA = txt; + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => { + this.timezone = timezone; + if (this.chartData) { + this.loadLoadChartData(); + } + }); + this.storeHelperService.getDateFormatArray(this.unsubscribe, 5, 6).subscribe((dateFormat: string[]) => { + this.dateFormatMonth = dateFormat[0]; + this.dateFormatDay = dateFormat[1]; + if (this.chartData) { + this.loadLoadChartData(); + } + }); + this.storeHelperService.getAgentSelection(this.unsubscribe).subscribe((agent: string) => { + this.setDisable(true); + this.selectedAgent = agent; + if (this.selectedTarget) { + this.loadLoadChartData(); + } + this.changeDetector.detectChanges(); + }); + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: ServerMapData) => { + this.serverMapData = serverMapData; + if (this.selectedTarget && this.selectedTarget.isMerged === false) { + this.yMax = -1; + this.loadLoadChartData(); + } + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.yMax = -1; + this.selectedTarget = target; + this.hiddenComponent = target.isMerged; + if (target.isMerged === false) { + this.loadLoadChartData(); + } + this.changeDetector.detectChanges(); + }); + this.storeHelperService.getServerMapTargetSelectedByList(this.unsubscribe).subscribe((target: any) => { + this.yMax = -1; + this.hiddenComponent = false; + this.passDownChartData(this.agentHistogramDataService.makeChartDataForLoad(target.timeSeriesHistogram, this.timezone, [this.dateFormatMonth, this.dateFormatDay], this.getChartYMax())); + }); + } + private setDisable(disable: boolean): void { + this.useDisable = disable; + this.showLoading = disable; + } + private getChartYMax(): number { + return this.yMax === -1 ? null : this.yMax; + } + private loadLoadChartData(from?: number, to?: number): void { + const target = this.getTargetInfo(); + if (this.selectedAgent === '') { + this.passDownChartData(this.agentHistogramDataService.makeChartDataForLoad(target.timeSeriesHistogram, this.timezone, [this.dateFormatMonth, this.dateFormatDay], this.getChartYMax())); + } else { + this.passDownChartData(this.agentHistogramDataService.makeChartDataForLoad(target['agentTimeSeriesHistogram'][this.selectedAgent], this.timezone, [this.dateFormatMonth, this.dateFormatDay], this.getChartYMax())); + } + } + private passDownChartData(chartData: any): void { + if (chartData) { + this.hiddenChart = false; + this.chartData = chartData; + } else { + this.hiddenChart = true; + } + this.setDisable(false); + this.changeDetector.detectChanges(); + } + private getTargetInfo(): any { + if (this.selectedTarget.isNode) { + return this.serverMapData.getNodeData(this.selectedTarget.node[0]); + } else { + return this.serverMapData.getLinkData(this.selectedTarget.link[0]); + } + } + onNotifyMax(max: number) { + if (max > this.yMax) { + this.yMax = max; + this.storeHelperService.dispatch(new Actions.ChangeLoadChartYMax(max)); + } + } + onClickColumn($event: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_LOAD_GRAPH); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.LOAD); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.LOAD, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-info-per-server-container.component.css b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-info-per-server-container.component.css new file mode 100644 index 000000000000..28e7365d3a05 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-info-per-server-container.component.css @@ -0,0 +1,29 @@ +:host { + display: block; +} +.l-chart-item { + height: 253px; +} +.l-tool-box { + font-size: 14px; + color: #b3b5b9; + text-align: right; + position: relative; + padding: 16px 25px 0; +} +.l-tool-box .l-title { + float: left; + text-align: left; + font-size: 16px; + color: #333; + font-weight: 600; + margin: 0 0 23px; +} +.l-content-section { + padding: 0 25px 0; +} +.l-no-data { + padding-top: 60px; + text-align: center; + font-weight: 600; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-info-per-server-container.component.html b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-info-per-server-container.component.html new file mode 100644 index 000000000000..98c98ad1b784 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-info-per-server-container.component.html @@ -0,0 +1,19 @@ +
+
+

Load

+ +
+
+ + +
+
+ {{i18nText.NO_DATA}} +
+
+ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-info-per-server-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-info-per-server-container.component.ts new file mode 100644 index 000000000000..c222f01cc003 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-info-per-server-container.component.ts @@ -0,0 +1,102 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { WebAppSettingDataService, StoreHelperService, AgentHistogramDataService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-load-chart-for-info-per-server-container', + templateUrl: './load-chart-for-info-per-server-container.component.html', + styleUrls: ['./load-chart-for-info-per-server-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class LoadChartForInfoPerServerContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private lastChartData: any = null; + private timezone: string; + private dateFormatMonth: string; + private dateFormatDay: string; + hiddenChart = false; + yMax: number; + chartData: IHistogram[]; + chartColors: string[]; + i18nText = { + NO_DATA: '' + }; + useDisable = false; + showLoading = false; + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private webAppSettingDataService: WebAppSettingDataService, + private agentHistogramDataService: AgentHistogramDataService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.chartColors = this.webAppSettingDataService.getColorByRequest(); + this.translateService.get('COMMON.NO_DATA').subscribe((txt: string) => { + this.i18nText.NO_DATA = txt; + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => { + this.timezone = timezone; + if (this.lastChartData) { + this.makeChartData(); + } + }); + this.storeHelperService.getDateFormatArray(this.unsubscribe, 5, 6).subscribe((dateFormat: string[]) => { + this.dateFormatMonth = dateFormat[0]; + this.dateFormatDay = dateFormat[1]; + if (this.lastChartData) { + this.makeChartData(); + } + }); + this.storeHelperService.getLoadChartYMax(this.unsubscribe).subscribe((max: number) => { + this.yMax = max; + }); + this.storeHelperService.getAgentSelectionForServerList(this.unsubscribe).pipe( + filter((chartData: IAgentSelection) => { + return (chartData && chartData.agent) ? true : false; + }) + ).subscribe((chartData: IAgentSelection) => { + this.lastChartData = chartData; + this.makeChartData(); + }); + } + private makeChartData(): void { + if (this.lastChartData.load) { + this.hiddenChart = false; + this.chartData = this.agentHistogramDataService.makeChartDataForLoad(this.lastChartData.load, this.timezone, [this.dateFormatMonth, this.dateFormatDay], this.yMax); + } else { + this.hiddenChart = true; + } + this.changeDetector.detectChanges(); + } + onNotifyMax(max: number): void {} + onClickColumn($event: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_LOAD_GRAPH); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.LOAD); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.LOAD, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-side-bar-container.component.css b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-side-bar-container.component.css new file mode 100644 index 000000000000..0919f4cddd72 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-side-bar-container.component.css @@ -0,0 +1,34 @@ +:host { + display: block; + position: relative; +} +.l-chart-item { + height: 253px; +} +.l-tool-box { + font-size: 14px; + color: #b3b5b9; + text-align: right; + position: relative; + padding: 16px 25px 0; +} +.l-tool-box .l-title { + float: left; + text-align: left; + font-size: 16px; + color: #333; + font-weight: 600; + margin: 0 0 23px; +} +.l-tool-box button { + font-size: 18px; + margin: 0; +} +.l-content-section { + padding: 0 25px 0; +} +.l-no-data { + padding-top: 60px; + text-align: center; + font-weight: 600; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-side-bar-container.component.html b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-side-bar-container.component.html new file mode 100644 index 000000000000..8f52d2296740 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-side-bar-container.component.html @@ -0,0 +1,20 @@ +
+
+

Load

+ +
+
+ + +
+ {{i18nText.NO_DATA}} +
+
+
+ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-side-bar-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-side-bar-container.component.ts new file mode 100644 index 000000000000..60f7c62e6cec --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart-for-side-bar-container.component.ts @@ -0,0 +1,163 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, StoreHelperService, AgentHistogramDataService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { ServerMapData } from 'app/core/components/server-map/class/server-map-data.class'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-load-chart-for-side-bar-container', + templateUrl: './load-chart-for-side-bar-container.component.html', + styleUrls: ['./load-chart-for-side-bar-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class LoadChartForSideBarContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private timezone: string; + private dateFormatMonth: string; + private dateFormatDay: string; + hiddenComponent = false; + hiddenChart = false; + yMax = -1; + selectedTarget: ISelectedTarget = null; + selectedAgent = ''; + serverMapData: ServerMapData; + useDisable = false; + showLoading = false; + i18nText = { + NO_DATA: '' + }; + chartData: IHistogram[]; + chartColors: string[]; + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private webAppSettingDataService: WebAppSettingDataService, + private agentHistogramDataService: AgentHistogramDataService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.chartColors = this.webAppSettingDataService.getColorByRequest(); + this.translateService.get('COMMON.NO_DATA').subscribe((txt: string) => { + this.i18nText.NO_DATA = txt; + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => { + this.timezone = timezone; + if (this.selectedTarget) { + this.loadLoadChartData(); + } + }); + this.storeHelperService.getDateFormatArray(this.unsubscribe, 5, 6).subscribe((dateFormat: string[]) => { + this.dateFormatMonth = dateFormat[0]; + this.dateFormatDay = dateFormat[1]; + if (this.selectedTarget) { + this.loadLoadChartData(); + } + }); + this.storeHelperService.getAgentSelection(this.unsubscribe).subscribe((agent: string) => { + this.setDisable(true); + this.selectedAgent = agent; + if (this.selectedTarget) { + this.loadLoadChartData(); + this.changeDetector.detectChanges(); + } + }); + this.storeHelperService.getRealTimeScatterChartRange(this.unsubscribe).subscribe(({ from, to }: IScatterXRange) => { + this.yMax = -1; + this.loadLoadChartData(from, to); + }); + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: ServerMapData) => { + this.serverMapData = serverMapData; + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.yMax = -1; + this.selectedTarget = target; + this.hiddenComponent = target.isMerged; + if (target.isMerged === false) { + this.loadLoadChartData(); + } + this.changeDetector.detectChanges(); + }); + this.storeHelperService.getServerMapTargetSelectedByList(this.unsubscribe).subscribe((target: any) => { + this.yMax = -1; + this.hiddenComponent = false; + this.passDownChartData(this.agentHistogramDataService.makeChartDataForLoad(target.timeSeriesHistogram, this.timezone, [this.dateFormatMonth, this.dateFormatDay], this.getChartYMax())); + }); + } + private setDisable(disable: boolean): void { + this.useDisable = disable; + this.showLoading = disable; + } + private getChartYMax(): number { + return this.yMax === -1 ? null : this.yMax; + } + private loadLoadChartData(from?: number, to?: number): void { + const target = this.getTargetInfo(); + if (this.isAllAgent() && arguments.length !== 2) { + this.passDownChartData(this.agentHistogramDataService.makeChartDataForLoad(target.timeSeriesHistogram, this.timezone, [this.dateFormatMonth, this.dateFormatDay], this.getChartYMax())); + } else { + this.agentHistogramDataService.getData(target.key, target.applicationName, target.serviceTypeCode, this.serverMapData, from, to).subscribe((chartData: any) => { + const chartDataForAgent = this.isAllAgent() ? chartData['timeSeriesHistogram'] : chartData['agentTimeSeriesHistogram'][this.selectedAgent]; + this.passDownChartData(this.agentHistogramDataService.makeChartDataForLoad(chartDataForAgent, this.timezone, [this.dateFormatMonth, this.dateFormatDay], this.getChartYMax())); + }); + } + } + private passDownChartData(chartData: any): void { + if (chartData) { + this.hiddenChart = false; + this.chartData = chartData; + } else { + this.hiddenChart = true; + } + this.setDisable(false); + this.changeDetector.detectChanges(); + } + private getTargetInfo(): any { + if (this.selectedTarget.isNode) { + return this.serverMapData.getNodeData(this.selectedTarget.node[0]); + } else { + return this.serverMapData.getLinkData(this.selectedTarget.link[0]); + } + } + private isAllAgent(): boolean { + return this.selectedAgent === ''; + } + onNotifyMax(max: number) { + if (this.yMax === -1) { + this.yMax = max; + this.storeHelperService.dispatch(new Actions.ChangeLoadChartYMax(max)); + } + } + onClickColumn($event: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_LOAD_GRAPH); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.LOAD); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.LOAD, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart.component.css b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart.component.css new file mode 100644 index 000000000000..ca3b73860ccd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart.component.css @@ -0,0 +1,6 @@ +:host { + width: 100%; + height: 200px; + display: block; + position: relative; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart.component.html b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart.component.html new file mode 100644 index 000000000000..214e1a55d8a0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart.component.html @@ -0,0 +1 @@ + diff --git a/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart.component.ts b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart.component.ts new file mode 100644 index 000000000000..b6ee7cd817d6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/load-chart/load-chart.component.ts @@ -0,0 +1,138 @@ +import { Component, OnInit, OnChanges, ViewChild, ElementRef, SimpleChanges, Input, Output, EventEmitter } from '@angular/core'; +import { Chart } from 'chart.js'; + +// @TODO Loading 전 화면 처리 +@Component({ + selector: 'pp-load-chart', + templateUrl: './load-chart.component.html', + styleUrls: ['./load-chart.component.css'] +}) +export class LoadChartComponent implements OnInit, OnChanges { + @ViewChild('loadChart') el: ElementRef; + @Input() chartData: any; + @Input() chartColors: string[]; + @Output() outNotifyMax: EventEmitter = new EventEmitter(); + @Output() outClickColumn: EventEmitter = new EventEmitter(); + chartObj: any; + constructor() {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if (changes['chartData'] && changes['chartData']['firstChange'] === false) { + this.initChart(); + } + } + private initChart(): void { + if (this.chartObj) { + if (this.chartData.max) { + this.chartObj.config.options.scales.yAxes[0].ticks.max = this.chartData.max; + } + this.chartObj.data.labels = this.chartData.labels; + this.chartData.keyValues.forEach((keyValues: any, index: number) => { + this.chartObj.data.datasets[index].data = keyValues.values; + this.chartObj.data.datasets[index].label = keyValues.key; + }); + this.chartObj.update(); + } else { + this.chartObj = new Chart(this.el.nativeElement.getContext('2d'), { + type: 'bar', + data: this.makeDataOption(), + options: this.makeNormalOption() + }); + } + this.outNotifyMax.emit(this.chartObj.scales['y-axis-0'].max); + } + private makeDataOption(): any { + const dataOption = { + labels: this.chartData.labels, + borderWidth: 0, + datasets: [] + }; + + this.chartData.keyValues.forEach((keyValues: any, index: number) => { + dataOption.datasets.push({ + label: keyValues.key, + data: keyValues.values, + backgroundColor: this.chartColors[index], + borderColor: 'rgba(120, 119, 121, 0.8)', + borderWidth: 0 + }); + }); + return dataOption; + } + private makeNormalOption(): any { + return { + onClick: (event: any, aChartEl: any[]) => { + if ( aChartEl.length > 0 ) { + this.outClickColumn.emit(aChartEl[0]._view.label); + } + event.preventDefault(); + // AnalyticsService.send(AnalyticsService.CONST.MAIN, AnalyticsService.CONST.CLK_LOAD_GRAPH); + }, + maintainAspectRatio: false, + tooltips: { + mode: 'label', + bodySpacing: 6 + }, + scales: { + yAxes: [{ + gridLines: { + display: true, + drawBorder: false, + zeroLineWidth: 1.5, + zeroLineColor: 'rgb(0, 0, 0)' + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 4, + callback: (label: number) => { + return ' ' + (label >= 1000 ? `${label / 1000}k` : label) + ' '; + }, + fontColor: 'rgba(162, 162, 162, 1)', + fontSize: 11, + max: this.chartData.max + }, + stacked: true + }], + xAxes: [{ + gridLines: { + display: false, + drawBorder: false + }, + ticks: { + maxTicksLimit: 6, + callback: (label: string): string[] => { + return label.split('#'); + }, + autoSkip: true, + fontColor: 'rgba(162, 162, 162, 1)', + fontSize: 11, + max: this.chartData.max + }, + categoryPercentage: 1.0, + barPercentage: 1.0, + stacked: true, + display: true + }] + }, + animation: { + duration: 0 + }, + legend: { + display: true, + labels: { + boxWidth: 30, + padding: 10 + }, + position: 'bottom' + } + }; + } + getPreSpace(str: string) { + const space = ' '; // 7 is max space + if (str.length > space.length) { + return str; + } else { + return space.substr(0, space.length - str.length); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/main-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/main-contents/index.ts new file mode 100644 index 000000000000..670642d831b3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/main-contents/index.ts @@ -0,0 +1,27 @@ + +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { FixedPeriodMoverModule } from 'app/core/components/fixed-period-mover'; +import { ServerMapSearchResultViewerModule } from 'app/core/components/server-map-search-result-viewer'; +import { ServerMapModule } from 'app/core/components/server-map'; +import { MainContentsContainerComponent } from './main-contents-container.component'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + MainContentsContainerComponent + ], + imports: [ + SharedModule, + FixedPeriodMoverModule, + ServerMapSearchResultViewerModule, + ServerMapModule, + HelpViewerPopupModule + ], + exports: [ + MainContentsContainerComponent + ], + providers: [] +}) +export class MainContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/main-contents/main-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/main-contents/main-contents-container.component.css new file mode 100644 index 000000000000..f47e448eed3b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/main-contents/main-contents-container.component.css @@ -0,0 +1,17 @@ +.l-main-contents-top { + display: flex; + flex-flow: row wrap; + margin: 11px 0 0 25px; + align-items: center; + top: 0; + right: 0; + z-index: 8; + padding:0 10px; + font-size:13px; + justify-content: flex-end; + position:absolute; +} +.fas { + color:#a8acb5; + font-size:18px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/main-contents/main-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/main-contents/main-contents-container.component.html new file mode 100644 index 000000000000..a2e69aa3a065 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/main-contents/main-contents-container.component.html @@ -0,0 +1,6 @@ +
+ + + +
+ diff --git a/web/src/main/webapp/v2/src/app/core/components/main-contents/main-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/main-contents/main-contents-container.component.ts new file mode 100644 index 000000000000..53b531b52b06 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/main-contents/main-contents-container.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; + +import { DynamicPopupService } from 'app/shared/services'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-main-contents-container', + templateUrl: './main-contents-container.component.html', + styleUrls: ['./main-contents-container.component.css'] +}) +export class MainContentsContainerComponent implements OnInit { + constructor( + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() {} + onShowHelp($event: MouseEvent): void { + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.SERVER_MAP, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/message-popup/index.ts b/web/src/main/webapp/v2/src/app/core/components/message-popup/index.ts new file mode 100644 index 000000000000..2f416d2241da --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/message-popup/index.ts @@ -0,0 +1,22 @@ + +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { MessagePopupContainerComponent } from './message-popup-container.component'; +import { MessagePopupComponent } from 'app/core/components/message-popup/message-popup.component'; + +@NgModule({ + declarations: [ + MessagePopupContainerComponent, + MessagePopupComponent + ], + imports: [ + SharedModule + ], + exports: [], + entryComponents: [ + MessagePopupContainerComponent + ], + providers: [] +}) +export class MessagePopupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup-container.component.css b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup-container.component.css new file mode 100644 index 000000000000..3c6a97654ec4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup-container.component.css @@ -0,0 +1,18 @@ +:host { + display: block; + background-color: transparent; + width: 100%; + height: 100%; +} + +:host::before { + content: ''; + display: block; + height: 100%; + width: 100%; + background: #000; + opacity: 0.6; + position: absolute; + left: 0; + top: 0; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup-container.component.html b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup-container.component.html new file mode 100644 index 000000000000..5cb0e8c1493b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup-container.component.html @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup-container.component.ts new file mode 100644 index 000000000000..cf294dd730c3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup-container.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit, Input, Output, EventEmitter, AfterViewInit } from '@angular/core'; + +import { DynamicPopup } from 'app/shared/services'; + +@Component({ + selector: 'pp-message-popup-container', + templateUrl: './message-popup-container.component.html', + styleUrls: ['./message-popup-container.component.css'], +}) +export class MessagePopupContainerComponent implements OnInit, AfterViewInit, DynamicPopup { + @Input() data: ITransactionMessage; + @Output() outClose = new EventEmitter(); + @Output() outCreated = new EventEmitter(); + + constructor() {} + ngOnInit() {} + ngAfterViewInit() { + this.outCreated.emit({ coordX: 0, coordY: 0 }); + } + + onClosePopup() { + this.outClose.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup.component.css b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup.component.css new file mode 100644 index 000000000000..60a9ec136756 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup.component.css @@ -0,0 +1,75 @@ +:host { + display: block; + position: absolute; + width: 100%; + min-width: 500px; + max-width: 1000px; + background-color: #fff; + border: 1px solid #e5e8f0; + box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.75); + text-align: left; + top: 50%; + transform: translateX(-50%) translateY(-50%); + left: 50%; +} +.l-title-group { + padding: 17px 18px; + height: auto; + background-color: #fff; + border-bottom: 1px solid #e5e8f0; + position: relative; + font-size: 13px; + font-weight: 600; + color: #333; + display: flex; + align-items: center; + justify-content: space-between; +} +.l-title-group dt { + font-size: 20px; + font-weight: 600; + color: #4a8fd2; +} +.l-title-group button { + position:absolute; + right:27px; + top:50%; + transform:translateY(-50%); + color:#4a8fd2; + font-size: 30px; + width:20px; + height:20px; + background:url(../../../../assets/img/icon-close.png) no-repeat 0 0; +} +.l-contents-group { + background: #f6f8fb; + padding: 17px 18px; + overflow: auto; +} +.l-list { + height: 400px; + margin: 30px 0 0 0; +} +.l-list:first-child { + margin: 0; +} +.l-list dt { + color: #333; + margin: 0 0 12px; + font-size: 13px; + font-weight: 600; +} +.l-list dd { + height: 100%; + border: 1px solid #cfd7e1; + display: block; + padding: 28px 28px; + font-size: 13px; + background: #fff; + word-break: break-all; + overflow-y: auto; + line-height: 2em; +} +.l-list a { + color: #4a8fd2; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup.component.html b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup.component.html new file mode 100644 index 000000000000..a73f5ca2df9c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup.component.html @@ -0,0 +1,13 @@ +
+
+
{{data.title}}
+
+ +
+
+
+
+
+
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup.component.ts b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup.component.ts new file mode 100644 index 000000000000..ce9e46c96d13 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/message-popup/message-popup.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit, Input, Output, EventEmitter, HostBinding } from '@angular/core'; + +@Component({ + selector: 'pp-message-popup', + templateUrl: './message-popup.component.html', + styleUrls: ['./message-popup.component.css'] +}) +export class MessagePopupComponent implements OnInit { + @Input() data: ITransactionMessage; + @Output() outClosePopup = new EventEmitter(); + @HostBinding('class.font-opensans') fontFamily = true; + + constructor() {} + ngOnInit() {} + onClose(): void { + this.outClosePopup.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/notice/index.ts b/web/src/main/webapp/v2/src/app/core/components/notice/index.ts new file mode 100644 index 000000000000..8f35a438ebda --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/notice/index.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { NoticeContainerComponent } from 'app/core/components/notice/notice-container.component'; + +@NgModule({ + imports: [ + SharedModule, + ], + exports: [ + NoticeContainerComponent + ], + declarations: [ + NoticeContainerComponent + ], + providers: [], +}) +export class NoticeModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/notice/notice-container.component.css b/web/src/main/webapp/v2/src/app/core/components/notice/notice-container.component.css new file mode 100644 index 000000000000..c8c63d1c4847 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/notice/notice-container.component.css @@ -0,0 +1,22 @@ +:host { + display: none; + position: relative; + padding: 8px 10px; + background-color: #f3e0df; + color: #c04e3f; +} + +.l-notice-message { + font-size: 13px; +} + +.l-notice-message > .fas { + margin-right: 4px; +} + +.l-close-notice-area-button { + position: absolute; + top: 7px; + right: 15px; + font-size: 15px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/notice/notice-container.component.html b/web/src/main/webapp/v2/src/app/core/components/notice/notice-container.component.html new file mode 100644 index 000000000000..035bfe2d826b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/notice/notice-container.component.html @@ -0,0 +1,4 @@ +

+ diff --git a/web/src/main/webapp/v2/src/app/core/components/notice/notice-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/notice/notice-container.component.ts new file mode 100644 index 000000000000..8576413273bf --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/notice/notice-container.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, ElementRef, Renderer2 } from '@angular/core'; +import { Observable } from 'rxjs'; +import { tap, filter } from 'rxjs/operators'; + +import { BrowserSupportCheckService } from 'app/shared/services'; + +@Component({ + selector: 'pp-notice-container', + templateUrl: './notice-container.component.html', + styleUrls: ['./notice-container.component.css'] +}) +export class NoticeContainerComponent implements OnInit { + noticeMessage$: Observable; + + constructor( + private elementRef: ElementRef, + private renderer: Renderer2, + private browserSupportCheckService: BrowserSupportCheckService + ) { } + + ngOnInit() { + this.noticeMessage$ = this.browserSupportCheckService.getMessage().pipe( + filter((message: string) => { + return message.length !== 0; + }), + tap(() => this.show()) + ); + } + + onClose(): void { + this.hide(); + } + + private hide(): void { + this.renderer.setStyle(this.elementRef.nativeElement, 'display', 'none'); + } + + private show(): void { + this.renderer.setStyle(this.elementRef.nativeElement, 'display', 'block'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/index.ts b/web/src/main/webapp/v2/src/app/core/components/period-selector/index.ts new file mode 100644 index 000000000000..dc631dae80bf --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/index.ts @@ -0,0 +1,27 @@ + +import { NgModule } from '@angular/core'; +import { NguiDatetimePickerModule } from '@ngui/datetime-picker'; +import { SharedModule } from 'app/shared'; + +import { PeriodSelectorUsingReservedTimeComponent } from './period-selector-using-reserved-time.component'; +import { PeriodSelectorUsingCalendarComponent } from './period-selector-using-calendar.component'; +import { PeriodSelectorComponent } from './period-selector.component'; +import { PeriodSelectorContainerComponent } from './period-selector-container.component'; + +@NgModule({ + declarations: [ + PeriodSelectorUsingReservedTimeComponent, + PeriodSelectorUsingCalendarComponent, + PeriodSelectorComponent, + PeriodSelectorContainerComponent + ], + imports: [ + NguiDatetimePickerModule, + SharedModule + ], + exports: [ + PeriodSelectorContainerComponent + ], + providers: [] +}) +export class PeriodSelectorModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-container.component.css b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-container.component.html b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-container.component.html new file mode 100644 index 000000000000..ac3e6476d76c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-container.component.html @@ -0,0 +1,14 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-container.component.ts new file mode 100644 index 000000000000..4c642c1a288d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-container.component.ts @@ -0,0 +1,119 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject, Observable, combineLatest } from 'rxjs'; +import { takeUntil, tap } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { + TranslateReplaceService, + StoreHelperService, + WebAppSettingDataService, + UrlRouteManagerService, + NewUrlStateNotificationService, + AnalyticsService, + TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { Period } from 'app/core/models/period'; +import { EndTime } from 'app/core/models/end-time'; + +@Component({ + selector: 'pp-period-selector-container', + templateUrl: './period-selector-container.component.html', + styleUrls: ['./period-selector-container.component.css'] +}) +export class PeriodSelectorContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + i18nText = { + MAX_PERIOD: '', + }; + hiddenComponent: boolean; + selectedPeriod: Period; + selectedEndTime: EndTime; + periodList: Array; + maxPeriod: number; + isRealTimeMode: boolean; + showRealTimeButton: boolean; + timezone$: Observable; + dateFormat$: Observable; + constructor( + private webAppSettingDataService: WebAppSettingDataService, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + private analyticsService: AnalyticsService, + ) {} + ngOnInit() { + this.periodList = this.webAppSettingDataService.getPeriodList(this.newUrlStateNotificationService.getStartPath()); + this.maxPeriod = this.periodList[this.periodList.length - 1].getValue(); + this.getI18NText(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + tap((urlService: NewUrlStateNotificationService) => { + this.showRealTimeButton = urlService.showRealTimeButton(); + this.isRealTimeMode = urlService.isRealTimeMode(); + }) + ).subscribe((urlService: NewUrlStateNotificationService) => { + if ( this.showRealTimeButton && this.isRealTimeMode ) { + this.hiddenComponent = false; + this.selectedPeriod = this.webAppSettingDataService.getSystemDefaultPeriod(); + this.selectedEndTime = EndTime.newByNumber(urlService.getUrlServerTimeData()); + } else { + if (urlService.hasValue(UrlPathId.PERIOD, UrlPathId.END_TIME)) { + this.hiddenComponent = false; + this.selectedPeriod = urlService.getPathValue(UrlPathId.PERIOD); + this.selectedEndTime = urlService.getPathValue(UrlPathId.END_TIME); + } else { + this.hiddenComponent = true; + } + } + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.dateFormat$ = this.storeHelperService.getDateFormat(this.unsubscribe, 1); + } + private getI18NText(): void { + combineLatest( + this.translateService.get('COMMON.MAX_SEARCH_PERIOD') + ).subscribe((i18n: string[]) => { + this.i18nText.MAX_PERIOD = this.translateReplaceService.replace(i18n[0], this.maxPeriod / 24 / 60); + }); + } + onChangePeriodTime(selectedPeriod: string): void { + if (this.newUrlStateNotificationService.isRealTimeMode(selectedPeriod)) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SET_PERIOD_AS_REAL_TIME); + this.urlRouteManagerService.moveToRealTime(); + } else { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_PERIOD, selectedPeriod); + this.urlRouteManagerService.move({ + url: [ + this.newUrlStateNotificationService.getStartPath(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + selectedPeriod + ], + needServerTimeRequest: true, + nextUrl: this.newUrlStateNotificationService.hasValue(UrlPathId.AGENT_ID) ? [this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID)] : [] + }); + } + } + onChangeCalendarTime(oChangeTime: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_PERIOD, oChangeTime.period.getValueWithTime()); + this.urlRouteManagerService.move({ + url: [ + this.newUrlStateNotificationService.getStartPath(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + oChangeTime.period.getValueWithTime(), + oChangeTime.endTime.getEndTime() + ], + needServerTimeRequest: false, + nextUrl: this.newUrlStateNotificationService.hasValue(UrlPathId.AGENT_ID) ? [this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID)] : [] + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-calendar.component.css b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-calendar.component.css new file mode 100644 index 000000000000..649edca3d89f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-calendar.component.css @@ -0,0 +1,145 @@ +.l-calendar { + height: 100%; + position: relative; +} +.l-input-group { + display: flex; + flex-flow: row wrap; + align-items: center; + height: 100%; + justify-content: space-between; + background: #3e506b; + color: #fff; + width: 427px; +} +.l-input-group input { + font-size: 12px; + width: 180px; + font-weight: 600; + text-align: center; +} + +.l-input-wrap { + display: flex; + flex-flow: row wrap; + flex: 1 1 0; + justify-content: center; + height: 100%; + border-left:1px solid #4c5c75; + box-shadow: -1px 0px 0px #303f59; + align-items: center; +} +.l-input-wrap .l-sy { + display: inline-block; + margin: 0 6px; +} +.l-input-group .fa-search { + width: 35px; + height: 100%; + border-left: 1px solid #4c5c75; + box-shadow: -1px 0px 0px #303f59; +} + +.l-inner-calendar { + height: 260px; + border-bottom: 1px solid #DBDEE6; +} + +.l-calendar-group { + bottom: -439px; + position: absolute; + left: -36px; + background:#fff; + border: 1px solid #4477CB; + width: calc(100% + 37px); + z-index: 701; +} +.l-calendar-group .l-calendar-wrap { + display: flex; + flex-flow: row wrap; +} +.l-calendar-section { + flex:1; +} +.l-calendar-section:nth-last-child(2) { + border-right: 1px solid #dbdee6; +} +.l-calendar-section .l-set-group { + background: #f8f9fb; + color: #777879; + font-size: 12px; + padding: 15px 13px; +} +.l-calendar-section .l-set-group li { + margin: 6px 0 0 0; + padding: 0 0 0 10px; + background:url(../../../../assets/img/icon-calendar-arrow.png) no-repeat 0 center; + border-left: none; +} +.l-calendar-section .l-set-group li:first-child { + margin-top:0; +} +.l-calendar-section .l-set-group li span { + display: inline-block; + width: 52px; + border-right: 1px solid #e6e8ec; + margin: 0 10px 0 0; +} +.l-calendar-section .l-set-group li select { + border: 1px solid #d7dde4; + background:#fff; +} +.l-btn-group { + border: none; + height: 32px; +} +.l-btn-group .l-time-set { + display: flex; + flex-flow: row wrap; + height: 100%; + justify-content: space-between; +} +.l-btn-group .l-time-set li { + color:#777879; + font-size: 13px; + flex: 1; + text-align: center; + border-left:none; +} +.l-btn-group .l-time-set li.active button, .l-btn-group .l-time-set li button:hover, .l-btn-group .l-time-set li button:focus { + border: none; + background:#469ae4; + color:#fff; +} +.l-btn-group .l-time-set button { + background:none; + color:#777879; + border-left:none; + height: 34px; + font-size: 12px; + font-weight: 600; + padding: 0 8px; + border-radius: 0px; + border-bottom: 1px solid #dbdee6; + border-top: 1px solid #dbdee6; + width: 100%; +} + +.l-calendar-group .l-text { + padding: 15px 13px; + font-size: 13px; + color: #38c2a2; +} + +.ngui-datetime-picker { + font: normal 12px sans-serif !important; + border: none !important; +} +.ngui-datetime-picker > .days { + margin: 4px !important; + padding: 0 4px; +} +.ngui-datetime-picker > .days .day.selected { + background: #469ae4 !important; + color: #FFF !important; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-calendar.component.html b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-calendar.component.html new file mode 100644 index 000000000000..cd0904deed82 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-calendar.component.html @@ -0,0 +1,70 @@ +
+
+
+ + - + +
+ +
+
+
+
+ + +
+
    +
  • + Time{{getStartTime()}}
  • +
  • + Hour + +
  • +
  • + Minute + +
  • +
+
+
+
+ + +
+
    +
  • + Time{{getEndTime()}}
  • +
  • + Hour + +
  • +
  • + Minute + +
  • +
+
+
+
+
    +
  • + +
  • +
+
+
{{i18nText.MAX_PERIOD}}
+
+
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-calendar.component.ts b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-calendar.component.ts new file mode 100644 index 000000000000..cbebf580d0e0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-calendar.component.ts @@ -0,0 +1,122 @@ +import { Component, Input, Output, OnInit, OnChanges, SimpleChanges, EventEmitter, ViewEncapsulation } from '@angular/core'; +import * as moment from 'moment-timezone'; +import { Period } from 'app/core/models/period'; +import { EndTime } from 'app/core/models/end-time'; + +@Component({ + selector: 'pp-period-selector-using-calendar', + templateUrl: './period-selector-using-calendar.component.html', + styleUrls: ['./period-selector-using-calendar.component.css'], + encapsulation: ViewEncapsulation.None +}) +export class PeriodSelectorUsingCalendarComponent implements OnInit, OnChanges { + + @Input() isHidden: boolean; + @Input() i18nText: any; + @Input() maxPeriod: number; + @Input() periodList: Array; + @Input() initPeriod: Period; + @Input() initEndTime: EndTime; + @Input() timezone: string; + @Input() dateFormat: string; + @Output() outChangePeriod = new EventEmitter(); + + isPopupHidden = true; + startDate = moment(); + endDate = moment(); + minDate = new Date(1977, 0, 1); + maxDate = new Date(); + hours: Array = Array(24).fill('').map(function (v, i) { + return { + value: i, + display: ('0' + i).substr(-2) + }; + }); + minutes: Array = Array(60).fill('').map(function (v, i) { + return { + value: i, + display: ('0' + i).substr(-2) + }; + }); + wrongDate = false; + constructor() {} + ngOnChanges(changes: SimpleChanges) { + if ((changes['initEndTime'] && changes['initEndTime']['currentValue']) && (changes['initPeriod'] && changes['initPeriod']['currentValue'])) { + this.startDate = moment(this.initEndTime.calcuStartTime(this.initPeriod.getValue()).getDate()).tz(this.timezone); + this.endDate = moment(this.initEndTime.getDate()).tz(this.timezone); + } + } + ngOnInit() {} + getStartTime(): string { + return this.startDate.format('HH:mm Z'); + } + getEndTime(): string { + return this.endDate.format('HH:mm Z'); + } + getStartDate(): string { + return this.startDate.format(this.dateFormat); + } + getEndDate(): string { + return this.endDate.format(this.dateFormat); + } + checkTimeValid(): void { + if (this.endDate.isBefore(this.startDate) || this.endDate.diff(this.startDate, 'minute') > this.maxPeriod) { + this.wrongDate = true; + } else { + this.wrongDate = false; + } + } + onChangedStartDate(date: Date): void { + this.startDate.set({ + 'year': date.getFullYear(), + 'month': date.getMonth(), + 'date': date.getDate() + }); + this.checkTimeValid(); + } + onChangedEndDate(date: Date): void { + this.endDate.set({ + 'year': date.getFullYear(), + 'month': date.getMonth(), + 'date': date.getDate() + }); + this.checkTimeValid(); + } + onClosePopup(): void { + this.isPopupHidden = true; + } + onTogglePopup(): void { + this.isPopupHidden = !this.isPopupHidden; + } + onChangeStartHour(val: string): void { + this.startDate.hour(parseInt(val, 10)); + this.checkTimeValid(); + } + onChangeStartMinute(val: string): void { + this.startDate.minute(parseInt(val, 10)); + this.checkTimeValid(); + } + onChangeEndHour(val: string): void { + this.endDate.hour(parseInt(val, 10)); + this.checkTimeValid(); + } + onChangeEndMinute(val: string): void { + this.endDate.minute(parseInt(val, 10)); + this.checkTimeValid(); + } + onChangeToReservedPeriod($event: any): void { + if ($event.target.tagName.toUpperCase() === 'BUTTON') { + this.startDate = this.endDate.clone().subtract(Period.parseToMinute($event.target.getAttribute('data-period')), 'minute'); + this.checkTimeValid(); + } + } + onSelectPeriod(): void { + if (this.wrongDate === false) { + this.onClosePopup(); + this.outChangePeriod.emit({ + period: new Period(this.endDate.diff(this.startDate, 'minute')), + endTime: EndTime.newByNumber(this.endDate.valueOf()) + }); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-reserved-time.component.css b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-reserved-time.component.css new file mode 100644 index 000000000000..6d0aa99d576e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-reserved-time.component.css @@ -0,0 +1,28 @@ +.l-time-set { + display: flex; + flex-flow: row wrap; + height: 100%; +} +.l-time-set li { + border-left: 1px solid #364660; +} +.l-time-set button { + height: 100%; + font-size:12px; + font-weight:600; + color:#fff; + padding:0 8px; + background: #3e506b; + border-left:1px solid #4c5c75; + border-radius: 0px; +} +.l-time-set button.active { + background:#33b692; +} +.l-time-set button:focus { + background-color:#469ae4; + color:#fff; +} +.l-time-set button:hover, .l-time-set button:focus { + background:#33b692 +} diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-reserved-time.component.html b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-reserved-time.component.html new file mode 100644 index 000000000000..b92799a40c6e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-reserved-time.component.html @@ -0,0 +1,8 @@ +
    +
  • + +
  • +
  • + +
  • +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-reserved-time.component.ts b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-reserved-time.component.ts new file mode 100644 index 000000000000..865e3b45e7d9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector-using-reserved-time.component.ts @@ -0,0 +1,28 @@ +import { Component, EventEmitter, Input, Output, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { Period } from 'app/core/models/period'; + +@Component({ + selector: 'pp-period-selector-using-reserved-time', + templateUrl: './period-selector-using-reserved-time.component.html', + styleUrls: ['./period-selector-using-reserved-time.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PeriodSelectorUsingReservedTimeComponent implements OnInit { + @Input() showRealTimeButton: boolean; + @Input() isRealTimeMode: boolean; + @Input() isHidden: boolean; + @Input() periodList: Array; + @Input() initPeriod: Period; + @Output() outChangePeriod = new EventEmitter(); + constructor() {} + ngOnInit() {} + onSelectPeriod($event: any): void { + if ($event.target.tagName.toUpperCase() === 'BUTTON') { + this.outChangePeriod.emit($event.target.getAttribute('data-period')); + } + } + isSelectedPeriod(period: Period): boolean { + return this.isRealTimeMode === false && period.equals(this.initPeriod); + } + +} diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector.component.css b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector.component.css new file mode 100644 index 000000000000..d14523ded466 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector.component.css @@ -0,0 +1,59 @@ +.l-wrapper { + display: flex; + flex-flow: row wrap; + margin-right: 10px; + border: 1px solid #364660; + border-radius: 0px; + line-height: 1em; + align-items:stretch; + height: 32px; +} +.fas { + font-size: 16px; +} +.l-type-select { + display: flex; + flex-flow: row wrap; + position: relative; +} +.l-type-select li { + position: relative; + border-left: none; +} +.l-type-select button { + height: 100%; + background: #3E506B; + padding: 0 5px; + width: 35px; + color:#38c2a2; +} +.l-type-select li.inactive { + border: 1px solid #364660; + border-radius: 0 0 2px 2px; + overflow: hidden; + bottom: -36px; + left: -1px; + position: absolute; +} +.l-type-select li:after { + content: ''; + width: 0; + height: 0; + border-right: 0 solid #33b692; + border-bottom: 8px solid #33b692; + border-left: 8px solid transparent; + position: absolute; + right: 0; + bottom: 0; +} +.l-type-select li.inactive:after { + display:none; +} +.l-type-select li.inactive button { + color:#7185a2; + height: 34px; + width: 34px; +} +.l-type-select li:first-child button { + border-left: none; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector.component.html b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector.component.html new file mode 100644 index 000000000000..d29b48c7d704 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector.component.html @@ -0,0 +1,27 @@ +
+
    +
  • + +
  • +
  • + +
  • +
+ + +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector.component.ts b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector.component.ts new file mode 100644 index 000000000000..44e45d9aa282 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/period-selector/period-selector.component.ts @@ -0,0 +1,72 @@ +import { Component, OnInit, OnChanges, OnDestroy, Input, Output, EventEmitter, SimpleChanges } from '@angular/core'; + +import { Period } from 'app/core/models/period'; +import { EndTime } from 'app/core/models/end-time'; +import { AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +enum PeriodSelectType { + RESERVED_PERIOD, + CALENDAR_PERIOD +} + +@Component({ + selector: 'pp-period-selector', + templateUrl: './period-selector.component.html', + styleUrls: ['./period-selector.component.css'] +}) +export class PeriodSelectorComponent implements OnInit, OnChanges, OnDestroy { + periodSelectType: PeriodSelectType = PeriodSelectType.RESERVED_PERIOD; + @Input() i18nText: any; + @Input() showRealTimeButton: boolean; + @Input() isRealTimeMode: boolean; + @Input() selectedPeriod: Period; + @Input() selectedEndTime: EndTime; + @Input() periodList: Array; + @Input() maxPeriod: number; + @Input() timezone: string; + @Input() dateFormat: string; + @Output() outChangePeriod: EventEmitter = new EventEmitter(); + @Output() outChangeCalendarTime: EventEmitter = new EventEmitter(); + constructor( + private analyticsService: AnalyticsService, + ) {} + ngOnChanges(changes: SimpleChanges) { + if (changes['selectedPeriod'] && changes['selectedEndTime'] || changes['isRealTimeMode']) { + this.checkPeriodType(); + } + } + ngOnInit() {} + ngOnDestroy() {} + onChangePeriodTime(selectedPeriod: string): void { + this.outChangePeriod.emit(selectedPeriod); + } + onChangeCalendarTime(oChangeTime: any): void { + if (this.selectedEndTime.equals(oChangeTime.endTime) === false || this.selectedPeriod.equals(oChangeTime.period) === false) { + this.outChangeCalendarTime.emit(oChangeTime); + } + } + private checkPeriodType(): void { + if (this.isRealTimeMode) { + this.periodSelectType = PeriodSelectType.RESERVED_PERIOD; + return; + } + for (let i = 0; i < this.periodList.length; i++) { + if (this.periodList[i].equals(this.selectedPeriod)) { + this.periodSelectType = PeriodSelectType.RESERVED_PERIOD; + return; + } + } + this.periodSelectType = PeriodSelectType.CALENDAR_PERIOD; + } + isReservedType(): boolean { + return this.periodSelectType === PeriodSelectType.RESERVED_PERIOD; + } + changeToReservedType(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_PERIOD_SELECT_TYPE, PeriodSelectType[PeriodSelectType.RESERVED_PERIOD]); + this.periodSelectType = PeriodSelectType.RESERVED_PERIOD; + } + changeToCalendarType(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_PERIOD_SELECT_TYPE, PeriodSelectType[PeriodSelectType.CALENDAR_PERIOD]); + this.periodSelectType = PeriodSelectType.CALENDAR_PERIOD; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/index.ts b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/index.ts new file mode 100644 index 000000000000..ca0d4c80b873 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/index.ts @@ -0,0 +1,36 @@ + +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { InfiniteScrollModule } from 'ngx-infinite-scroll'; +import { SharedModule } from 'app/shared'; +import { PinpointUserComponent } from './pinpoint-user.component'; +import { PinpointUserCreateAndUpdateComponent } from './pinpoint-user-create-and-update.component'; +import { PinpointUserContainerComponent } from './pinpoint-user-container.component'; +import { PinpointUserInteractionService } from './pinpoint-user-interaction.service'; +import { PinpointUserDataService } from './pinpoint-user-data.service'; + +@NgModule({ + declarations: [ + PinpointUserComponent, + PinpointUserCreateAndUpdateComponent, + PinpointUserContainerComponent + ], + imports: [ + FormsModule, + ReactiveFormsModule, + InfiniteScrollModule, + SharedModule + ], + exports: [ + PinpointUserContainerComponent + ], + entryComponents: [ + PinpointUserComponent, + PinpointUserContainerComponent + ], + providers: [ + PinpointUserInteractionService, + PinpointUserDataService + ] +}) +export class PinpointUserModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-container.component.css b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-container.component.css new file mode 100644 index 000000000000..9786fa434dff --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-container.component.css @@ -0,0 +1,56 @@ +:host { + position: relative; +} +.l-pinpoint-user-wrapper { + color: #333; + border: 1px solid #e5e8f0; + height: 100%; + display: grid; + position: relative; + font-size: 13px; + font-family: 'Open Sans', sans-serif; + font-weight: 600; + grid-template-columns: auto; + grid-template-rows: 48px 53px 416px; +} +.l-pinpoint-user-title { + display: flex; + padding: 0px 15px; + align-items: center; + justify-content: space-between; + background-color: #f6f8fb; + border-bottom: 1px solid #e5e8f0; +} +.l-pinpoint-user-search { + padding: 10px 15px; + border-bottom: 1px solid #e5e8f0; +} +.l-pinpoint-user-list { + overflow-y: auto; +} +.l-message { + width: 100%; + height: 100%; + z-index: 15; + display: flex; + position: absolute; + align-items: center; + justify-content: center; + background-color: rgba(226, 226, 226, 0.8); +} +.l-message span { + color: #ff8c00; + text-align: center; +} +.l-message button { + top: 0px; + right: 0px; + position: absolute; +} +.l-search-input { + width: 100%; + color: #b3b3b4; + border: 1px solid #469ae4; + padding: 6px 11px; + font-size: 13px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-container.component.html b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-container.component.html new file mode 100644 index 000000000000..2593403a37fc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-container.component.html @@ -0,0 +1,41 @@ +
+
+ Pinpoint User ({{pinpointUserList.length}}) + +
+ +
+ + +
+
+ + {{message}} +
+ + + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-container.component.ts new file mode 100644 index 000000000000..34ef43e8bfe9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-container.component.ts @@ -0,0 +1,261 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject, combineLatest } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { TranslateReplaceService, WebAppSettingDataService } from 'app/shared/services'; +import { GroupMemberInteractionService } from 'app/core/components/group-member/group-member-interaction.service'; +import { UserGroupInteractionService } from 'app/core/components/user-group/user-group-interaction.service'; +import { PinpointUserInteractionService } from './pinpoint-user-interaction.service'; +import { PinpointUser } from './pinpoint-user-create-and-update.component'; +import { PinpointUserDataService, IPinpointUser, IPinpointUserResponse } from './pinpoint-user-data.service'; + +@Component({ + selector: 'pp-pinpoint-user-container', + templateUrl: './pinpoint-user-container.component.html', + styleUrls: ['./pinpoint-user-container.component.css'] +}) +export class PinpointUserContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private searchQuery = ''; + i18nLabel = { + USER_ID_LABEL: '', + NAME_LABEL: '', + DEPARTMENT_LABEL: '', + PHONE_LABEL: '', + EMAIL_LABEL: '', + }; + i18nGuide = { + USER_ID_MIN_LENGTH: '', + NAME_MIN_LENGTH: '', + USER_ID_REQUIRED: '', + NAME_REQUIRED: '', + PHONE_REQUIRED: '', + EMAIL_REQUIRED: '', + }; + i18nText = { + SEARCH_INPUT_GUIDE: '' + }; + minLength = { + userId: 3, + name: 3, + search: 2 + }; + allowedUserEdit = false; + searchUseEnter = false; + pinpointUserList: IPinpointUser[] = []; + filteredPinpointUserList: IPinpointUser[] = []; + groupMemberList: string[] = []; + editPinpointUserIndex: number; + editPinpointUser: PinpointUser; + isUserGroupSelected = false; + useDisable = true; + showLoading = true; + showCreate = false; + message = ''; + + displayPinpointUserList: IPinpointUser[] = []; + defaultScrollSize = 100; + currentSize: number; + constructor( + private webAppSettingDataService: WebAppSettingDataService, + private pinpointUserDataService: PinpointUserDataService, + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + private userGroupInteractionService: UserGroupInteractionService, + private groupMemberInteractionService: GroupMemberInteractionService, + private pinpointUserInteractionService: PinpointUserInteractionService + ) {} + ngOnInit() { + this.webAppSettingDataService.useUserEdit().subscribe((allowedUserEdit: boolean) => { + this.allowedUserEdit = allowedUserEdit; + }); + this.userGroupInteractionService.onSelect$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((userGroupId: string) => { + this.isUserGroupSelected = userGroupId === '' ? false : true; + }); + this.groupMemberInteractionService.onChangeGroupMember$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((memberList: string[]) => { + this.groupMemberList = memberList; + this.hideProcessing(); + }); + this.getI18NText(); + this.getPinpointUserList(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private getI18NText(): void { + combineLatest( + this.translateService.get('COMMON.MIN_LENGTH'), + this.translateService.get('COMMON.REQUIRED'), + this.translateService.get('CONFIGURATION.COMMON.USER_ID'), + this.translateService.get('CONFIGURATION.COMMON.NAME'), + this.translateService.get('CONFIGURATION.COMMON.DEPARTMENT'), + this.translateService.get('CONFIGURATION.COMMON.PHONE'), + this.translateService.get('CONFIGURATION.COMMON.EMAIL'), + this.translateService.get('CONFIGURATION.USER_GROUP.USER_GROUP_REQUIRED') + ).subscribe((i18n: string[]) => { + this.i18nGuide.USER_ID_MIN_LENGTH = this.translateReplaceService.replace(i18n[0], this.minLength.userId); + this.i18nGuide.NAME_MIN_LENGTH = this.translateReplaceService.replace(i18n[0], this.minLength.name); + this.i18nGuide.USER_ID_REQUIRED = this.translateReplaceService.replace(i18n[1], i18n[2]); + this.i18nGuide.NAME_REQUIRED = this.translateReplaceService.replace(i18n[1], i18n[3]); + this.i18nGuide.PHONE_REQUIRED = this.translateReplaceService.replace(i18n[1], i18n[5]); + this.i18nGuide.EMAIL_REQUIRED = this.translateReplaceService.replace(i18n[1], i18n[6]); + + this.i18nText.SEARCH_INPUT_GUIDE = this.translateReplaceService.replace(i18n[0], this.minLength.search); + + this.i18nLabel.USER_ID_LABEL = i18n[2]; + this.i18nLabel.NAME_LABEL = i18n[3]; + this.i18nLabel.DEPARTMENT_LABEL = i18n[4]; + this.i18nLabel.PHONE_LABEL = i18n[5]; + this.i18nLabel.EMAIL_LABEL = i18n[6]; + }); + } + private getPinpointUserList(): void { + this.showProcessing(); + this.webAppSettingDataService.getUserDepartment().subscribe((department: string) => { + this.pinpointUserDataService.retrieve(department).subscribe((pinpointUserData: IPinpointUser[]) => { + this.pinpointUserList = pinpointUserData; + this.filteringPinpointUserList(); + this.hideProcessing(); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + }); + } + private filteringPinpointUserList(): void { + if (this.searchQuery === '') { + this.filteredPinpointUserList = this.pinpointUserList; + } else { + this.filteredPinpointUserList = this.pinpointUserList.filter((pinpointUser: IPinpointUser): boolean => { + return pinpointUser.name.indexOf(this.searchQuery) === -1 ? false : true; + }); + } + this.addListItem(true); + } + isEnable(): boolean { + return false; + } + isChecked(userId: string): boolean { + return this.groupMemberList.indexOf(userId) !== -1; + } + hasMessage(): boolean { + return this.message !== ''; + } + onAddUser(pinpointUserId: string): void { + this.showProcessing(); + this.pinpointUserInteractionService.setAddPinpointUser(pinpointUserId); + } + onCloseMessage(): void { + this.message = ''; + } + onSearch(query: string): void { + this.searchQuery = query; + this.filteringPinpointUserList(); + } + onCloseCreateUserPopup(): void { + this.showCreate = false; + } + onShowCreateUserPopup(): void { + this.showCreate = true; + } + onCreatePinpointUser(pinpointUser: PinpointUser): void { + this.pinpointUserDataService.create({ + userId: pinpointUser.userId, + name: pinpointUser.name, + phoneNumber: pinpointUser.phoneNumber, + email: pinpointUser.email, + department: pinpointUser.department + } as IPinpointUser).subscribe((response: IPinpointUserResponse) => { + this.getPinpointUserList(); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + } + onUpdatePinpointUser(pinpointUser: PinpointUser): void { + const editPinpointUser = this.pinpointUserList[this.editPinpointUserIndex]; + this.pinpointUserDataService.update({ + userId: pinpointUser.userId, + name: pinpointUser.name, + phoneNumber: pinpointUser.phoneNumber, + email: pinpointUser.email, + department: pinpointUser.department, + number: editPinpointUser.number + } as IPinpointUser).subscribe((response: IPinpointUserResponse) => { + this.getPinpointUserList(); + this.pinpointUserInteractionService.setUserUpdated({ + userId: pinpointUser.userId, + department: pinpointUser.department, + name: pinpointUser.name + }); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + } + onRemovePinpointUser(userId: string): void { + this.showProcessing(); + this.pinpointUserDataService.remove(userId).subscribe((response: IPinpointUserResponse) => { + this.pinpointUserList.splice(this.getPinpointUserIndexByUserId(userId), 1); + this.hideProcessing(); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + } + onEditPinpointUser(userId: string): void { + this.editPinpointUserIndex = this.getPinpointUserIndexByUserId(userId); + const editPinpointUser = this.pinpointUserList[this.editPinpointUserIndex]; + this.editPinpointUser = new PinpointUser( + editPinpointUser.userId, + editPinpointUser.name, + editPinpointUser.phoneNumber, + editPinpointUser.email, + editPinpointUser.department + ); + this.onShowCreateUserPopup(); + } + private getPinpointUserIndexByUserId(userId: string): number { + let index = -1; + for (let i = 0 ; i < this.pinpointUserList.length ; i++) { + if (this.pinpointUserList[i].userId === userId) { + index = i; + break; + } + } + return index; + } + private showProcessing(): void { + this.useDisable = true; + this.showLoading = true; + } + private hideProcessing(): void { + this.useDisable = false; + this.showLoading = false; + } + onScroll($event: MouseEvent): void { + this.addListItem(); + } + private addListItem(reset: boolean = false): void { + if (reset) { + this.displayPinpointUserList.length = 0; + this.addScrollItem(0, Math.min(this.defaultScrollSize, this.filteredPinpointUserList.length)); + } else { + if (this.currentSize < this.filteredPinpointUserList.length) { + this.addScrollItem(this.currentSize, Math.min(this.currentSize + this.defaultScrollSize, this.filteredPinpointUserList.length)); + } + } + } + private addScrollItem(start: number, end: number): void { + for (let i = start ; i < end ; i++) { + this.displayPinpointUserList.push(this.filteredPinpointUserList[i]); + } + this.currentSize = end; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-create-and-update.component.css b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-create-and-update.component.css new file mode 100644 index 000000000000..78db2a42dd5f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-create-and-update.component.css @@ -0,0 +1,50 @@ +.l-wrapper { + top: 0px; + left: 0px; + width: 100%; + height: 100%; + z-index: 15; + display: flex; + padding: 0px 20px; + position: absolute; + align-items: center; + flex-direction: column; + justify-content: center; + background-color: rgba(226, 226, 226, 0.9); +} +.l-wrapper h1 { + margin-bottom: 6px; +} +.l-wrapper input { + width: 100%; + border: 1px solid #469ae4; + padding: 6px 11px; + font-size: 13px; + margin-bottom: 2px; + background-color: #FFF; +} +.l-wrapper .l-create { + width: 100%; + margin-top: 6px; +} +.l-wrapper .l-close { + top: 0px; + right: 0px; + position: absolute; +} +.l-wrapper .l-alert { + color: #FFF; + padding: 4px; + margin-bottom: 4px; + background-color: #000; +} +.ng-valid[required], .ng-valid.required { + border-left: 5px solid #42A948 !important; +} + +.ng-invalid:not(form) { + border-left: 5px solid #a94442 !important; +} +input[disabled] { + border: 1px solid #DDD !important; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-create-and-update.component.html b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-create-and-update.component.html new file mode 100644 index 000000000000..0cdcf8e0d5b0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-create-and-update.component.html @@ -0,0 +1,41 @@ +
+ +

{{title}}

+
+
+ + +
+
{{i18nGuide.USER_ID_REQUIRED}}
+
{{i18nGuide.USER_ID_MIN_LENGTH}}
+
+
+
+ + +
+
{{i18nGuide.NAME_REQUIRED}}
+
{{i18nGuide.NAME_MIN_LENGTH}}
+
+
+
+ + +
+
+ + +
+
{{i18nGuide.PHONE_REQUIRED}}
+
+
+
+ + +
+
{{i18nGuide.EMAIL_REQUIRED}}
+
+
+ +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-create-and-update.component.ts b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-create-and-update.component.ts new file mode 100644 index 000000000000..02d3dc1bd53a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-create-and-update.component.ts @@ -0,0 +1,101 @@ +import { Component, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter, AfterViewChecked, ViewChild, ElementRef } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; + +export class PinpointUser { + constructor( + public userId: string, + public name: string, + public phoneNumber: string, + public email: string, + public department?: string + ) {} +} + +@Component({ + selector: 'pp-pinpoint-user-create-and-update', + templateUrl: './pinpoint-user-create-and-update.component.html', + styleUrls: ['./pinpoint-user-create-and-update.component.css'] +}) +export class PinpointUserCreateAndUpdateComponent implements OnInit, OnChanges, AfterViewChecked { + @ViewChild('newUserGroupName') userGroupInput: ElementRef; + @Input() showCreate = false; + @Input() i18nLabel: any; + @Input() i18nGuide: any; + @Input() minLength: any; + @Input() editPinpointUser: PinpointUser = null; + @Output() outUpdatePinpointUser: EventEmitter = new EventEmitter(); + @Output() outCreatePinpointUser: EventEmitter = new EventEmitter(); + @Output() outClose: EventEmitter = new EventEmitter(); + newUserModel = new PinpointUser('', '', '', '', ''); + pinpointUserForm: FormGroup; + title = 'Pinpoint User'; + constructor() {} + ngOnInit() { + this.pinpointUserForm = new FormGroup({ + 'userId': new FormControl(this.newUserModel.userId, [ + Validators.required, + Validators.minLength(this.minLength.userId) + ]), + 'name': new FormControl(this.newUserModel.name, [ + Validators.required, + Validators.minLength(this.minLength.name) + ]), + 'phoneNumber': new FormControl(this.newUserModel.phoneNumber, [ + Validators.required, + Validators.pattern(/\d*/) + ]), + 'email': new FormControl(this.newUserModel.email, [ + Validators.required, + Validators.email + ]), + 'department': new FormControl(this.newUserModel.department, []) + }); + } + ngOnChanges(changes: SimpleChanges) { + if (changes['editPinpointUser'] && changes['editPinpointUser'].currentValue) { + this.pinpointUserForm.get('userId').setValue(this.editPinpointUser.userId); + this.pinpointUserForm.get('name').setValue(this.editPinpointUser.name); + this.pinpointUserForm.get('phoneNumber').setValue(this.editPinpointUser.phoneNumber); + this.pinpointUserForm.get('email').setValue(this.editPinpointUser.email); + this.pinpointUserForm.get('department').setValue(this.editPinpointUser.department); + this.pinpointUserForm.get('userId').disable(); + } + } + ngAfterViewChecked() {} + onCreateOrUpdate(): void { + const pinpointUser = new PinpointUser( + this.pinpointUserForm.get('userId').value, + this.pinpointUserForm.get('name').value, + this.pinpointUserForm.get('phoneNumber').value, + this.pinpointUserForm.get('email').value.toString(), + this.pinpointUserForm.get('department').value + ); + if (this.editPinpointUser) { + this.outUpdatePinpointUser.emit(pinpointUser); + } else { + this.outCreatePinpointUser.emit(pinpointUser); + } + this.onClose(); + } + onClose(): void { + this.editPinpointUser = null; + this.outClose.emit(); + this.pinpointUserForm.reset(); + this.pinpointUserForm.get('userId').enable(); + } + get userId() { + return this.pinpointUserForm.get('userId'); + } + get name() { + return this.pinpointUserForm.get('name'); + } + get department() { + return this.pinpointUserForm.get('department'); + } + get phoneNumber() { + return this.pinpointUserForm.get('phoneNumber'); + } + get email() { + return this.pinpointUserForm.get('email'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-data.service.ts new file mode 100644 index 000000000000..46a45a11e1cd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-data.service.ts @@ -0,0 +1,67 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, tap } from 'rxjs/operators'; + +export interface IPinpointUser { + department?: string; + email: string; + name: string; + number: string; + phoneNumber: string; + userId: string; +} +export interface IPinpointUserResponse { + result: string; +} + +@Injectable() +export class PinpointUserDataService { + url = 'user.pinpoint'; + constructor(private http: HttpClient) { } + retrieve(department?: string): Observable { + return this.http.get(this.url, this.makeRequestOptionsArgs(department)).pipe( + tap((data) => { + if (data['errorCode']) { + throw data['errorMessage']; + } + }), + catchError(this.handleError) + ); + } + create(params: IPinpointUser): Observable { + return this.http.post(this.url, params).pipe( + tap(this.checkError), + catchError(this.handleError) + ); + } + update(params: IPinpointUser): Observable { + return this.http.put(this.url, params).pipe( + tap(this.checkError), + catchError(this.handleError) + ); + } + remove(userId: string): Observable { + return this.http.request('delete', this.url, { + body: { userId } + }).pipe( + tap(this.checkError), + catchError(this.handleError) + ); + } + private checkError(data: any) { + if (data['errorCode']) { + throw data['errorMessage']; + } else if (data['result'] !== 'SUCCESS') { + throw data; + } + } + private handleError(error: HttpErrorResponse | string) { + return throwError(error['statusText'] || error); + } + private makeRequestOptionsArgs(department?: string): object { + return { + params: department ? { searchKey: department } : {} + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-interaction.service.ts new file mode 100644 index 000000000000..72f7ee0afe2f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user-interaction.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; + +@Injectable() +export class PinpointUserInteractionService { + private outAdd = new Subject(); + private outUpdate = new Subject(); + private outRemove = new Subject(); + + onAdd$: Observable; + onRemove$: Observable; + onUpdate$: Observable; + + constructor() { + this.onAdd$ = this.outAdd.asObservable(); + this.onRemove$ = this.outRemove.asObservable(); + this.onUpdate$ = this.outUpdate.asObservable(); + } + setAddPinpointUser(memberId: string): void { + this.outAdd.next(memberId); + } + setRemovePinpointUser(memberId: string): void { + this.outRemove.next(memberId); + } + setUserUpdated(memberInfo: any): void { + this.outUpdate.next(memberInfo); + } +} + diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user.component.css b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user.component.css new file mode 100644 index 000000000000..1ada1e8c3f54 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user.component.css @@ -0,0 +1,33 @@ +.l-item-wrapper { + height: 28px; + margin: 0; + color: #333; + padding: 6px 15px 0px 15px; + font-size: 13px; + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.l-item-wrapper:hover { + background:#fff0f0; +} +.l-item-wrapper.selected { + background:#fff0f0; +} + +.l-item-wrapper > div { + float: right; +} +.l-item-wrapper .fa-trash-alt { + color: #b3b6bf; + font-size: 14px; + margin-left: 4px; +} +.l-item-wrapper .fa-check { + color: #F00; + margin-left: 10px; +} +.l-disabled { + color:#DDD; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user.component.html b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user.component.html new file mode 100644 index 000000000000..bb6f0ab7932f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user.component.html @@ -0,0 +1,13 @@ +
+
+ + + + +
+ ({{pinpointUser.department}}) {{pinpointUser.name}} +
diff --git a/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user.component.ts b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user.component.ts new file mode 100644 index 000000000000..1f9e24d45e61 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/pinpoint-user/pinpoint-user.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { IPinpointUser } from './pinpoint-user-data.service'; + +@Component({ + selector: 'pp-pinpoint-user', + templateUrl: './pinpoint-user.component.html', + styleUrls: ['./pinpoint-user.component.css'] +}) +export class PinpointUserComponent implements OnInit { + @Input() pinpointUser: IPinpointUser; + @Input() allowedUserEdit: boolean; + @Input() isChecked = false; + @Input() isEnabled = false; + @Output() outRemove: EventEmitter = new EventEmitter(); + @Output() outAddUser: EventEmitter = new EventEmitter(); + @Output() outEditUser: EventEmitter = new EventEmitter(); + private removeConformId = ''; + private selectedPinpointUser: string; + + constructor() {} + ngOnInit() {} + onRemove(): void { + this.removeConformId = this.pinpointUser.userId; + } + onEdit(): void { + this.outEditUser.emit(this.pinpointUser.userId); + } + onAddUser(): void { + if (this.isEnableUser()) { + this.outAddUser.emit(this.pinpointUser.userId); + } + } + onCancelRemove(): void { + this.removeConformId = ''; + } + onConfirmRemove(): void { + this.outRemove.emit(this.removeConformId); + this.removeConformId = ''; + } + isRemoveTarget(): boolean { + return this.removeConformId === this.pinpointUser.userId; + } + isSelected(): boolean { + return this.selectedPinpointUser === this.pinpointUser.userId; + } + isEnableUser(): boolean { + return this.isEnabled && !this.isChecked; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/index.ts b/web/src/main/webapp/v2/src/app/core/components/real-time/index.ts new file mode 100644 index 000000000000..2148706cee40 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/index.ts @@ -0,0 +1,38 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { RealTimeChartComponent } from './real-time-chart.component'; +import { RealTimeContainerComponent } from './real-time-container.component'; +import { RealTimePagingContainerComponent } from './real-time-paging-container.component'; +import { RealTimeTotalChartComponent } from './real-time-total-chart.component'; +import { RealTimeAgentChartComponent } from './real-time-agent-chart.component'; +import { RealTimeWebSocketService } from './real-time-websocket.service'; +import { ResizeTopDirective } from './resize-top.directive'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + ResizeTopDirective, + RealTimeChartComponent, + RealTimeAgentChartComponent, + RealTimeTotalChartComponent, + RealTimeContainerComponent, + RealTimePagingContainerComponent + ], + imports: [ + SharedModule, + HelpViewerPopupModule + ], + exports: [ + RealTimeContainerComponent, + RealTimePagingContainerComponent + ], + entryComponents: [ + RealTimeTotalChartComponent, + RealTimeAgentChartComponent + ], + providers: [ + RealTimeWebSocketService + ] +}) +export class RealTimeModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-agent-chart.component.css b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-agent-chart.component.css new file mode 100644 index 000000000000..4f523908b2e0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-agent-chart.component.css @@ -0,0 +1,55 @@ +:host { + display: block; +} +.l-chart-item { + width: 152px; + height: 84px; + margin: 5px; +} +.l-chart-item dt { + color: #fff; + padding: 10px 20px 10px 2px; + display: block; + overflow: hidden; + font-size: 11px; + text-align: center; + line-height: 1em; + white-space: nowrap; + text-overflow: ellipsis; + border-bottom: 1px solid #687b8e; + background-color: #74879a; +} +i { + float: right; + cursor: pointer; + margin-right: -16px; +} +.l-chart-item dd { + display: flex; + flex-flow: row wrap; + background: #fff; + justify-content: space-around; + align-items: center; +} +.l-chart-section { + background: #fff; + border-top: 1px solid #e5e8f0; + height: 54px; + width: 100%; + position: relative; +} +.l-error-template { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + justify-content: center; + align-items: center; + z-index: 2; +} +.l-error-text { + font-size: 14px; + font-weight: 600; + color: #c04e3f; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-agent-chart.component.html b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-agent-chart.component.html new file mode 100644 index 000000000000..d7b32273a8bc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-agent-chart.component.html @@ -0,0 +1,17 @@ +
+
+
{{agentName}}
+
+
+ + +
+

{{errorMessage}}

+
+
+
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-agent-chart.component.ts b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-agent-chart.component.ts new file mode 100644 index 000000000000..568d129f62f2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-agent-chart.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { IRealTimeChartData } from './real-time-chart.component'; + +@Component({ + selector: 'pp-real-time-agent-chart', + templateUrl: './real-time-agent-chart.component.html', + styleUrls: ['./real-time-agent-chart.component.css'] +}) +export class RealTimeAgentChartComponent implements OnInit { + @Input() agentName: string; + @Input() hasError: boolean; + @Input() errorMessage: string; + @Input() chartData: IRealTimeChartData; + @Output() outOpenThreadDump: EventEmitter = new EventEmitter(); + + showAxis = false; + + constructor() {} + ngOnInit() {} + onOpen(): void { + this.outOpenThreadDump.emit(this.agentName); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-chart.component.css b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-chart.component.css new file mode 100644 index 000000000000..31b579f87ed9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-chart.component.css @@ -0,0 +1,52 @@ +:host { + display: block; + width: 100%; + height: 100%; + position: relative; +} + +.l-tooltip { + background-color: rgba(0, 0, 0, 0.8); + color: #fff; + border: 0px solid rgba(0, 0, 0, 0); + border-radius: 5px; + padding: 5px 7px; + position: absolute; + top: 110%; +} + +.l-tooltip-caret { + width: 0; + height: 0; + position: absolute; + top: calc(110% - 7px); + border-bottom: 7px solid rgba(0, 0, 0, 0.8); + border-left: 7px solid transparent; + border-right: 7px solid transparent; +} + +.l-tooltip-title { + font-size: 12px; + font-weight: 600; + margin-bottom: 2px; +} + +.l-tooltip-body { + font-size: 12px; +} + +.l-tooltip-body > dl { + display: flex; +} + +.l-tooltip-body dt { + margin-right: 5px; +} + +.l-tooltip-label { + display: inline-block; + width: 10px; + height: 10px; + margin-right: 2px; + box-shadow: inset 0px 0px 0px 1px rgba(255, 255, 255, 0.4); +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-chart.component.html b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-chart.component.html new file mode 100644 index 000000000000..5f556d032afd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-chart.component.html @@ -0,0 +1,13 @@ + + +
+
{{tooltipDataObj.title}}
+
+
+
{{chartLabels[i]}}:
+
{{value}}
+
+
+
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-chart.component.ts b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-chart.component.ts new file mode 100644 index 000000000000..57758147a3b7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-chart.component.ts @@ -0,0 +1,204 @@ +import { Component, OnInit, ViewChild, ElementRef, Input, OnChanges, SimpleChanges, Renderer2 } from '@angular/core'; +import { Chart, ChartPoint, ChartDataSets } from 'chart.js'; +import 'chartjs-plugin-streaming'; +import * as moment from 'moment-timezone'; + +export interface IRealTimeChartData { + timeStamp: number; + responseCount: number[]; +} + +@Component({ + selector: 'pp-real-time-chart', + templateUrl: './real-time-chart.component.html', + styleUrls: ['./real-time-chart.component.css'] +}) +export class RealTimeChartComponent implements OnInit, OnChanges { + @ViewChild('real') chartElement: ElementRef; + @ViewChild('tooltip') tooltip: ElementRef; + @ViewChild('tooltipCaret') tooltipCaret: ElementRef; + @Input() showAxis: boolean; + @Input() namespace: string; + @Input() timezone: string; + @Input() dateFormat: string; + @Input() chartData: IRealTimeChartData; + + private chartObj: Chart; + private defaultYMax = 5; + + chartColors = ['#33b692', '#51afdf', '#fea63e', '#e76f4b']; + chartLabels = ['1s', '3s', '5s', 'Slow']; + showTooltip: boolean; + tooltipDataObj = { + title: '', + values: [] as number[], + }; + // private yMaxHolder: number[] = []; + + constructor( + private renderer: Renderer2, + private el: ElementRef + ) {} + ngOnChanges(changes: SimpleChanges) { + Object.keys(changes) + .filter(() => this.chartObj !== undefined) + .filter((propName: string) => { + return changes[propName].currentValue; + }) + .forEach((propName: string) => { + const changedProp = changes[propName]; + + switch (propName) { + case 'chartData': + this.updateChartData(changedProp.currentValue); + break; + } + + this.chartObj.update({ + duration: 0 + }); + }); + } + ngOnInit() { + this.chartObj = new Chart(this.chartElement.nativeElement.getContext('2d'), { + type: 'line', + data: { + labels: [], + datasets: this.chartColors.map((color: string, i: number) => { + return { + label: this.chartLabels[i], + backgroundColor: color, + borderColor: color, + borderWidth: 0.5, + fill: true, + pointRadius: 0, + pointHoverRadius: 3, + data: [] + }; + }) + }, + options: { + animation: { + duration: 0 + }, + elements: { + line: { + tension: 0 + } + }, + responsive: true, + maintainAspectRatio: false, + legend: { + display: false + }, + events: this.showAxis ? ['mousemove', 'mouseout', 'click'] : [], + hover: { + animationDuration: 0, + mode: 'index', + intersect: false, + onHover: (event: MouseEvent, elements: {[key: string]: any}[]): void => { + if (event.type !== 'mouseout' && event.offsetX >= 25 && event.offsetX <= 167) { + this.showTooltip = true; + this.setTooltipData(elements); + this.setTooltipPosition(event); + } else { + // * event.type === 'mouseout' + this.showTooltip = false; + } + } + }, + responsiveAnimationDuration: 0, + tooltips: { + enabled: false, + }, + scales: { + xAxes: [{ + type: 'realtime', + gridLines: { + display: !this.showAxis, + drawBorder: false, + tickMarkLength: 0 + }, + ticks: { + display: false + } + }], + yAxes: [{ + gridLines: { + display: this.showAxis, + drawBorder: false, + tickMarkLength: 0 + }, + ticks: { + display: this.showAxis, + beginAtZero: true, + min: 0, + max: this.defaultYMax, + padding: 5 + }, + + }] + }, + plugins: { + streaming: { + duration: 12000, // data in the past 12000 ms will be displayed + // refresh: 1000, // onRefresh callback will be called every 1000 ms + delay: 2000, // delay of 2000 ms, so upcoming values are known before plotting a line + frameRate: 30, // chart is drawn 30 times every second + // pause: false, + } + } + } + }); + } + + private setTooltipData(elements: {[key: string]: any}[]): void { + const datasets = this.chartObj.config.data.datasets; + + this.tooltipDataObj = { + title: moment((datasets[0].data[elements[0]._index] as ChartPoint).x).tz(this.timezone).format(this.dateFormat), + values: elements.map((element: {[key: string]: any}, i: number) => (datasets[i].data[element._index] as ChartPoint).y as number) + }; + } + + private setTooltipPosition(event: MouseEvent): void { + if (this.tooltip) { + const tooltipCaret = this.tooltipCaret.nativeElement; + const tooltip = this.tooltip.nativeElement; + const ratio = event.offsetX / this.el.nativeElement.offsetWidth; + + this.renderer.setStyle(tooltipCaret, 'left', event.offsetX - (tooltipCaret.offsetWidth / 2) + 'px'); + this.renderer.setStyle(tooltip, 'left', event.offsetX - (tooltip.offsetWidth * ratio) + 'px'); + } + } + + private updateChartData({timeStamp, responseCount}: {timeStamp: number, responseCount: number[]}): void { + if (responseCount.length === 0) { + this.chartObj.config.options.plugins.streaming.pause = true; + } else { + this.chartObj.config.data.datasets.forEach(function(dataset, i) { + (dataset.data as ChartPoint[]).push({ + x: timeStamp, + y: responseCount[i] + }); + }); + /** + * 1. 데이터 들어올때 마다 최댓값을 yMaxHolder에 넣음. + * 2. 11초(duration time이랑 비슷한 시간) 지날때마다 맨앞의 element 제거. + * 3. yMaxHolder의 최댓값을 yMax로 세팅. + */ + // this.yMaxHolder.push(Math.max(...responseCount)); + // setTimeout(() => { + // this.yMaxHolder.shift(); + // }, 11000); + // this.chartObj.config.options.scales.yAxes[0].ticks.max = Math.max(this.defaultYMax, Math.round(Math.max(...this.yMaxHolder) * 2)); + this.chartObj.config.options.scales.yAxes[0].ticks.max = + Math.max(...this.chartObj.data.datasets.map(function(dataset: ChartDataSets) { + return Math.max(...(dataset.data as ChartPoint[]).map(function(value) { + return value.y as number; + })); + })) >= this.defaultYMax ? undefined : this.defaultYMax; + this.chartObj.config.options.plugins.streaming.pause = false; + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-container.component.css b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-container.component.css new file mode 100644 index 000000000000..331d3b0ea8d3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-container.component.css @@ -0,0 +1,118 @@ +:host { + z-index: 30; +} +.l-thread-chart-wrap { + background: #d3dbe6; + border-top: 1px solid #ccd5e0; + position: absolute; + bottom: 0; + left: 0; + width: 100%; +} +.l-title-group { + display: flex; + flex-flow: row wrap; + color: #566370; + font-size: 14px; + font-weight: 600; + height: 34px; + padding: 0 25px; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #bdc7d5; + cursor: ns-resize; + background: #f6f8fb; +} +.l-title-group .fas { + font-size: 18px; + color: #a8acb5; + cursor: pointer; +} +.l-title-group .l-title .fas { + margin: 0 9px 0 0; + +} +.l-title-group .l-tool-box { + color: #8F9CAF; +} +.l-tool-box button { + margin-left: 10px; +} +.l-pin-up { + color: #F00; +} +.l-chart-group-wrap { + display: flex; + flex-flow: row wrap; + padding: 20px; +} +.l-chart-group-wrap > .l-chart-item { + width: calc(50% - 20px); + width: 277px; + height: 178px; + margin: 5px 10px 5px 5px; +} +.l-chart-group-wrap .l-chart-group-list { + display: flex; + flex-flow: row wrap; + flex: 1; + overflow-y: auto; + height: 100%; +} +.l-message { + top: 0px; + left: 0px; + width: 100%; + height: 100%; + position: absolute; + text-align: center; + background-color: rgba(226, 226, 226, 0.5); + z-index: 9999; +} +.l-message h4 { + padding: 80px 0 10px 0; + font-weight: 100; +} +.l-message h4 span { + padding: 8px 40px 6px 40px; + background-color: #FFF; +} +.l-retry button span { + margin-right: 6px; +} +.l-message.l-no-data h4 span { + background-color: #000; + color: #FFF; +} +.l-paging { + width: 100%; + background-color: #74879a; + color: #FFF; + font-size: 15px; + margin: 5px 5px 4px 5px; + padding: 11px; +} +.l-paging .l-txt { + font-weight: lighter; +} +.l-paging .l-txt-bold { + font-weight: bolder; + font-size: 18px; +} +.l-paging .l-page { + float: right; + cursor: pointer; + padding: 0px 6px; +} +.l-paging .l-page:hover { + color: #4b99e3; +} +.l-paging .l-page:last-child { + color: #000; + cursor: initial; + text-decoration: initial; + background-color: #FFF; +} +.fa-chart-area { + margin-right: 2px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-container.component.html b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-container.component.html new file mode 100644 index 000000000000..2b3afd680206 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-container.component.html @@ -0,0 +1,40 @@ +
+
+
+ Realtime Active Thread Chart +
+
+ + +
+
+
+
+ +
+
+
+ Total Servers : + {{totalCount}} + [ 1 ~ {{pagingSize}} ] + {{page}} + 1 +
+ +
+ +
+

Waiting Connection...

+ +
+
+

Closed connection

+ +
+
+

This node is not WAS

+
+ +
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-container.component.ts new file mode 100644 index 000000000000..11a3a4401a6b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-container.component.ts @@ -0,0 +1,329 @@ +import { Component, OnInit, OnDestroy, ViewChild, ViewContainerRef, ComponentFactoryResolver } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { + StoreHelperService, + WebAppSettingDataService, + NewUrlStateNotificationService, + UrlRouteManagerService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { RealTimeWebSocketService, IWebSocketResponse, IWebSocketDataResult, ResponseCode, IActiveThreadCounts } from './real-time-websocket.service'; +import { RealTimeTotalChartComponent } from './real-time-total-chart.component'; +import { RealTimeAgentChartComponent } from './real-time-agent-chart.component'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +// TODO: 나중에 공통으로 추출. +const enum MessageTemplate { + LOADING = 'LOADING', + RETRY = 'RETRY', + NO_DATA = 'NO_DATA', + NOTHING = 'NOTHING' +} + +@Component({ + selector: 'pp-real-time-container', + templateUrl: './real-time-container.component.html', + styleUrls: ['./real-time-container.component.css'] +}) +export class RealTimeContainerComponent implements OnInit, OnDestroy { + @ViewChild('totalChartPlaceHolder', { read: ViewContainerRef} ) totalChartViewContainerRef: ViewContainerRef; + @ViewChild('agentChartPlaceHolder', { read: ViewContainerRef} ) agentChartViewContainerRef: ViewContainerRef; + + private unsubscribe = new Subject(); + private applicationName = ''; + private serviceType = ''; + private totalComponentRef: any = null; + private componentRefMap: any = {}; + totalCount = 0; + pinUp = true; + lastHeight: number; + minHeight = 343; + maxHeightPadding = 50; // Header Height + timezone: string; + dateFormat: string; + hiddenComponent = true; + messageTemplate = MessageTemplate.LOADING; + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private realTimeWebSocketService: RealTimeWebSocketService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.lastHeight = this.webAppSettingDataService.getLayerHeight() || this.minHeight; + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter(() => { + return this.newUrlStateNotificationService.hasValue(UrlPathId.APPLICATION); + }) + ).subscribe(() => { + this.hiddenComponent = true; + this.resetState(); + this.resetAgentComponentRef(); + }); + this.connectStore(); + + this.realTimeWebSocketService.onMessage$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((response: IWebSocketResponse) => { + switch (response.type) { + case 'open': + this.onOpen(); + break; + case 'close': + this.onClose(); + break; + case 'retry': + this.onRetry(); + break; + case 'message': + this.onMessage(response.message as IWebSocketDataResult); + break; + } + }); + } + ngOnDestroy() { + this.realTimeWebSocketService.close(); + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => { + this.timezone = timezone; + }); + this.storeHelperService.getDateFormat(this.unsubscribe, 0).subscribe((dateFormat: string) => { + this.dateFormat = dateFormat; + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isMerged === true || target.isMerged === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.webAppSettingDataService.useActiveThreadChart().subscribe((use: boolean) => { + this.hiddenComponent = false; + const application = target.node[0].split('^'); + if (use === false) { + // this.resetState(); + // this.resetAgentComponentRef(); + this.hide(); + return; + } + if (this.pinUp === true) { + if (this.applicationName !== '') { + return; + } + } + if (target.isWAS) { + if (this.isSameWithCurrentTarget(application[0], application[1]) === false) { + // this.resetState(); + // this.resetAgentComponentRef(); + // this.hide(); + // } else { + this.applicationName = application[0]; + this.serviceType = application[1]; + if (this.realTimeWebSocketService.isOpened()) { + this.resetAgentComponentRef(); + this.startDataRequest(); + } else { + this.realTimeWebSocketService.connect(); + } + } + } else { + this.applicationName = application[0]; + this.serviceType = application[1]; + this.realTimeWebSocketService.close(); + this.stopDataRequest(); + this.resetAgentComponentRef(); + this.hide(); + } + }); + }); + } + private resetAgentComponentRef(): void { + this.totalChartViewContainerRef.clear(); + this.agentChartViewContainerRef.clear(); + this.totalComponentRef = null; + this.componentRefMap = {}; + } + private resetState() { + this.applicationName = ''; + this.serviceType = ''; + this.pinUp = true; + } + private hide() { + this.messageTemplate = MessageTemplate.NO_DATA; + } + private isSameWithCurrentTarget(applicationName: string, serviceType: string): boolean { + return (this.applicationName === applicationName && this.serviceType === serviceType); + } + private startDataRequest(): void { + this.realTimeWebSocketService.send(this.getRequestDataStr(this.applicationName)); + } + private stopDataRequest(): void { + this.realTimeWebSocketService.send(this.getRequestDataStr('')); + } + private getRequestDataStr(name: string): object { + return { + type: 'REQUEST', + command: 'activeThreadCount', + parameters: { + applicationName: name + } + }; + } + private onOpen(): void { + this.startDataRequest(); + } + private onClose(): void { + this.messageTemplate = MessageTemplate.RETRY; + } + private onRetry(): void { + this.retryConnection(); + } + private onMessage(data: IWebSocketDataResult): void { + this.messageTemplate = MessageTemplate.NOTHING; + if (data.applicationName && data.applicationName !== this.applicationName) { + return; + } + this.totalCount = Object.keys(data.activeThreadCounts).length; + this.publishData(data); + } + private setAgentChart({timeStamp, activeThreadCounts}: {timeStamp?: number, activeThreadCounts: { [key: string]: IActiveThreadCounts }}): void { + /** + * 0. MERGE (componentRefMap Key, 넘어온 데이터 Key) as Set + * 1. Set 루핑 + * 2. 넘어온 데이터 object에 대해서 hasOwnProperty(key) + * 2-1. true && componentRefMap에 없음 => init + update + * 2-2. true && componentRefMap에 존재 => update + * 2-3. false => componentRefMap에서 delete, viewContainer에서 지움(.remove(index)) + */ + const mergedKeySet = new Set([...Object.keys(this.componentRefMap), ...Object.keys(activeThreadCounts)]); + + mergedKeySet.forEach((agentName: string) => { + if (activeThreadCounts.hasOwnProperty(agentName)) { + if (typeof this.componentRefMap[agentName] === 'undefined') { + this.initAgentComponent(agentName); + } + const componentInstance = this.componentRefMap[agentName].componentRef.instance; + const isResponseSuccess = activeThreadCounts[agentName].code === ResponseCode.SUCCESS; + + componentInstance.agentName = agentName; + componentInstance.hasError = isResponseSuccess ? false : true; + componentInstance.errorMessage = isResponseSuccess ? '' : activeThreadCounts[agentName].message; + componentInstance.chartData = { + timeStamp, + responseCount: isResponseSuccess ? activeThreadCounts[agentName].status : [] + }; + } else { + this.componentRefMap[agentName].unsubscription.unsubscribe(); + this.agentChartViewContainerRef.remove(this.componentRefMap[agentName].index); + delete this.componentRefMap[agentName]; + } + }); + } + private setTotalChart({timeStamp, applicationName, activeThreadCounts}: {timeStamp?: number, applicationName?: string, activeThreadCounts: { [key: string]: IActiveThreadCounts }}): void { + if (this.totalComponentRef === null) { + this.initTotalComponent(); + } + const componentInstance = this.totalComponentRef.instance; + const successData = this.getSuccessData(activeThreadCounts); + + componentInstance.applicationName = applicationName ? applicationName : this.applicationName; + componentInstance.hasError = successData.length === 0 ? true : false; + componentInstance.errorMessage = successData.length === 0 ? activeThreadCounts[Object.keys(activeThreadCounts)[0]].message : ''; + componentInstance.timezone = this.timezone; + componentInstance.dateFormat = this.dateFormat; + componentInstance.chartData = { + timeStamp, + responseCount: successData.length === 0 ? [] : this.getTotalResponseCount(successData) + }; + } + private getSuccessData(obj: { [key: string]: IActiveThreadCounts }): IActiveThreadCounts[] { + return Object.keys(obj) + .filter((agentName: string) => obj[agentName].code === ResponseCode.SUCCESS) + .map((agentName: string) => obj[agentName]); + } + private getTotalResponseCount(data: IActiveThreadCounts[]): number[] { + return data.reduce((prev: number[], curr: IActiveThreadCounts) => { + return prev.map((a: number, i: number) => a + curr.status[i]); + }, [0, 0, 0, 0]); + } + private publishData(data: IWebSocketDataResult): void { + this.setTotalChart(data); + this.setAgentChart(data); + } + private initTotalComponent(): void { + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(RealTimeTotalChartComponent); + this.totalComponentRef = this.totalChartViewContainerRef.createComponent(componentFactory); + } + private initAgentComponent(namespace: string) { + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(RealTimeAgentChartComponent); + const componentRef = this.agentChartViewContainerRef.createComponent(componentFactory); + const unsubscription = componentRef.instance.outOpenThreadDump.subscribe((agentId: string) => { + this.openThreadDump(agentId); + }); + + this.componentRefMap[namespace] = { + componentRef: componentRef, + index: this.agentChartViewContainerRef.length - 1, + unsubscription: unsubscription + }; + } + needPaging(): boolean { + return this.totalCount > this.realTimeWebSocketService.getPagingSize(); + } + getTotalPage(): number[] { + const totalPage = Math.ceil(this.totalCount / this.realTimeWebSocketService.getPagingSize()); + const pages = []; + for (let i = totalPage ; i > 1 ; i-- ) { + pages.push(i); + } + return pages; + } + retryConnection(): void { + this.messageTemplate = MessageTemplate.LOADING; + this.realTimeWebSocketService.connect(); + } + onPinUp(): void { + this.analyticsService.trackEvent(this.pinUp ? TRACKED_EVENT_LIST.PIN_UP_REAL_TIME_CHART : TRACKED_EVENT_LIST.REMOVE_PIN_ON_REAL_TIME_CHART); + this.pinUp = !this.pinUp; + } + openPage(page: number): void { + this.urlRouteManagerService.openPage([ + UrlPath.REAL_TIME, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + '' + page + ]); + } + openThreadDump(agentId: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_THREAD_DUMP); + this.urlRouteManagerService.openPage([ + UrlPath.THREAD_DUMP, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + agentId, + '' + Date.now() + ]); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.REAL_TIME); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.REAL_TIME, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-paging-container.component.css b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-paging-container.component.css new file mode 100644 index 000000000000..ce04f966de8a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-paging-container.component.css @@ -0,0 +1,77 @@ +:host { + z-index: 30; +} +.l-thread-chart-wrap { + display: flex; + flex-flow: row wrap; + background:#d3dbe6; + border-top:1px solid #ccd5e0; + position: absolute; + bottom: 0; + left: 0; + width:100%; + height: 100%; +} +.l-chart-group-wrap { + display: flex; + flex-flow: row wrap; +} +.l-chart-group-list { + display: flex; + flex-flow: row wrap; + width: 100%; +} +.l-message { + top: 0px; + left: 0px; + width: 100%; + height: 100%; + position: absolute; + text-align: center; + background-color: rgba(226, 226, 226, 0.5); + z-index: 9999; +} +.l-message h4 { + padding: 80px 0 10px 0; + font-weight: 100; +} +.l-message h4 span { + padding: 8px 40px 6px 40px; + background-color: #FFF; +} +.l-retry button span { + margin-right: 6px; +} +.l-message.l-no-data h4 span { + background-color: #000; + color: #FFF; +} +.l-paging { + width: 100%; + background-color: #74879a; + color: #FFF; + font-size: 15px; + margin: 5px 5px 4px 5px; + padding: 11px; +} +.l-paging .l-txt { + font-weight: lighter; +} +.l-paging .l-txt-bold { + font-weight: bolder; + font-size: 18px; +} +.l-paging .l-page { + float: right; + cursor: pointer; + padding: 0px 6px; +} +.l-paging .l-page:hover { + color: #4b99e3; +} +.l-paging .l-page:last-child { + color: #000; + cursor: initial; + text-decoration: initial; + background-color:#FFF; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-paging-container.component.html b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-paging-container.component.html new file mode 100644 index 000000000000..8f87ef7629de --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-paging-container.component.html @@ -0,0 +1,26 @@ +
+
+
+
+ Total Servers : + {{totalCount}} + [ {{startCount + 1}} ~ {{endCount}} ] +
+ +
+ +
+

Waiting Connection...

+ +
+
+

Closed connection

+ +
+
+

This node is not WAS

+
+ +
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-paging-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-paging-container.component.ts new file mode 100644 index 000000000000..471ac40e5a70 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-paging-container.component.ts @@ -0,0 +1,182 @@ +import { Component, OnInit, OnDestroy, ViewChild, ViewContainerRef, ComponentFactoryResolver } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { WebAppSettingDataService, NewUrlStateNotificationService } from 'app/shared/services'; +import { RealTimeWebSocketService, IWebSocketResponse, IWebSocketDataResult, ResponseCode, IActiveThreadCounts } from './real-time-websocket.service'; +import { RealTimeAgentChartComponent } from './real-time-agent-chart.component'; + +// TODO: 나중에 공통으로 추출. +const enum MessageTemplate { + LOADING = 'LOADING', + RETRY = 'RETRY', + NO_DATA = 'NO_DATA', + NOTHING = 'NOTHING' +} + +@Component({ + selector: 'pp-real-time-paging-container', + templateUrl: './real-time-paging-container.component.html', + styleUrls: ['./real-time-paging-container.component.css'] +}) +export class RealTimePagingContainerComponent implements OnInit, OnDestroy { + @ViewChild('agentChartPlaceHolder', { read: ViewContainerRef} ) agentChartViewContainerRef: ViewContainerRef; + + private unsubscribe = new Subject(); + private applicationName = ''; + private serviceType = ''; + private componentRefMap: any = {}; + totalCount = 0; + startCount: number; + endCount: number; + currentPage: number; + messageTemplate = MessageTemplate.LOADING; + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private webAppSettingDataService: WebAppSettingDataService, + private realTimeWebSocketService: RealTimeWebSocketService + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter(() => { + return this.newUrlStateNotificationService.hasValue(UrlPathId.APPLICATION); + }) + ).subscribe(() => { + this.applicationName = this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(); + this.serviceType = this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getServiceType(); + this.currentPage = Number.parseInt(this.newUrlStateNotificationService.getPathValue(UrlPathId.PAGE)); + this.startCount = this.currentPage * (this.realTimeWebSocketService.getPagingSize() - 1); + this.endCount = this.startCount + this.realTimeWebSocketService.getPagingSize(); + + this.webAppSettingDataService.useActiveThreadChart().subscribe((use: boolean) => { + if (use === false) { + this.resetState(); + this.resetAgentComponentRef(); + this.hide(); + return; + } + if (this.realTimeWebSocketService.isOpened()) { + this.resetAgentComponentRef(); + this.startDataRequest(); + } else { + this.realTimeWebSocketService.connect(); + } + }); + }); + this.realTimeWebSocketService.onMessage$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((response: IWebSocketResponse) => { + switch (response.type) { + case 'open': + this.onOpen(); + break; + case 'close': + this.onClose(); + break; + case 'retry': + this.onRetry(); + break; + case 'message': + this.onMessage(response.message as IWebSocketDataResult); + break; + } + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private resetAgentComponentRef(): void { + this.agentChartViewContainerRef.clear(); + this.componentRefMap = {}; + } + private resetState() { + this.applicationName = ''; + this.serviceType = ''; + } + private hide() { + this.messageTemplate = MessageTemplate.NO_DATA; + } + private startDataRequest(): void { + this.realTimeWebSocketService.send(this.getRequestDataStr(this.applicationName)); + } + private getRequestDataStr(name: string): object { + return { + type: 'REQUEST', + command: 'activeThreadCount', + parameters: { + applicationName: name + } + }; + } + private onOpen(): void { + this.startDataRequest(); + } + private onClose(): void { + this.messageTemplate = MessageTemplate.RETRY; + } + private onRetry(): void { + this.retryConnection(); + } + private onMessage(data: IWebSocketDataResult): void { + this.messageTemplate = MessageTemplate.NOTHING; + if (data.applicationName && data.applicationName !== this.applicationName) { + return; + } + this.totalCount = Object.keys(data.activeThreadCounts).length; + this.publishData(data); + } + private setAgentChart({timeStamp, activeThreadCounts}: {timeStamp?: number, activeThreadCounts: { [key: string]: IActiveThreadCounts }}): void { + const mergedKeySet = new Set([...Object.keys(this.componentRefMap), ...Object.keys(activeThreadCounts)]); + + mergedKeySet.forEach((agentName: string) => { + if (activeThreadCounts.hasOwnProperty(agentName)) { + if (typeof this.componentRefMap[agentName] === 'undefined') { + this.initAgentComponent(agentName); + } + const componentInstance = this.componentRefMap[agentName].componentRef.instance; + const isResponseSuccess = activeThreadCounts[agentName].code === ResponseCode.SUCCESS; + + componentInstance.agentName = agentName; + componentInstance.hasError = isResponseSuccess ? false : true; + componentInstance.errorMessage = isResponseSuccess ? '' : activeThreadCounts[agentName].message; + componentInstance.chartData = { + timeStamp, + responseCount: isResponseSuccess ? activeThreadCounts[agentName].status : [] + }; + } else { + this.agentChartViewContainerRef.remove(this.componentRefMap[agentName].index); + delete this.componentRefMap[agentName]; + } + }); + } + private publishData(data: IWebSocketDataResult): void { + this.setAgentChart(data); + } + private initAgentComponent(namespace: string) { + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(RealTimeAgentChartComponent); + const componentRef = this.agentChartViewContainerRef.createComponent(componentFactory); + this.componentRefMap[namespace] = { + componentRef: componentRef, + index: this.agentChartViewContainerRef.length - 1 + }; + } + needPaging(): boolean { + return this.totalCount > this.realTimeWebSocketService.getPagingSize(); + } + getTotalPage(): number[] { + const totalPage = Math.ceil(this.totalCount / this.realTimeWebSocketService.getPagingSize()); + const pages = []; + for (let i = totalPage ; i > 1 ; i-- ) { + pages.push(i); + } + return pages; + } + retryConnection(): void { + this.messageTemplate = MessageTemplate.LOADING; + this.realTimeWebSocketService.connect(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-total-chart.component.css b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-total-chart.component.css new file mode 100644 index 000000000000..1ae408e5b373 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-total-chart.component.css @@ -0,0 +1,93 @@ +:host { + display: block; +} +.l-wrapper dt { + color:#fff; + padding: 15px 4px; + display: block; + overflow: hidden; + font-size: 15px; + text-align:center; + line-height: 1em; + white-space: nowrap; + text-overflow: ellipsis; + border-bottom: 1px solid #687b8e; + background-color: #74879a; +} +.l-wrapper dd { + display: flex; + flex-flow: row wrap; + background:#fff; + justify-content:space-around; + align-items:center; + height: 135px; +} +.l-chart-section { + width: 180px; + height: 110px; + border-top: none; + padding: unset; + position: relative; +} +.l-error-template { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + justify-content: center; + align-items: center; + z-index: 2; +} +.l-error-text { + font-size: 14px; + font-weight: 600; + color: #c04e3f; +} +.l-legend { + font-size:11px; + width: 70px; + height: 110px; +} +.l-legend li { + margin:7px 0 0; + color:#666; +} +.l-legend li:first-child { + margin:0; + color:#010101; + font-weight:600; +} +.l-legend li span { + display:inline-block; +} +.l-legend li .l-text { + width:30px; + text-align:right; + margin:0 10px 0 0; +} +.l-legend li .l-circle { + width:21px; + height:11px; + color:#fff; + border-radius:10px; + text-align:center; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1em; +} +.l-legend li:nth-child(1) .l-circle { + background:#a8acb5 +} +.l-legend li:nth-child(2) .l-circle { + background:#e76f4b +} +.l-legend li:nth-child(3) .l-circle { + background:#fea63e +} +.l-legend li:nth-child(4) .l-circle { + background:#51afdf +} +.l-legend li:nth-child(5) .l-circle { + background:#33b692 +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-total-chart.component.html b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-total-chart.component.html new file mode 100644 index 000000000000..88a4c1f25ef7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-total-chart.component.html @@ -0,0 +1,39 @@ +
+
{{applicationName}}
+
+
+ + +
+

{{errorMessage}}

+
+
+
    +
  • + Total + {{getTotalCount()}} +
  • +
  • + Slow + {{chartData.responseCount[3]}} +
  • +
  • + 5s + {{chartData.responseCount[2]}} +
  • +
  • + 3s + {{chartData.responseCount[1]}} +
  • +
  • + 1s + {{chartData.responseCount[0]}} +
  • +
+
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-total-chart.component.ts b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-total-chart.component.ts new file mode 100644 index 000000000000..2d17ddcc156d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-total-chart.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { IRealTimeChartData } from './real-time-chart.component'; + +@Component({ + selector: 'pp-real-time-total-chart', + templateUrl: './real-time-total-chart.component.html', + styleUrls: ['./real-time-total-chart.component.css'] +}) +export class RealTimeTotalChartComponent implements OnInit { + @Input() applicationName: string; + @Input() hasError: boolean; + @Input() errorMessage: string; + @Input() timezone: string; + @Input() dateFormat: string; + @Input() chartData: IRealTimeChartData; + + showAxis = true; + constructor() {} + ngOnInit() {} + getTotalCount(): number { + return this.chartData.responseCount.reduce((prev: number, curr: number) => prev + curr, 0); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-websocket.service.ts b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-websocket.service.ts new file mode 100644 index 000000000000..bf867197c398 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/real-time-websocket.service.ts @@ -0,0 +1,242 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable, of, throwError } from 'rxjs'; +import { WebSocketSubject, WebSocketSubjectConfig } from 'rxjs/webSocket'; +import { timeout, catchError, map, filter } from 'rxjs/operators'; + +import { WindowRefService } from 'app/shared/services'; + +interface IWebSocketData { + type: ResponseType; + command: string; + result: IWebSocketDataResult; +} + +export interface IWebSocketDataResult { + timeStamp?: number; + applicationName?: string; + activeThreadCounts: { [key: string]: IActiveThreadCounts }; +} + +export interface IActiveThreadCounts { + code: number; + message: string; + status?: number[]; +} + +export interface IWebSocketResponse { + type: string; + message: string | IWebSocketDataResult; +} + +export const enum ResponseType { + PING = 'PING', + RESPONSE = 'RESPONSE' +} + +export const enum ResponseCode { + SUCCESS = 0, + TIMEOUT = 211, + ERROR_BLACK = 111, + OVER_DELAY = 9999 +} + +@Injectable() +export class RealTimeWebSocketService { + private url = 'agent/activeThread.pinpointws'; + private timeoutLimit = 10; // 서버로부터의 timeout response를 무시하는 최대횟수 + private timeoutCount: { [key: string]: number } = {}; // 각 agent별 timeout된 횟수 + private delayLimit = 10000; // 서버로부터의 응답을 기다리는 최대시간(ms) + private prevData: { [key: string]: IActiveThreadCounts } = {}; // Success일때의 데이터({ code, message, status })를 킵 + + private retryTimeout = 3000; + private retryCount = 0; + private maxRetryCount = 1; + private connectTime: number; + private isOpen = false; + private pagingSize = 30; + + private socket$: WebSocketSubject = null; + + private outMessage: Subject = new Subject(); + onMessage$: Observable; + + constructor( + private windowRefService: WindowRefService + ) { + this.onMessage$ = this.outMessage.asObservable(); + } + connect(): void { + if (this.isOpen === false) { + this.openWebSocket(); + } + } + isOpened(): boolean { + return this.isOpen; + } + close(): void { + if (this.isOpen) { + this.socket$.complete(); + } else { + this.outMessage.next({ + type: 'close', + message: '' + }); + } + } + send(message: object): void { + if (this.isOpen) { + this.socket$.next(message); + } + } + getPagingSize(): number { + return this.pagingSize; + } + private openWebSocket(): void { + const location = this.windowRefService.nativeWindow.location; + const protocol = location.protocol.indexOf('https') === -1 ? 'ws' : 'wss'; + const url = `${protocol}://${location.host}/${this.url}`; + + this.socket$ = new WebSocketSubject({ + url: url, + openObserver: { + next: () => { + this.isOpen = true; + this.connectTime = Date.now(); + this.outMessage.next({ + type: 'open', + message: event.toString() + }); + } + }, + closeObserver: { + next: () => { + this.onCloseEvents(); + } + } + } as WebSocketSubjectConfig); + + this.socket$.pipe( + filter((message: IWebSocketData) => { + return message.type === ResponseType.PING ? (this.send({ type: 'PONG' }), false) : true; + }), + map(({result}: {result: IWebSocketDataResult}) => { + return this.parseResult(result); + }), + // map(({timeStamp, applicationName}) => { + // const activeThreadCounts = {}; + // for (let i = 0; i < 30; i++) { + // activeThreadCounts[i] = { + // code: 0, + // message: 'OK', + // status: [ + // Math.floor(2 * Math.random()), + // Math.floor(3 * Math.random()), + // Math.floor(1 * Math.random()), + // Math.floor(4 * Math.random()) + // ] + // }; + // } + // return { + // timeStamp, + // applicationName, + // activeThreadCounts + // }; + // }), + timeout(this.delayLimit), + catchError((err: any) => err.name === 'TimeoutError' ? this.onTimeout() : throwError(err)), + filter((message: IWebSocketDataResult | null) => { + return !!message; + }), + ).subscribe((message: IWebSocketDataResult) => { + this.outMessage.next({ + type: 'message', + message: message + }); + }, (err: any) => { + console.log(err); + this.closed(); + }, () => { + console.log('Complete'); + this.closed(); + }); + } + + private parseResult(result: IWebSocketDataResult): IWebSocketDataResult { + const activeThreadCounts = Object.keys(result.activeThreadCounts).reduce((prev: IWebSocketDataResult, curr: string) => { + const responseCode = result.activeThreadCounts[curr].code; + let agentData: IActiveThreadCounts; + + switch (responseCode) { + case ResponseCode.SUCCESS: + this.timeoutCount[curr] = 0; + this.prevData[curr] = result.activeThreadCounts[curr]; + agentData = result.activeThreadCounts[curr]; + break; + case ResponseCode.TIMEOUT: + this.timeoutCount[curr] = this.timeoutCount[curr] ? this.timeoutCount[curr] + 1 : 1; + agentData = this.prevData[curr] && this.timeoutCount[curr] < this.timeoutLimit ? this.prevData[curr] : result.activeThreadCounts[curr]; + break; + default: + agentData = result.activeThreadCounts[curr]; + break; + } + return { + ...prev, + ...{ [curr]: agentData } + }; + }, {}); + + return { ...result, ...{ activeThreadCounts } }; + } + + private onTimeout(): Observable { + this.close(); + return this.getDelayMessage(); + } + + private getDelayMessage(): Observable { + const delayObj = { + code: ResponseCode.OVER_DELAY, + message: 'No Response' + }; + + if (Object.keys(this.prevData).length !== 0) { + return of({ + activeThreadCounts: Object.keys(this.prevData).reduce((prev: IWebSocketDataResult, curr: string) => { + return { + ...prev, + ...{ [curr]: delayObj } + }; + }, {}) + }); + } else { + return of(null); + } + } + + private closed(): void { + this.isOpen = false; + this.socket$ = null; + this.outMessage.next({ + type: 'close', + message: '' + }); + } + + private onCloseEvents(): void { + if (Date.now() - this.connectTime < this.retryTimeout) { + if (this.retryCount < this.maxRetryCount) { + this.retryCount++; + this.outMessage.next({ + type: 'retry', + message: '' + }); + } else { + this.outMessage.next({ + type: 'close', + message: '' + }); + } + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/real-time/resize-top.directive.ts b/web/src/main/webapp/v2/src/app/core/components/real-time/resize-top.directive.ts new file mode 100644 index 000000000000..28923164c511 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/real-time/resize-top.directive.ts @@ -0,0 +1,61 @@ +import { Directive, ElementRef, OnInit, OnDestroy, Renderer2, Input, HostListener } from '@angular/core'; + +import { WindowRefService, WebAppSettingDataService } from 'app/shared/services'; + +@Directive({ + selector: '[ppResizeTop]' +}) +export class ResizeTopDirective implements OnInit, OnDestroy { + @Input() minHeight: number; + @Input() maxHeightPadding: number; + private resizeElement: HTMLElement; + private maxHeight: number; + private dragging = false; + constructor( + private elementRef: ElementRef, + private renderer: Renderer2, + private webAppSettingDataService: WebAppSettingDataService, + private windowRefService: WindowRefService + ) { + this.resizeElement = this.elementRef.nativeElement.parentElement; + this.maxHeight = this.windowRefService.nativeWindow.innerHeight; + this.windowRefService.nativeWindow.addEventListener('mouseup', this.onWindowMouseUp.bind(this)); + this.windowRefService.nativeWindow.addEventListener('mousemove', this.onWindowMouseMove.bind(this)); + } + ngOnInit() { + this.maxHeight = this.windowRefService.nativeWindow.innerHeight - this.maxHeightPadding; + } + ngOnDestroy() { + this.windowRefService.nativeWindow.removeEventListener('mouseup', this.onWindowMouseUp); + this.windowRefService.nativeWindow.removeEventListener('mousemove', this.onWindowMouseMove); + } + onWindowMouseUp($event: MouseEvent): void { + this.dragging = false; + } + onWindowMouseMove($event: MouseEvent): void { + if (this.dragging) { + this.resizeOn(-$event.movementY); + } + } + @HostListener('mousedown', ['$event']) onMouseDown($event) { + this.dragging = true; + } + @HostListener('mousemove', ['$event']) onMouseMove($event: MouseEvent) { + if (this.dragging) { + this.resizeOn(-$event.movementY); + } + } + @HostListener('mouseup') onMouseUp() { + this.dragging = false; + } + resizeOn(y: number): void { + if (y !== 0) { + const nextHeight = (Number.parseInt(this.resizeElement.style.height, 10) || this.minHeight) + y; + if (nextHeight >= this.minHeight && nextHeight <= this.maxHeight) { + this.webAppSettingDataService.setLayerHeight(nextHeight); + this.renderer.setStyle(this.resizeElement, 'height', nextHeight + 'px'); + + } + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/index.ts b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/index.ts new file mode 100644 index 000000000000..8ddabcfaa461 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/index.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { ResponseSummaryChartComponent } from './response-summary-chart.component'; +import { ResponseSummaryChartForSideBarContainerComponent } from './response-summary-chart-for-side-bar-container.component'; +import { ResponseSummaryChartForInfoPerServerContainerComponent } from './response-summary-chart-for-info-per-server-container.component'; +import { ResponseSummaryChartForFilteredMapSideBarContainerComponent } from './response-summary-chart-for-filtered-map-side-bar-container.component'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + ResponseSummaryChartComponent, + ResponseSummaryChartForSideBarContainerComponent, + ResponseSummaryChartForFilteredMapSideBarContainerComponent, + ResponseSummaryChartForInfoPerServerContainerComponent + ], + imports: [ + SharedModule, + HelpViewerPopupModule + ], + exports: [ + ResponseSummaryChartForSideBarContainerComponent, + ResponseSummaryChartForFilteredMapSideBarContainerComponent, + ResponseSummaryChartForInfoPerServerContainerComponent + ], + providers: [] +}) +export class ResponseSummaryChartModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-filtered-map-side-bar-container.component.css b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-filtered-map-side-bar-container.component.css new file mode 100644 index 000000000000..099907bb7fd6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-filtered-map-side-bar-container.component.css @@ -0,0 +1,34 @@ +:host { + display: block; + position: relative; +} +.l-chart-item { + height: 202px; +} +.l-tool-box { + font-size: 14px; + color: #b3b5b9; + text-align: right; + position: relative; + padding: 16px 25px 0; +} +.l-tool-box .l-title { + float: left; + text-align: left; + font-size: 16px; + color: #333; + font-weight: 600; + margin: 0 0 23px; +} +.l-tool-box button { + font-size: 18px; + margin: 0; +} +.l-content-section { + padding: 0 25px 0; +} +.l-no-data { + padding-top: 60px; + text-align: center; + font-weight: 600; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-filtered-map-side-bar-container.component.html b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-filtered-map-side-bar-container.component.html new file mode 100644 index 000000000000..45c22260aebd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-filtered-map-side-bar-container.component.html @@ -0,0 +1,20 @@ +
+
+

Response Summary

+ +
+
+ + +
+ {{i18nText.NO_DATA}} +
+
+
+ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-filtered-map-side-bar-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-filtered-map-side-bar-container.component.ts new file mode 100644 index 000000000000..202756083517 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-filtered-map-side-bar-container.component.ts @@ -0,0 +1,156 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { Actions } from 'app/shared/store'; +import { StoreHelperService, WebAppSettingDataService, AgentHistogramDataService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { ServerMapData } from 'app/core/components/server-map/class/server-map-data.class'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-response-summary-chart-for-filtered-map-side-bar-container', + templateUrl: './response-summary-chart-for-filtered-map-side-bar-container.component.html', + styleUrls: ['./response-summary-chart-for-filtered-map-side-bar-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ResponseSummaryChartForFilteredMapSideBarContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + yMax = -1; + selectedTarget: ISelectedTarget; + selectedAgent = ''; + serverMapData: ServerMapData; + hiddenComponent = false; + hiddenChart = false; + useDisable = false; + showLoading = false; + i18nText = { + NO_DATA: '' + }; + chartData: IResponseTime | IResponseMilliSecondTime; + chartColors: string[]; + constructor( + private changeDetector: ChangeDetectorRef, + private translateService: TranslateService, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private agentHistogramDataService: AgentHistogramDataService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.chartColors = this.webAppSettingDataService.getColorByRequest(); + this.translateService.get('COMMON.NO_DATA').subscribe((txt: string) => { + this.i18nText.NO_DATA = txt; + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getAgentSelection(this.unsubscribe).subscribe((agent: string) => { + this.setDisable(true); + this.selectedAgent = agent; + if (this.selectedTarget) { + this.loadResponseSummaryChartData(); + } + this.changeDetector.detectChanges(); + }); + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: ServerMapData) => { + this.serverMapData = serverMapData; + if (this.selectedTarget && this.selectedTarget.isMerged === false) { + this.yMax = -1; + this.loadResponseSummaryChartData(); + } + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.yMax = -1; + this.selectedTarget = target; + this.hiddenComponent = target.isMerged; + if (target.isMerged === false) { + this.loadResponseSummaryChartData(); + } + this.changeDetector.detectChanges(); + }); + this.storeHelperService.getServerMapTargetSelectedByList(this.unsubscribe).subscribe((target: any) => { + this.yMax = -1; + this.hiddenComponent = false; + this.passDownChartData(this.agentHistogramDataService.makeChartDataForResponseSummary(target.histogram, this.getChartYMax())); + }); + } + private getChartYMax(): number { + return this.yMax === -1 ? null : this.yMax; + } + private setDisable(disable: boolean): void { + this.useDisable = disable; + this.showLoading = disable; + } + private loadResponseSummaryChartData(from?: number, to?: number): void { + const target = this.getTargetInfo(); + if (this.selectedAgent === '') { + this.passDownChartData(this.agentHistogramDataService.makeChartDataForResponseSummary(target.histogram, this.getChartYMax())); + } else { + this.passDownChartData(this.agentHistogramDataService.makeChartDataForResponseSummary(target['agentHistogram'][this.selectedAgent], this.getChartYMax())); + } + } + private passDownChartData(chartData: any): void { + if (chartData) { + this.hiddenChart = false; + this.chartData = chartData; + } else { + this.hiddenChart = true; + } + this.setDisable(false); + this.changeDetector.detectChanges(); + } + private getTargetInfo(): any { + if (this.selectedTarget.isNode) { + return this.serverMapData.getNodeData(this.selectedTarget.node[0]); + } else { + // return this.serverMapData.getNodeData(this.serverMapData.getLinkData(this.selectedTarget.link[0]).to); + return this.serverMapData.getLinkData(this.selectedTarget.link[0]); + } + } + onNotifyMax(max: number): void { + if (max > this.yMax) { + this.yMax = max; + this.storeHelperService.dispatch(new Actions.ChangeResponseSummaryChartYMax(max)); + } + } + onClickColumn(columnName: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_RESPONSE_GRAPH); + console.log('clicked Column Name :', columnName ); + if (columnName === 'Error') { + // scope.$emit('responseTimeSummaryChartDirective.showErrorTransactionList', type); + // @TODO Scatter Chart의 에러 부분만 Drag 하도록 하는 액션 + } + // @TODO FilteredMap transaction에서 만 처리되는 이벤트 + // if (useFilterTransaction) { + // scope.$emit('responseTimeSummaryChartDirective.itemClicked.' + scope.namespace, { + // "responseTime": type, + // "count": aTarget[0]._chart.config.data.datasets[0].data[aTarget[0]._index] + // }); + // } + + } + + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.RESPONSE_SUMMARY); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.RESPONSE_SUMMARY, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-info-per-server-container.component.css b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-info-per-server-container.component.css new file mode 100644 index 000000000000..fce05d97d0ac --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-info-per-server-container.component.css @@ -0,0 +1,33 @@ +:host { + display: block; +} +.l-chart-item { + height: 202px; +} +.l-tool-box { + font-size: 14px; + color: #b3b5b9; + text-align: right; + position: relative; + padding: 16px 25px 0; +} +.l-tool-box .l-title { + float: left; + text-align: left; + font-size: 16px; + color: #333; + font-weight: 600; + margin: 0 0 23px; +} +.l-tool-box button { + font-size: 18px; + margin: 0; +} +.l-content-section { + padding: 0 25px 0; +} +.l-no-data { + padding-top: 60px; + text-align: center; + font-weight: 600; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-info-per-server-container.component.html b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-info-per-server-container.component.html new file mode 100644 index 000000000000..4e1c8ee16a9e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-info-per-server-container.component.html @@ -0,0 +1,20 @@ +
+
+

Response Summary

+ +
+
+ + +
+ {{i18nText.NO_DATA}} +
+
+
+ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-info-per-server-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-info-per-server-container.component.ts new file mode 100644 index 000000000000..5f463539f588 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-info-per-server-container.component.ts @@ -0,0 +1,93 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { WebAppSettingDataService, StoreHelperService, AgentHistogramDataService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-response-summary-chart-for-info-per-server-container', + templateUrl: './response-summary-chart-for-info-per-server-container.component.html', + styleUrls: ['./response-summary-chart-for-info-per-server-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ResponseSummaryChartForInfoPerServerContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + hiddenChart = false; + yMax: number; + chartData: IResponseTime | IResponseMilliSecondTime; + chartColors: string[]; + useDisable = false; + showLoading = false; + i18nText = { + NO_DATA: '' + }; + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private webAppSettingDataService: WebAppSettingDataService, + private agentHistogramDataService: AgentHistogramDataService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.chartColors = this.webAppSettingDataService.getColorByRequest(); + this.translateService.get('COMMON.NO_DATA').subscribe((txt: string) => { + this.i18nText.NO_DATA = txt; + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getResponseSummaryChartYMax(this.unsubscribe).subscribe((max: number) => { + this.yMax = max; + }); + this.storeHelperService.getAgentSelectionForServerList(this.unsubscribe).pipe( + filter((chartData: IAgentSelection) => { + return (chartData && chartData.agent) ? true : false; + }) + ).subscribe((chartData: IAgentSelection) => { + if (chartData.responseSummary) { + this.hiddenChart = false; + this.chartData = this.agentHistogramDataService.makeChartDataForResponseSummary(chartData.responseSummary, this.yMax); + } else { + this.hiddenChart = true; + } + this.changeDetector.detectChanges(); + }); + } + onNotifyMax(max: number): void {} + onClickColumn(columnName: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_RESPONSE_GRAPH); + if (columnName === 'Error') { + // scope.$emit('responseTimeSummaryChartDirective.showErrorTransactionList', type); + // @TODO Scatter Chart의 에러 부분만 Drag 하도록 하는 액션 + } + // @TODO FilteredMap transaction에서 만 처리되는 이벤트 + // if (useFilterTransaction) { + // scope.$emit('responseTimeSummaryChartDirective.itemClicked.' + scope.namespace, { + // "responseTime": type, + // "count": aTarget[0]._chart.config.data.datasets[0].data[aTarget[0]._index] + // }); + // } + + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.RESPONSE_SUMMARY); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.RESPONSE_SUMMARY, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-side-bar-container.component.css b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-side-bar-container.component.css new file mode 100644 index 000000000000..f54bc58bff5e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-side-bar-container.component.css @@ -0,0 +1,30 @@ +.l-chart-item { + height: 202px; +} +.l-tool-box { + font-size: 14px; + color: #b3b5b9; + text-align: right; + position: relative; + padding: 16px 25px 0; +} +.l-tool-box .l-title { + float: left; + text-align: left; + font-size: 16px; + color: #333; + font-weight: 600; + margin: 0 0 23px; +} +.l-tool-box button { + font-size: 18px; + margin: 0; +} +.l-content-section { + padding: 0 25px 0; +} +.l-no-data { + padding-top: 60px; + text-align: center; + font-weight: 600; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-side-bar-container.component.html b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-side-bar-container.component.html new file mode 100644 index 000000000000..45c22260aebd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-side-bar-container.component.html @@ -0,0 +1,20 @@ +
+
+

Response Summary

+ +
+
+ + +
+ {{i18nText.NO_DATA}} +
+
+
+ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-side-bar-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-side-bar-container.component.ts new file mode 100644 index 000000000000..5414a835a961 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart-for-side-bar-container.component.ts @@ -0,0 +1,161 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { Actions } from 'app/shared/store'; +import { ServerMapData } from 'app/core/components/server-map/class/server-map-data.class'; +import { StoreHelperService, WebAppSettingDataService, AgentHistogramDataService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-response-summary-chart-for-side-bar-container', + templateUrl: './response-summary-chart-for-side-bar-container.component.html', + styleUrls: ['./response-summary-chart-for-side-bar-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ResponseSummaryChartForSideBarContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + selectedTarget: ISelectedTarget; + selectedAgent = ''; + serverMapData: ServerMapData; + hiddenComponent = false; + hiddenChart = false; + yMax = -1; + useDisable = false; + showLoading = false; + i18nText = { + NO_DATA: '' + }; + chartData: IResponseTime | IResponseMilliSecondTime; + chartColors: string[]; + constructor( + private changeDetector: ChangeDetectorRef, + private translateService: TranslateService, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private agentHistogramDataService: AgentHistogramDataService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.chartColors = this.webAppSettingDataService.getColorByRequest(); + this.translateService.get('COMMON.NO_DATA').subscribe((txt: string) => { + this.i18nText.NO_DATA = txt; + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getAgentSelection(this.unsubscribe).subscribe((agent: string) => { + this.setDisable(true); + this.selectedAgent = agent; + if (this.selectedTarget) { + this.loadResponseSummaryChartData(); + this.changeDetector.detectChanges(); + } + }); + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: ServerMapData) => { + this.serverMapData = serverMapData; + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.yMax = -1; + this.selectedTarget = target; + this.hiddenComponent = target.isMerged; + if (target.isMerged === false) { + this.loadResponseSummaryChartData(); + } else { + this.changeDetector.detectChanges(); + } + }); + this.storeHelperService.getRealTimeScatterChartRange(this.unsubscribe).subscribe((range: IScatterXRange) => { + this.yMax = -1; + this.loadResponseSummaryChartData(range.from, range.to); + }); + this.storeHelperService.getServerMapTargetSelectedByList(this.unsubscribe).subscribe((target: any) => { + this.yMax = -1; + this.hiddenComponent = false; + this.passDownChartData(this.agentHistogramDataService.makeChartDataForResponseSummary(target.histogram, this.getChartYMax())); + }); + } + private getChartYMax(): number { + return this.yMax === -1 ? null : this.yMax; + } + private setDisable(disable: boolean): void { + this.useDisable = disable; + this.showLoading = disable; + } + private loadResponseSummaryChartData(from?: number, to?: number): void { + const target = this.getTargetInfo(); + if (this.isAllAgent() && arguments.length !== 2) { + this.passDownChartData(this.agentHistogramDataService.makeChartDataForResponseSummary(target.histogram, this.getChartYMax())); + } else { + this.agentHistogramDataService.getData(target.key, target.applicationName, target.serviceTypeCode, this.serverMapData, from, to).subscribe((chartData: any) => { + const chartDataForAgent = this.isAllAgent() ? chartData['histogram'] : chartData['agentHistogram'][this.selectedAgent]; + this.passDownChartData(this.agentHistogramDataService.makeChartDataForResponseSummary(chartDataForAgent, this.getChartYMax())); + }); + } + } + private passDownChartData(chartData: any): void { + if (chartData) { + this.hiddenChart = false; + this.chartData = chartData; + } else { + this.hiddenChart = true; + } + this.setDisable(false); + this.changeDetector.detectChanges(); + } + private getTargetInfo(): any { + if (this.selectedTarget.isNode) { + return this.serverMapData.getNodeData(this.selectedTarget.node[0]); + } else { + return this.serverMapData.getLinkData(this.selectedTarget.link[0]); + } + } + private isAllAgent(): boolean { + return this.selectedAgent === ''; + } + onNotifyMax(max: number): void { + if (this.yMax === -1) { + this.yMax = max; + this.storeHelperService.dispatch(new Actions.ChangeResponseSummaryChartYMax(max)); + } + } + onClickColumn(columnName: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_RESPONSE_GRAPH); + console.log('clicked Column Name :', columnName ); + if (columnName === 'Error') { + // scope.$emit('responseTimeSummaryChartDirective.showErrorTransactionList', type); + // @TODO Scatter Chart의 에러 부분만 Drag 하도록 하는 액션 + } + // @TODO FilteredMap transaction에서 만 처리되는 이벤트 + // if (useFilterTransaction) { + // scope.$emit('responseTimeSummaryChartDirective.itemClicked.' + scope.namespace, { + // "responseTime": type, + // "count": aTarget[0]._chart.config.data.datasets[0].data[aTarget[0]._index] + // }); + // } + + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.RESPONSE_SUMMARY); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.RESPONSE_SUMMARY, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart.component.css b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart.component.css new file mode 100644 index 000000000000..116a2a1d8688 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart.component.css @@ -0,0 +1,6 @@ +:host { + width: 100%; + height: 121px; + display: block; + position: relative; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart.component.html b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart.component.html new file mode 100644 index 000000000000..82928869e15f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart.component.html @@ -0,0 +1 @@ + diff --git a/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart.component.ts b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart.component.ts new file mode 100644 index 000000000000..8cb1a3a82ba5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/response-summary-chart/response-summary-chart.component.ts @@ -0,0 +1,135 @@ +import { Component, ViewChild, ElementRef, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core'; +import { Chart } from 'chart.js'; + +@Component({ + selector: 'pp-response-summary-chart', + templateUrl: './response-summary-chart.component.html', + styleUrls: ['./response-summary-chart.component.css'] +}) +export class ResponseSummaryChartComponent implements OnInit, OnChanges { + @ViewChild('responseSummaryChart') el: ElementRef; + @Input() instanceKey: string; + @Input() chartData: any; + @Input() chartColors: string[]; + @Output() outNotifyMax: EventEmitter = new EventEmitter(); + @Output() outClickColumn: EventEmitter = new EventEmitter(); + chartObj: any; + constructor() {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if ( changes['chartData'] && changes['chartData']['firstChange'] === false ) { + this.initChart(); + } + } + private initChart(): void { + if (this.chartObj) { + if (this.chartData.max) { + this.chartObj.config.options.scales.yAxes[0].ticks.max = this.chartData.max; + } + this.chartObj.data.labels = this.chartData.keys; + this.chartObj.data.datasets[0].data = this.chartData.values; + this.chartObj.update(); + } else { + this.chartObj = new Chart(this.el.nativeElement.getContext('2d'), { + type: 'bar', + data: this.makeDataOption(), + options: this.makeNormalOption() + }); + } + this.outNotifyMax.emit(this.chartObj.scales['y-axis-0'].max); + } + private makeDataOption(): any { + return { + labels: this.chartData['keys'], + datasets: [{ + data: this.chartData['values'], + backgroundColor: this.chartColors, + borderColor: [ + 'rgba(120, 119, 121, 0)', + 'rgba(120, 119, 121, 0)', + 'rgba(120, 119, 121, 0)', + 'rgba(120, 119, 121, 0)', + 'rgba(120, 119, 121, 0)' + ], + borderWidth: 0.5 + }] + }; + } + private makeNormalOption(): any { + return { + onClick: (event, aChartEl: any[]) => { + if ( aChartEl.length > 0 ) { + this.outClickColumn.emit(aChartEl[0]._view.label); + } + event.preventDefault(); + }, + layout: { + padding: { + top: 20 + } + }, + maintainAspectRatio: false, + legend: { + display: false + }, + title: { + display: false + }, + scales: { + yAxes: [{ + gridLines: { + display: true, + drawBorder: false, + zeroLineWidth: 1.5, + zeroLineColor: 'rgb(0, 0, 0)' + }, + ticks: { + beginAtZero: true, + maxTicksLimit: 3, + callback: (label: number) => { + return ' ' + (label >= 1000 ? `${label / 1000}k` : label) + ' '; + }, + fontColor: 'rgba(162, 162, 162, 1)', + fontSize: 11, + max: this.chartData.max + } + }], + xAxes: [{ + gridLines: { + display: false, + drawBorder: false + }, + ticks: { + fontColor: 'rgba(162, 162, 162, 1)', + fontSize: 11 + } + }] + }, + animation: { + duration: 0, + onComplete: (chartElement: any) => { + const ctx = chartElement.chart.ctx; + // ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily); + ctx.fillStyle = chartElement.chart.config.options.defaultFontColor; + ctx.textAlign = 'center'; + ctx.textBaseline = 'bottom'; + chartElement.chart.data.datasets.forEach((dataset) => { + for (let i = 0 ; i < dataset.data.length ; i++) { + const model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model; + ctx.fillText(this.addComma(dataset.data[i] + ''), model.x, model.y - 5); + } + }); + } + }, + hover: { + animationDuration: 0 + }, + tooltips: { + enabled: false + } + }; + } + addComma(str: string): string { + return str.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, '$1,'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-axis-renderer.class.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-axis-renderer.class.ts new file mode 100644 index 000000000000..5fcda5bdb662 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-axis-renderer.class.ts @@ -0,0 +1,250 @@ +import { interval, of, animationFrameScheduler } from 'rxjs'; +import { map, takeWhile, concat } from 'rxjs/operators'; +import * as moment from 'moment-timezone'; +import { IOptions } from './scatter-chart.class'; +import { ScatterChartSizeCoordinateManager } from './scatter-chart-size-coordinate-manager.class'; + +export class ScatterChartAxisRenderer { + private ticksX: number; + private ticksY: number; + + private elementAxisCanvas: HTMLCanvasElement; + private ctxAxisCanvas: CanvasRenderingContext2D; + private elementAxisContainer: HTMLElement; + private elementsAxisX: HTMLElement[] = []; + private elementsAxisY: HTMLElement[] = []; + private elementAxisXUnit: HTMLElement; + private elementAxisYUnit: HTMLElement; + constructor( + private options: IOptions, + private coordinateManager: ScatterChartSizeCoordinateManager, + private elementContainer: HTMLElement + ) { + this.ticksX = this.options.ticks.x - 1; + this.ticksY = this.options.ticks.y - 1; + + this.makeAxisCanvas(); + this.drawAxisLine(); + this.drawAxisValue(); + this.updateAxisValue(); + } + private makeAxisCanvas(): void { + this.elementAxisCanvas = document.createElement('canvas'); + this.elementAxisCanvas.setAttribute('width', this.coordinateManager.getWidth() + 'px'); + this.elementAxisCanvas.setAttribute('height', this.coordinateManager.getHeight() + 'px'); + this.elementAxisCanvas.setAttribute('style', 'top: 0px; z-index: 10; position: absolute'); + + this.elementContainer.appendChild(this.elementAxisCanvas); + this.ctxAxisCanvas = this.elementAxisCanvas.getContext('2d'); + } + private drawAxisLine(): void { + const width = this.coordinateManager.getWidth(); + const height = this.coordinateManager.getHeight(); + const oPadding = this.coordinateManager.getPadding(); + const bubbleHalfSize = this.coordinateManager.getBubbleHalfSize(); + const lineColor = this.options.lineColor; + const gridAxisStyle = this.options.gridAxisStyle; + const tickX = this.coordinateManager.getWidthOfChartSpace() / this.ticksX; + const tickY = this.coordinateManager.getHeightOfChartSpace() / this.ticksY; + const xTickLength = this.options.tickLength.x; + const yTickLength = this.options.tickLength.y; + + this.ctxAxisCanvas.lineWidth = gridAxisStyle.lineWidth; + this.ctxAxisCanvas.globalAlpha = 1; + this.ctxAxisCanvas.lineCap = 'round'; + this.ctxAxisCanvas.strokeStyle = lineColor; + + this.ctxAxisCanvas.beginPath(); + this.moveTo(this.ctxAxisCanvas, oPadding.left, oPadding.top); + this.lineTo(this.ctxAxisCanvas, oPadding.left, height - oPadding.bottom); + this.lineTo(this.ctxAxisCanvas, width - oPadding.right, height - oPadding.bottom); + this.ctxAxisCanvas.stroke(); + + for (let i = 0 ; i <= this.ticksX ; i++) { + const mov = oPadding.left + bubbleHalfSize + tickX * i; + this.ctxAxisCanvas.beginPath(); + this.moveTo(this.ctxAxisCanvas, mov, height - oPadding.bottom); + this.lineTo(this.ctxAxisCanvas, mov, height - oPadding.bottom + xTickLength); + this.ctxAxisCanvas.stroke(); + } + + for (let i = 0 ; i <= this.ticksY ; i++) { + const mov = height - (oPadding.bottom + bubbleHalfSize + tickY * i); + this.ctxAxisCanvas.beginPath(); + this.moveTo(this.ctxAxisCanvas, oPadding.left, mov); + this.lineTo(this.ctxAxisCanvas, oPadding.left - yTickLength, mov); + this.ctxAxisCanvas.stroke(); + } + } + private moveTo(ctx: any, x: number, y: number): void { + if (x % 1 === 0) { + x += 0.5; + } + if (y % 1 === 0) { + y += 0.5; + } + ctx.moveTo(x, y); + } + private lineTo(ctx: any, x: number, y: number): void { + if (x % 1 === 0) { + x += 0.5; + } + if (y % 1 === 0) { + y += 0.5; + } + ctx.lineTo(x, y); + } + private drawAxisValue() { + const widthTickX = this.coordinateManager.getWidthOfChartSpace() / this.ticksX; + const widthTickY = this.coordinateManager.getHeightOfChartSpace() / this.ticksY; + const width = this.coordinateManager.getWidth(); + const height = this.coordinateManager.getHeight(); + const padding = this.coordinateManager.getPadding(); + const bubbleHalfSize = this.coordinateManager.getBubbleHalfSize(); + const axisColor = this.options.axisColor; + + this.elementAxisContainer = document.createElement('div'); + const elementDivStyle = `top: 0px; width:${width}px ; height:${height}px ;cursor: corsshair; z-index: 10; position: absolute; background-color: rgba(0,0,0,0)`; + this.elementAxisContainer.setAttribute('style', elementDivStyle); + this.elementContainer.appendChild(this.elementAxisContainer); + + // x axis + for (let i = 0 ; i <= this.ticksX ; i++) { + const tempAxisDiv = document.createElement('div'); + const style = ` + top: ${height - padding.bottom + 10}px; + left: ${padding.left - (widthTickX / 2) + (i * widthTickX)}px; + width: ${widthTickX}px; + color: ${axisColor}; + position: absolute; + text-align: center; + `; + tempAxisDiv.setAttribute('style', style + this.options.axisLabelStyle); + tempAxisDiv.textContent = ' '; + this.elementsAxisX.push(tempAxisDiv); + this.elementAxisContainer.appendChild(tempAxisDiv); + } + // y axis + for (let i = 0 ; i <= this.ticksY ; i++) { + const tempAxisDiv = document.createElement('div'); + const style = ` + top: ${bubbleHalfSize + (i * widthTickY) + padding.top - 10}px; + left: 0px; + width: ${padding.left - 10}px; + color: ${axisColor}; + position: absolute; + text-align: center; + vertical-align: middle; + `; + tempAxisDiv.setAttribute('style', style + this.options.axisLabelStyle); + tempAxisDiv.textContent = ' '; + this.elementsAxisY.push(tempAxisDiv); + this.elementAxisContainer.appendChild(tempAxisDiv); + } + + // x axis unit + const xUnit = this.options.axisUnit.x; + if (xUnit !== '') { + this.elementAxisXUnit = document.createElement('div'); + const style = ` + top: ${height - padding.bottom + 10}px; + right: 80px; + color: ${axisColor}; + position: absolute; + text-align: right; + `; + this.elementAxisXUnit.setAttribute('style', style + this.options.axisLabelStyle); + this.elementAxisXUnit.textContent = xUnit; + this.elementAxisContainer.appendChild(this.elementAxisXUnit); + } + // y axis unit + const yUnit = this.options.axisUnit.y; + if (yUnit !== '') { + this.elementAxisYUnit = document.createElement('div'); + const style = ` + top: 0px; + left: 10px; + color: ${axisColor}; + width: ${padding.left - 15}; + position: absolute; + text-align: right; + vertical-align: middle; + `; + this.elementAxisYUnit.setAttribute('style', style + this.options.axisLabelStyle); + this.elementAxisYUnit.textContent = yUnit; + this.elementAxisContainer.appendChild(this.elementAxisYUnit); + } + } + updateAxisValue(animation?: boolean, duration?: number): void { + const xRange = this.coordinateManager.getX(); + const tickX = (this.coordinateManager.getGapX()) / this.ticksX; + this.elementsAxisX.forEach((element: Element, index: number) => { + const xMoment = moment(tickX * index + xRange.from).tz(this.options.timezone); + element.innerHTML = xMoment.format(this.options.dateFormat[0]) + '
' + xMoment.format(this.options.dateFormat[1]); + }); + + const yRange = this.coordinateManager.getY(); + const tickY = (this.coordinateManager.getGapY()) / this.ticksY; + this.elementsAxisY.forEach((element: Element, index: number) => { + element.innerHTML = ((yRange.to + yRange.from) - ((tickY * index) + yRange.from)).toLocaleString(); + }); + if (animation === true) { + this.animateBackground(this.elementsAxisX[this.elementsAxisX.length - 1], duration); + } + } + private animateBackground(element: HTMLElement, duration: number): void { + const start = animationFrameScheduler.now(); + interval(1, animationFrameScheduler).pipe( + map(() => { + return (animationFrameScheduler.now() - start) / duration; + }), + takeWhile((opacity: number) => { + return opacity <= 1; + }), + concat(of(1)) + ).subscribe((opacity: number) => { + element.style.backgroundColor = `rgba(254, 255, 210, ${1 - opacity}`; + }); + + } + drawToCanvas(ctxDownload: CanvasRenderingContext2D, topPadding: number): CanvasRenderingContext2D { + let xLastLabelLeftPosition = 0; + ctxDownload.drawImage(this.elementAxisCanvas, 0, topPadding); + // x axis + ctxDownload.textAlign = 'center'; + this.elementsAxisX.forEach((element: HTMLElement) => { + ctxDownload.font = element.style.font; + ctxDownload.fillStyle = element.style.color; + const axisX = element.innerHTML.replace(/
/gi, ' ').split(' '); + axisX.forEach((txt: string, index: number) => { + ctxDownload.fillText(txt, parseInt(element.style.left, 10) + element.getBoundingClientRect().width / 2, parseInt(element.style.top, 10) + (15 * (index + 1)) + topPadding); + }); + xLastLabelLeftPosition = Math.max(xLastLabelLeftPosition, parseInt(element.style.left, 10)); + }); + // y axis + ctxDownload.textAlign = 'right'; + this.elementsAxisY.forEach((element: HTMLElement) => { + ctxDownload.font = element.style.font; + ctxDownload.fillStyle = element.style.color; + ctxDownload.fillText(element.textContent, element.getBoundingClientRect().width - 10, parseInt(element.style.top, 10) + 15 + + topPadding); + }); + // x label + if (this.options.axisUnit.x !== '' && this.elementAxisXUnit) { + ctxDownload.textAlign = 'right'; + ctxDownload.font = this.elementAxisXUnit.style.fontFamily; + ctxDownload.fillStyle = this.elementAxisXUnit.style.color; + ctxDownload.fillText(this.elementAxisXUnit.textContent, xLastLabelLeftPosition + this.elementAxisXUnit.getBoundingClientRect().width, parseInt(this.elementAxisXUnit.style.top, 10) + 30 + topPadding); + } + // y label + if (this.options.axisUnit.y !== '' && this.elementAxisYUnit) { + ctxDownload.textAlign = 'right'; + ctxDownload.font = this.elementAxisYUnit.style.fontFamily; + ctxDownload.fillStyle = this.elementAxisYUnit.style.color; + ctxDownload.fillText(this.elementAxisYUnit.textContent, parseInt(this.elementAxisYUnit.style.left, 10) + this.elementAxisYUnit.getBoundingClientRect().width, 10 + topPadding); + } + return ctxDownload; + } + reset(): void { + this.updateAxisValue(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-data-block.class.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-data-block.class.ts new file mode 100644 index 000000000000..1d909cebffa9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-data-block.class.ts @@ -0,0 +1,183 @@ +import { ITypeInfo } from './scatter-chart.class'; +import { ScatterChartTransactionTypeManager } from './scatter-chart-transaction-type-manager.class'; + +export enum DataIndex { + X, + Y, + META, + TRANSACTION_ID, + TYPE, + GROUP_COUNT +} +export enum MetadataIndex { + AGENT_NAME, TRANSACTION_PREFIX_1, TRANSACTION_PREFIX_2 +} + +export class ScatterChartDataBlock { + private from: number; + private to: number; + private resultFrom: number; + private resultTo: number; + + private agentMetadata: { [key: string]: any[] }; + private transactionData: number[][]; + + private agentList: string[] = []; + private transactionDataByAgent: {[key: string]: any} = {}; + private countByType: {[key: string]: {[key: string]: number}} = {}; + + private fromX: number; + private toX: number; + /* + oPropertyIndex : { + x: 0, + y: 1, + meta: 2, + transactionId: 3, + type: 4, + groupCount: 5 + } + */ + constructor(private originalData: IScatterData, private typeManager: ScatterChartTransactionTypeManager) { + this.initVariable(); + this.initInnerDataStructure(); + this.classifyDataByAgent(); + } + private initVariable() { + this.from = this.originalData.from; + this.to = this.originalData.to; + if (this.originalData.complete) { + this.fromX = this.resultFrom = this.originalData.from; + this.toX = this.resultTo = this.originalData.to; + } else { + this.fromX = this.resultFrom = this.originalData.resultFrom; + this.toX = this.resultTo = this.originalData.resultTo; + } + this.agentMetadata = this.originalData.scatter.metadata; + this.transactionData = []; + } + private initInnerDataStructure(): void { + Object.keys(this.agentMetadata).forEach((key: string) => { + const metaInfo = this.agentMetadata[key]; + const agentName = metaInfo[MetadataIndex.AGENT_NAME]; + this.transactionDataByAgent[agentName] = []; + + this.countByType[agentName] = {}; + this.typeManager.getTypeNameList().forEach((typeName: string) => { + this.countByType[agentName][typeName] = 0; + }); + if (this.agentList.indexOf(agentName) === -1) { + this.agentList.push(agentName); + } + }); + this.agentList.sort(); + } + private classifyDataByAgent(): void { + this.originalData.scatter.dotList.forEach((tData: number[]) => { + const agentName = this.getAgentName(tData); + const typeName = this.typeManager.getNameByIndex(tData[DataIndex.TYPE]); + const tNewData = tData.concat(); + + tNewData[DataIndex.X] += this.from; + this.transactionData.push(tNewData); + this.transactionDataByAgent[agentName].push(tNewData); + this.countByType[agentName][typeName]++; + }); + } + getAgentName(data: number[] | string): string { + if (typeof data === 'string') { + return this.agentMetadata[data][MetadataIndex.AGENT_NAME]; + } else { + return this.agentMetadata[data[DataIndex.META]][MetadataIndex.AGENT_NAME]; + } + } + getDataByAgentAndIndex(agent: string, index: number): number[] { + return this.transactionDataByAgent[agent][index]; + } + getGroupCount(data: number[]): number { + return data[DataIndex.GROUP_COUNT]; + } + getDataByIndex(index: number): number[] { + return this.transactionData[index]; + } + getTotalCount(): number { + return this.transactionData.length; + } + countByAgent(agent: string): number { + if (this.transactionDataByAgent[agent]) { + return this.transactionDataByAgent[agent].length; + } else { + return 0; + } + } + getCount(agentName: string, type: string, fromX?: number, toX?: number): number { + if ( fromX && toX ) { + return this.getCountOfRange(agentName, type, fromX, toX); + } else { + if (agentName === '') { + let sumType = 0; + Object.keys(this.countByType).forEach((innerAgentName: string) => { + sumType += this.countByType[innerAgentName][type]; + }); + return sumType; + } else { + if (this.countByType[agentName]) { + return this.countByType[agentName][type]; + } else { + return 0; + } + } + } + } + private getCountOfRange(agentName: string, type: string, fromX: number, toX: number): number { + // @TODO: agentName : ALL 인 경우 처리 + let sum = 0; + const length = this.transactionDataByAgent[agentName].length; + if (this.from >= toX || this.to <= fromX || length === 0 || (agentName in this.countByType) === false) { + return sum; + } + if (this.from >= fromX && this.to <= toX) { + return this.getCount(agentName, type); + } + for (let i = 0 ; i < length ; i++) { + const data = this.transactionDataByAgent[agentName][i]; + if (data[DataIndex.X] < fromX) { + break; + } + // if (agentName === this.getAgentName(data)) { + if (type === this.typeManager.getNameByIndex(data[DataIndex.TYPE])) { + if (data[DataIndex.X] <= toX) { + sum++; + } + } + // } + } + return sum; + } + getTransactionID(data: number[]): string { + const oMeta = this.agentMetadata[data[DataIndex.META]]; + return `${oMeta[MetadataIndex.TRANSACTION_PREFIX_1]}^${oMeta[MetadataIndex.TRANSACTION_PREFIX_2]}^${data[DataIndex.TRANSACTION_ID]}`; + } + getX(data: number[]): number { + return data[DataIndex.X]; + } + getY(data: number[]): number { + return data[DataIndex.Y]; + } + getTypeIndex(data: number[]): number { + return data[DataIndex.TYPE]; + } + getXRange(): {from: number, to: number} { + return { + 'from': this.fromX, + 'to': this.toX + }; + } + isEmpty(): boolean { + return this.transactionData.length === 0; + } + getAgentList(): string[] { + return this.agentList; + } + +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-data-load-manager.class.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-data-load-manager.class.ts new file mode 100644 index 000000000000..f1677e1f66e5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-data-load-manager.class.ts @@ -0,0 +1,142 @@ +export class ScatterChartDataLoadManager { + constructor(application, filter, option, cbLoaded) { + // this._filter = filter; + // this._option = option; + // this._application = application; + // this._cbLoaded = cbLoaded; + // this._initVar(); + } + // _initVar() { + // this._callCount = 0; + // this._bLoadCompleted = false; + // this._lastLoadTime = -1; + // }; + // option(k) { + // return this._option[k]; + // } + // loadData(cbComplete, cbSuccess, cbFail, widthOfPixel, heightOfPixel) { + // var self = this; + // var oFromTo = this._oSCManager.getX(); + + // this._oAjax = $.ajax({ + // 'url': this.getUrl(), + // 'data': { + // 'to': this._callCount === 0 ? oFromTo.max : this._lastLoadTime - 1, + // 'from': oFromTo.min, + // 'limit': this.option('fetchLimit'), + // 'filter': this._filter || '', + // 'application': this._application, + // 'xGroupUnit': widthOfPixel, + // 'yGroupUnit': heightOfPixel + // }, + // 'headers': { 'accept': 'application/json' }, + // 'dataType': 'json' + // }).done(function (oResultData) { + // if (oResultData.exception) { + // cbFail(); + // } else { + // self._callCount += 1; + // self._bLoadCompleted = oResultData.complete; + // self._lastLoadTime = oResultData.resultFrom; + // cbSuccess(oResultData, !self._bLoadCompleted, self._getIntervalTime()); + // } + // }).always(function () { + // cbComplete(); + // }); + // } + // loadRealtimeData(callbackRealtimeSuccess, callbackRealtimeFail, widthOfPixel, heightOfPixel) { + // var self = this; + // var oFromTo = this._oSCManager.getX(); + + // var beforeRequest = Date.now(); + // var currentFrom = this._nextFrom || oFromTo.max; + // var currentTo = this._nextTo || oFromTo.max + this.option('realtimeInterval'); + + // this._oRealtimeAjax = $.ajax({ + // 'url': this.getUrl(), + // 'data': { + // 'to': currentTo, + // 'from': currentFrom, + // 'limit': this.option('fetchLimit'), + // 'filter': '', + // 'application': this._application, + // 'xGroupUnit': widthOfPixel, + // 'yGroupUnit': heightOfPixel, + // 'backwardDirection': false + // }, + // 'headers': { 'accept': 'application/json' }, + // 'dataType': 'json' + // }).done(function (oResultData) { + // if (oResultData.exception) { + // callbackRealtimeFail(); + // } else { + + // self._nextFrom = oResultData.complete ? oResultData.to : oResultData.resultTo; + // self._nextTo = self._nextFrom + self.option('realtimeInterval'); + + // callbackRealtimeSuccess(oResultData, self._calcuRealtimeIntervalTime(oResultData.currentServerTime, Date.now() - beforeRequest), self._isResetRealtime(oResultData.currentServerTime), oResultData.currentServerTime); + // self._cbLoaded(self._oSCManager.getX(), self._nextFrom, self._nextTo); + // } + // }).fail(function () { + // setTimeout(function () { + // self.loadRealtimeData(callbackRealtimeSuccess, callbackRealtimeFail, widthOfPixel, heightOfPixel); + // }, (self.option('realtimeInterval') / 2)); + // }); + // } + // _isResetRealtime(currentServerTime) { + // return (currentServerTime - this._nextTo) >= this.option('realtimeResetTimeGap'); + // } + // _calcuRealtimeIntervalTime(currentServerTime, requestGap) { + // var interval = parseInt(this.option('realtimeInterval'), 0); + // if (requestGap > interval) { + // return 0; + // } else { + // var gapTime = (currentServerTime - this._nextTo) - this.option('realtimeDefaultTimeGap'); + // if (gapTime < 0) { + // return Math.max(interval - requestGap - gapTime, interval); + // } else { + // return Math.max(interval - requestGap - gapTime, 0); + // } + // } + // } + // setRealtimeFrom(from) { + // this._nextFrom = from; + // this._nextTo = from + this.option('realtimeInteraval'); + // } + // _getIntervalTime() { + // if (this.option('useIntervalForFetching')) { + // return this.option('fetchingInterval'); + // } + // return 0; + // } + // getRealtimeInterval() { + // return this.option('realtimeDefaultTimeGap'); + // } + // getUrl() { + // return this.option('url'); + // } + // initCallCount() { + // this._callCount = 0; + // } + // isFirstRequest() { + // return this._callCount === 0; + // } + // isCompleted() { + // return this._bLoadCompleted; + // } + // abort() { + // if (this._oAjax) { + // this._oAjax.abort(); + // } + // if (this._oRealtimeAjax) { + // this._oRealtimeAjax.abort(); + // } + // } + // setTimeManager(oSCManager) { + // this._oSCManager = oSCManager; + // } + // reset = function () { + // this._bLoadCompleted = false; + // this.initCallCount(); + // } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-grid-renderer.class.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-grid-renderer.class.ts new file mode 100644 index 000000000000..d88d7ebffca7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-grid-renderer.class.ts @@ -0,0 +1,96 @@ +import { IOptions } from './scatter-chart.class'; +import { ScatterChartSizeCoordinateManager } from './scatter-chart-size-coordinate-manager.class'; + +export class ScatterChartGridRenderer { + private ticksX: number; + private ticksY: number; + private elementGridCanvas: HTMLCanvasElement; + private ctxGridCanvas: CanvasRenderingContext2D; + constructor(private options: IOptions, private coordinateManager: ScatterChartSizeCoordinateManager, private elementContainer: HTMLElement) { + this.ticksX = this.options.ticks.x - 1; + this.ticksY = this.options.ticks.y - 1; + + this.makeGridCanvas(); + this.drawGridLine(); + } + private makeGridCanvas(): void { + this.elementGridCanvas = document.createElement('canvas'); + this.elementGridCanvas.setAttribute('width', this.coordinateManager.getWidth() + 'px'); + this.elementGridCanvas.setAttribute('height', this.coordinateManager.getHeight() + 'px'); + this.elementGridCanvas.setAttribute('style', 'top: 0px; z-index: 0; position: absolute'); + + this.elementContainer.appendChild(this.elementGridCanvas); + this.ctxGridCanvas = this.elementGridCanvas.getContext('2d'); + } + private drawGridLine(): void { + this.setStyle(this.ctxGridCanvas, this.options.gridAxisStyle); + this.drawXGridLine(); + this.drawYGridLine(); + } + private drawXGridLine(): void { + const height = this.coordinateManager.getHeight(); + const padding = this.coordinateManager.getPadding(); + const bubbleHalfSize = this.coordinateManager.getBubbleHalfSize(); + const tickX = this.coordinateManager.getWidthOfChartSpace() / this.ticksX; + + const xStart = padding.left + bubbleHalfSize; + const yStart = padding.top; + const yEnd = height - padding.bottom; + for (let i = 0 ; i <= this.ticksX ; i++) { + const x = xStart + (tickX * i); + this.drawLine(this.ctxGridCanvas, x, yStart, x, yEnd); + } + } + private drawYGridLine(): void { + const width = this.coordinateManager.getWidth(); + const height = this.coordinateManager.getHeight(); + const padding = this.coordinateManager.getPadding(); + const bubbleHalfSize = this.coordinateManager.getBubbleHalfSize(); + const tickY = this.coordinateManager.getHeightOfChartSpace() / this.ticksY; + + const xStart = padding.left; + const xEnd = width - padding.right; + const yZero = padding.bottom + bubbleHalfSize; + for (let i = 0 ; i <= this.ticksY ; i++) { + const y = height - (yZero + tickY * i); + this.drawLine(this.ctxGridCanvas, xStart, y, xEnd, y); + } + } + private drawLine(context: CanvasRenderingContext2D, xStart: number, yStart: number, xEnd: number, yEnd: number): void { + context.beginPath(); + this.moveTo(context, xStart, yStart); + this.lineTo(context, xEnd, yEnd); + context.stroke(); + } + private setStyle(ctx: any, styles: any): void { + Object.keys(styles).forEach((key: string) => { + if (key === 'lineDash') { + ctx.setLineDash(styles[key]); + } else { + ctx[key] = styles[key]; + } + }); + } + private moveTo(ctx: any, x: number, y: number): void { + if (x % 1 === 0) { + x += 0.5; + } + if (y % 1 === 0) { + y += 0.5; + } + ctx.moveTo(x, y); + } + private lineTo(ctx: any, x: number, y: number): void { + if (x % 1 === 0) { + x += 0.5; + } + if (y % 1 === 0) { + y += 0.5; + } + ctx.lineTo(x, y); + } + drawToCanvas(ctxDownload: CanvasRenderingContext2D, topPadding: number): CanvasRenderingContext2D { + ctxDownload.drawImage(this.elementGridCanvas, 0, topPadding); + return ctxDownload; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-mouse-manager.class.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-mouse-manager.class.ts new file mode 100644 index 000000000000..3c421257c3af --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-mouse-manager.class.ts @@ -0,0 +1,332 @@ +import * as moment from 'moment-timezone'; +import { Observable, Subject } from 'rxjs'; +import { IOptions } from './scatter-chart.class'; +import { ScatterChartSizeCoordinateManager } from './scatter-chart-size-coordinate-manager.class'; + +export class ScatterChartMouseManager { + elementAxisWrapper: HTMLElement; + elementXAxisLabelWrapper: HTMLElement; + elementXAxisLabel: HTMLElement; + elementYAxisLabelWrapper: HTMLElement; + elementYAxisLabel: HTMLElement; + elementDragWrapper: HTMLElement; + elementDragArea: HTMLElement; + private outDragArea: Subject; + onDragArea$: Observable; + constructor( + private options: IOptions, + private coordinateManager: ScatterChartSizeCoordinateManager, + private elementContainer: HTMLElement + ) { + this.outDragArea = new Subject(); + this.onDragArea$ = this.outDragArea.asObservable(); + + const padding = this.coordinateManager.getPadding(); + const areaWidth = this.coordinateManager.getWidth() - padding.left - padding.right; + const areaHeight = this.coordinateManager.getHeight() - padding.top - padding.bottom; + const redLineWidth = 10; + + this.initWrapperElement(areaWidth, areaHeight); + this.initXLabelElement(areaHeight, redLineWidth); + this.initYLabelElement(padding.left, redLineWidth); + this.initDragElement(); + this.initEvent(); + } + private initWrapperElement(areaWidth: number, areaHeight: number): void { + this.elementAxisWrapper = document.createElement('div'); + this.elementAxisWrapper.setAttribute('style', ` + top: ${this.coordinateManager.getTopPadding()}px; + left: ${this.coordinateManager.getLeftPadding()}px; + width: ${areaWidth}px; + height: ${areaHeight}px; + cursor: crosshair; + z-index: 600; + position: absolute; + background-color: rgba(0,0,0,0); + `); + this.elementAxisWrapper.setAttribute('class', 'overlay'); + this.elementContainer.appendChild(this.elementAxisWrapper); + } + private initXLabelElement(areaHeight: number, redLineWidth: number): void { + this.elementXAxisLabelWrapper = document.createElement('div'); + this.elementXAxisLabelWrapper.draggable = false; + this.elementXAxisLabelWrapper.setAttribute('style', ` + top: ${areaHeight + redLineWidth}px; + left: 0px; + color: #FFF; + width: 80px; + display: none; + position: absolute; + text-align: center; + background: #000; + font-family: monospace; + margin-left: ${-(56 / 2)}px; + ` + this.options.axisLabelStyle); + this.elementXAxisLabel = document.createElement('span'); + const elementXLine = document.createElement('div'); + elementXLine.setAttribute('style', ` + top: ${-redLineWidth}px; + left: 27px; + height: ${redLineWidth}px; + position: absolute; + border-left: 1px solid red; + `); + this.elementXAxisLabelWrapper.appendChild(this.elementXAxisLabel); + this.elementXAxisLabelWrapper.appendChild(elementXLine); + this.elementAxisWrapper.appendChild(this.elementXAxisLabelWrapper); + } + private initYLabelElement(paddingLeft: number, redLineWidth: number): void { + this.elementYAxisLabelWrapper = document.createElement('div'); + this.elementYAxisLabelWrapper.draggable = false; + this.elementYAxisLabelWrapper.setAttribute('style', ` + top: 0px; + left: ${-(paddingLeft - 2)}px; + color: #fff; + width: ${paddingLeft - 2 - redLineWidth}px; + display: none; + position: absolute; + margin-top: ${-redLineWidth}px; + text-align: right; + background: #000; + font-family: monospace; + padding-right: 3px; + vertical-align: middle; + ` + this.options.axisLabelStyle); + this.elementYAxisLabel = document.createElement('span'); + const elementYLine = document.createElement('div'); + elementYLine.setAttribute('style', ` + top: 9px; + right: -10px; + width: ${redLineWidth}px; + position: absolute; + border-top: 1px solid red; + `); + this.elementYAxisLabelWrapper.appendChild(this.elementYAxisLabel); + this.elementYAxisLabelWrapper.appendChild(elementYLine); + this.elementAxisWrapper.appendChild(this.elementYAxisLabelWrapper); + } + private initDragElement(): void { + this.elementDragWrapper = document.createElement('div'); + this.elementDragWrapper.setAttribute('style', ` + top: 0px; + left: 0px; + width: ${this.coordinateManager.getWidth()}px; + height: ${this.coordinateManager.getHeight()}px; + cursor: crosshair; + z-index: 601; + position: absolute; + background-color: rgba(0,0,0,0); + `); + this.elementDragArea = document.createElement('div'); + this.elementDragArea.setAttribute('style', ` + width: 0px; + height: 0px; + display: none; + position: absolute; + border: 1px solid #469AE4; + background-color: rgba(237, 242, 248, 0.5); + `); + this.elementDragWrapper.appendChild(this.elementDragArea); + this.elementContainer.appendChild(this.elementDragWrapper); + } + private initEvent() { + const padding = this.coordinateManager.getPadding(); + const areaWidth = this.coordinateManager.getWidth(); + const areaHeight = this.coordinateManager.getHeight(); + const axisArea = { + leftRevision: this.coordinateManager.getBubbleHalfSize() + padding.left, + width: areaWidth - this.coordinateManager.getBubbleHalfSize() + padding.left - padding.right, + topRevision: padding.top, + height: areaHeight - padding.top - padding.bottom - this.coordinateManager.getBubbleHalfSize() + }; + + let startDrag = false; + let dragStartX = 0; + let dragStartY = 0; + let calculatedOffsetX = 0; + let calculatedOffsetY = 0; + let previousDragX = -1; + let previousDragY = -1; + function forceMouseUp(userMouseUp: boolean) { + startDrag = false; + this.elementDragArea.style.display = 'none'; + if (userMouseUp) { + const fromX = (calculatedOffsetX >= dragStartX ? dragStartX : calculatedOffsetX) - axisArea.leftRevision; + const toX = (calculatedOffsetX >= dragStartX ? calculatedOffsetX : dragStartX) - axisArea.leftRevision; + const fromY = (calculatedOffsetY >= dragStartY ? calculatedOffsetY : dragStartY) - axisArea.topRevision; + const toY = (calculatedOffsetY >= dragStartY ? dragStartY : calculatedOffsetY) - axisArea.topRevision; + this.outDragArea.next({ + x: { + from: Math.max(fromX, 0), + to: Math.min(toX, axisArea.width) + }, + y: { + // y값은 from과 to를 뒤집어서 보내야 함. + from: Math.max(axisArea.height - fromY, 0), + to: Math.min(axisArea.height - toY, axisArea.height) + } + }); + } + this.elementDragArea.style.top = '0px'; + this.elementDragArea.style.left = '0px'; + this.elementDragArea.style.width = '0px'; + this.elementDragArea.style.height = '0px'; + previousDragX = -1; + previousDragY = -1; + } + this.elementDragWrapper.addEventListener('mousedown', (event: MouseEvent) => { + startDrag = true; + dragStartX = calculatedOffsetX = event.offsetX; + dragStartY = calculatedOffsetY = event.offsetY; + this.elementDragArea.style.display = 'block'; + this.elementDragArea.style.top = dragStartY + 'px'; + this.elementDragArea.style.left = dragStartX + 'px'; + event.preventDefault(); + }); + this.elementDragWrapper.addEventListener('mouseup', (event: MouseEvent) => { + forceMouseUp.call(this, true); + event.preventDefault(); + }); + + this.elementDragWrapper.addEventListener('mousemove', (event: MouseEvent) => { + calculatedOffsetX += event.movementX; + calculatedOffsetY += event.movementY; + if (startDrag) { + this.checkAxisLabel(calculatedOffsetX, calculatedOffsetY, axisArea); + if (previousDragX !== calculatedOffsetX) { + if (calculatedOffsetX <= areaWidth) { + if (calculatedOffsetX >= dragStartX) { + this.elementDragArea.style.left = dragStartX + 'px'; + this.elementDragArea.style.width = (calculatedOffsetX - dragStartX) + 'px'; + } else { + this.elementDragArea.style.left = calculatedOffsetX + 'px'; + this.elementDragArea.style.width = (dragStartX - calculatedOffsetX) + 'px'; + } + previousDragX = calculatedOffsetX; + } + } + if (previousDragY !== calculatedOffsetY) { + if (calculatedOffsetY <= areaHeight) { + if (calculatedOffsetY >= dragStartY) { + this.elementDragArea.style.top = dragStartY + 'px'; + this.elementDragArea.style.height = (calculatedOffsetY - dragStartY) + 'px'; + } else { + this.elementDragArea.style.top = calculatedOffsetY + 'px'; + this.elementDragArea.style.height = (dragStartY - calculatedOffsetY) + 'px'; + } + previousDragY = calculatedOffsetY; + } + } + } else { + this.checkAxisLabel(event.offsetX, event.offsetY, axisArea); + } + event.preventDefault(); + }); + this.elementDragWrapper.addEventListener('mouseleave', (event: MouseEvent) => { + forceMouseUp.call(this, false); + this.hideAxisLabel(); + event.preventDefault(); + return false; + }); + } + private checkAxisLabel(offsetX: number, offsetY: number, axisArea: any): void { + if (!this.options.showMouseGuideLine) { + return; + } + const x = offsetX - axisArea.leftRevision; + const y = offsetY - axisArea.topRevision; + if (x >= 0 && x <= axisArea.width || y >= 0 && y <= axisArea.height) { + if (x >= 0 && x <= axisArea.width) { + this.showXAxisLabel(); + } else { + this.hideXAxisLabel(); + } + if (y >= 0 && y <= axisArea.height) { + this.showYAxisLabel(); + } else { + this.hideYAxisLabel(); + } + this.setAndMoveAxisLabel(x, y); + } else { + this.hideAxisLabel(); + } + // if (x >= 0 && x <= axisArea.width && y >= 0 && y <= axisArea.height) { + // this.showAxisLabel(); + // this.setAndMoveAxisLabel(x, y); + // } else { + // this.hideAxisLabel(); + // } + } + private setAndMoveAxisLabel(x: number, y: number): void { + const height = this.coordinateManager.getHeight(); + const padding = this.coordinateManager.getPadding(); + const bubbleHalfSize = this.coordinateManager.getBubbleHalfSize(); + const xLabel = moment(this.coordinateManager.parseMouseXToXData(x - bubbleHalfSize)).tz(this.options.timezone).format(this.options.dateFormat[1]); + const yLabel = this.coordinateManager.parseMouseYToYData(height - y - padding.bottom - padding.top - bubbleHalfSize); + this.elementXAxisLabel.textContent = xLabel; + this.elementYAxisLabel.textContent = yLabel.toLocaleString(); + this.elementXAxisLabelWrapper.style.left = (x + bubbleHalfSize) + 'px'; + this.elementYAxisLabelWrapper.style.top = (y + bubbleHalfSize) + 'px'; + } + showXAxisLabel(): void { + this.elementXAxisLabelWrapper.style.display = 'block'; + } + showYAxisLabel(): void { + this.elementYAxisLabelWrapper.style.display = 'block'; + } + showAxisLabel(): void { + this.showXAxisLabel(); + this.showYAxisLabel(); + } + hideXAxisLabel(): void { + this.elementXAxisLabelWrapper.style.display = 'none'; + } + hideYAxisLabel(): void { + this.elementYAxisLabelWrapper.style.display = 'none'; + } + hideAxisLabel(): void { + this.hideXAxisLabel(); + this.hideYAxisLabel(); + } + // triggerDrag(welFakeSelectBox) { + // var oDragAreaPosition = this._adjustSelectBoxForChart(welFakeSelectBox); + // this._oCallback.onSelect(oDragAreaPosition, this._parseCoordinatesToXY(oDragAreaPosition)); + // } + // _adjustSelectBoxForChart(welSelectBox) { + // var oPadding = this._oSCManager.getPadding(); + // var bubbleSize = this._oSCManager.getBubbleSize(); + // var nMinTop = oPadding.top + bubbleSize; + // var nMinLeft = oPadding.left + bubbleSize; + // var nMaxRight = this._oSCManager.getWidth() - oPadding.right - bubbleSize; + // var nMaxBottom = this._oSCManager.getHeight() - oPadding.bottom - bubbleSize; + + // var nLeft = parseInt(welSelectBox.css("left"), 10); + // var nRight = nLeft + welSelectBox.width(); + // var nTop = parseInt(welSelectBox.css("top"), 10); + // var nBottom = nTop + welSelectBox.height(); + + // nTop = Math.max(nTop, nMinTop); + // nLeft = Math.max(nLeft, nMinLeft); + // nRight = Math.min(nRight, nMaxRight); + // nBottom = Math.min(nBottom, nMaxBottom); + + // var oNextInfo = { + // "top": nTop, + // "left": nLeft, + // "width": nRight - nLeft, + // "height": nBottom - nTop + // }; + // welSelectBox.animate(oNextInfo, 200); + // return oNextInfo; + // } + // _parseCoordinatesToXY(oPosition) { + // var oPadding = this._oSCManager.getPadding(); + // var bubbleSize = this._oSCManager.getBubbleSize(); + // return { + // "fromX": this._oSCManager.parseMouseXToXData(oPosition.left - oPadding.left - bubbleSize), + // "toX": this._oSCManager.parseMouseXToXData(oPosition.left + oPosition.width - oPadding.left - bubbleSize), + // "fromY": this._oSCManager.parseMouseYToYData(this._oSCManager.getHeight() - (oPadding.bottom + bubbleSize) - (oPosition.top + oPosition.height)), + // "toY": this._oSCManager.parseMouseYToYData(this._oSCManager.getHeight() - (oPadding.bottom + bubbleSize) - oPosition.top) + // }; + // } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-renderer-manager.class.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-renderer-manager.class.ts new file mode 100644 index 000000000000..7a45a8a2cf5a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-renderer-manager.class.ts @@ -0,0 +1,247 @@ +import { IOptions } from './scatter-chart.class'; +import { DataIndex, ScatterChartDataBlock } from './scatter-chart-data-block.class'; +import { ScatterChartSizeCoordinateManager } from './scatter-chart-size-coordinate-manager.class'; +import { ScatterChartTransactionTypeManager } from './scatter-chart-transaction-type-manager.class'; + +export class ScatterChartRendererManager { + private canvasMap: {[key: string]: HTMLCanvasElement[]}; + private ctxMap: {[key: string]: CanvasRenderingContext2D[]}; + + private elementScroller: HTMLElement; + private scrollOrder: number[]; + private requestAnimationFrameRef: any; + constructor(private options: IOptions, private coordinateManager: ScatterChartSizeCoordinateManager, private elementContainer: any, private typeManager: ScatterChartTransactionTypeManager) { + this.initVariable(); + this.initCanvasWrapper(); + } + private initVariable(): void { + this.canvasMap = {}; + this.ctxMap = {}; + } + private initCanvasWrapper() { + let zIndex = 100; + const bubbleSize = this.coordinateManager.getBubbleSize(); + const bubbleHalfSize = this.coordinateManager.getBubbleHalfSize(); + const widthOfChartSpace = this.coordinateManager.getWidthOfChartSpace(); + const heightOfChartSpace = this.coordinateManager.getHeightOfChartSpace(); + + this.elementScroller = document.createElement('div'); + this.elementScroller.setAttribute('style', 'top: 0px; left: 0px; position: absolute; background-color: grey; height:' + (heightOfChartSpace + bubbleSize) + 'px' ); + this.elementScroller.setAttribute('class', 'canvas-scroller'); + + const elementCanvasWrapper = document.createElement('div'); + const elementCanvasWrapperStyles = ` + top: ${this.coordinateManager.getTopPadding()}px; + left: ${(this.coordinateManager.getLeftPadding() + bubbleHalfSize)}px; + width: ${widthOfChartSpace}px; + height: ${heightOfChartSpace + bubbleSize}px; + z-index: ${zIndex++}; + overflow: hidden; + position: absolute; + `; + elementCanvasWrapper.setAttribute('class', 'canvas-wrapper'); + elementCanvasWrapper.setAttribute('style', elementCanvasWrapperStyles); + elementCanvasWrapper.appendChild(this.elementScroller); + this.elementContainer.appendChild(elementCanvasWrapper); + } + makeDataCanvas(dataBlock: ScatterChartDataBlock, agentList: string[]): void { + let zIndex = 110; + const bubbleSize = this.coordinateManager.getBubbleSize(); + const prefix = this.options.prefix; + const heightOfChartSpace = this.coordinateManager.getHeightOfChartSpace(); + const canvasWidth = this.coordinateManager.getCanvasWidth(); + + agentList.forEach((agentName: string, index: number) => { + this.typeManager.getTypeNameList().forEach((typeName: string) => { + const key = `${agentName}-${prefix}-${typeName}`; + if ((key in this.canvasMap) === false) { + const canvas1 = this.createCanvas({ + width: canvasWidth + 'px', + height: (heightOfChartSpace + bubbleSize) + 'px', + 'data-agent': agentName, + 'data-type': typeName, + 'data-key': key + }, ` + top: 0px; + left: 0px; + z-index: ${zIndex++}; + position: absolute; + `); + const canvas2 = this.createCanvas({ + width: canvasWidth + 'px', + height: (heightOfChartSpace + bubbleSize) + 'px', + 'data-agent': agentName, + 'data-type': typeName, + 'data-key': key + }, ` + top: 0px; + left: ${canvasWidth}px; + z-index: ${zIndex++}; + position: absolute; + `); + this.elementScroller.appendChild(canvas1); + this.elementScroller.appendChild(canvas2); + this.canvasMap[key] = [canvas1, canvas2]; + this.ctxMap[key] = [canvas1.getContext('2d'), canvas2.getContext('2d')]; + } + }); + }); + this.scrollOrder = [0, 1]; + } + private createCanvas(attrs: object, styles: string): HTMLCanvasElement { + const elementCanvas = document.createElement('canvas'); + Object.keys(attrs).forEach((key: string) => { + elementCanvas.setAttribute(key, attrs[key]); + }); + elementCanvas.setAttribute('style', styles); + return elementCanvas; + } + setTypeView(agentName: string, typeName: string, typeChecked: boolean): void { + const viewType = typeChecked ? 'block' : 'none'; + Object.keys(this.canvasMap).forEach((key: string) => { + this.canvasMap[key].forEach((canvas: HTMLCanvasElement) => { + if (key.endsWith(typeName)) { + canvas.style.display = viewType; + } + }); + }); + } + toggle(bIsAll: boolean, agentName: string, type: string): void { + Object.keys(this.canvasMap).forEach((key: string) => { + this.canvasMap[key].forEach((canvas: HTMLCanvasElement) => { + if ((bIsAll || key.startsWith(agentName)) && key.endsWith(type)) { + if ( canvas.style.display === 'none' ) { + canvas.style.display = 'block'; + } else { + canvas.style.display = 'none'; + } + } + }); + }); + } + showSelectedAgent(agentName: string): void { + if (agentName === '') { + Object.keys(this.canvasMap).forEach((key: string) => { + this.canvasMap[key].forEach((canvas: HTMLCanvasElement) => { + if (this.typeManager.isCheckedByName(key.split('-').pop())) { + canvas.style.display = 'block'; + } else { + canvas.style.display = 'none'; + } + }); + }); + } else { + Object.keys(this.canvasMap).forEach((key: string) => { + if (key.startsWith(agentName)) { + this.canvasMap[key].forEach((canvas: HTMLCanvasElement) => { + if (this.typeManager.isCheckedByName(key.split('-').pop())) { + canvas.style.display = 'block'; + } + }); + } else { + this.canvasMap[key].forEach((canvas: HTMLCanvasElement) => { + canvas.style.display = 'none'; + }); + } + }); + } + } + drawTransaction(key: string, color: string, data: number[]): void { + const bubbleRadius = this.options.bubbleRadius; + const rangeY = this.coordinateManager.getY(); + + let x = (data[DataIndex.X] - this.coordinateManager.getInitFromX()) * this.coordinateManager.getPixelPerTime(); + const y = this.coordinateManager.parseYDataToYChart(Math.min(rangeY.to, Math.max(rangeY.from, data[DataIndex.Y]))); + const r = this.coordinateManager.parseZDataToZChart(bubbleRadius); + + let ctxIndex = this.scrollOrder[0]; + const canvasWidth = this.coordinateManager.getCanvasWidth(); + const zeroLeft = Math.round(parseInt(this.canvasMap[key][ctxIndex].style.left, 10)); + const currentMaxX = zeroLeft + canvasWidth; + if (x > currentMaxX) { + ctxIndex = this.scrollOrder[1]; + x -= currentMaxX; + } else { + x -= zeroLeft; + } + + this.ctxMap[key][ctxIndex].beginPath(); + this.ctxMap[key][ctxIndex].fillStyle = color; + this.ctxMap[key][ctxIndex].strokeStyle = color; + this.ctxMap[key][ctxIndex].arc(x, y, r, 0, Math.PI * 2, true); + this.ctxMap[key][ctxIndex].globalAlpha = 0.3 + (0.1 * data[DataIndex.GROUP_COUNT]); + this.ctxMap[key][ctxIndex].fill(); + } + moveChart(moveXValue: number, duration: number): void { + const canvasWidth = this.coordinateManager.getCanvasWidth(); + const height = this.coordinateManager.getHeight(); + const baseLeft = parseInt(this.elementScroller.style.left, 10); + const nextLeft = baseLeft - moveXValue; + + const self = this; + let startTime = -1; + function moveElement(timestamp: number, inMoveXValue: number, inDuration: number): void { + const runTime = timestamp - startTime; + let progress = runTime / inDuration; + progress = Math.min(progress, 1); + + self.elementScroller.style.left = (baseLeft + -(inMoveXValue * progress)).toFixed(2) + 'px'; + if (runTime < inDuration) { + self.requestAnimationFrameRef = window.requestAnimationFrame((time: number) => { + moveElement(time, inMoveXValue, inDuration); + }); + } else { + const orderFirst = self.scrollOrder[0]; + const orderSecond = self.scrollOrder[1]; + let bOverBoundary = false; + Object.keys(self.canvasMap).forEach((key: string): void => { + const canvasArr = self.canvasMap[key]; + if (Math.abs(nextLeft) > (parseInt(canvasArr[orderFirst].style.left, 10) + canvasWidth)) { + bOverBoundary = true; + canvasArr[orderFirst].style.left = (parseInt(canvasArr[orderSecond].style.left, 10) + canvasWidth) + 'px'; + self.ctxMap[key][orderFirst].clearRect(0, 0, canvasWidth, height); + } + }); + if (bOverBoundary) { + self.scrollOrder[0] = orderSecond; + self.scrollOrder[1] = orderFirst; + } + } + } + this.requestAnimationFrameRef = window.requestAnimationFrame((timestamp: number) => { + startTime = timestamp; + moveElement(timestamp, moveXValue, 300); + }); + } + reset() { + window.cancelAnimationFrame(this.requestAnimationFrameRef); + this.scrollOrder = [0, 1]; + this.elementScroller.style.left = '0px'; + this.elementScroller.innerHTML = ''; + this.initVariable(); + } + clear() { + const width = this.coordinateManager.getCanvasWidth(); + const height = this.coordinateManager.getHeight(); + + Object.keys(this.ctxMap).forEach((key: string) => { + this.ctxMap[key].forEach((ctx: CanvasRenderingContext2D) => { + ctx.clearRect(0, 0, width, height); + }); + }); + } + drawToCanvas(ctxDownload: CanvasRenderingContext2D, topPadding: number): CanvasRenderingContext2D { + const padding = this.coordinateManager.getPadding(); + const bubbleHalfSize = this.coordinateManager.getBubbleHalfSize(); + // scatter + Object.keys(this.canvasMap).forEach((key: string) => { + this.canvasMap[key].forEach((canvas: HTMLCanvasElement) => { + if (canvas.style.display !== 'none') { + ctxDownload.drawImage(canvas, padding.left + bubbleHalfSize, padding.top + topPadding); + } + }); + }); + ctxDownload.textBaseline = 'top'; + return ctxDownload; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-size-coordinate-manager.class.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-size-coordinate-manager.class.ts new file mode 100644 index 000000000000..473072c8f132 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-size-coordinate-manager.class.ts @@ -0,0 +1,123 @@ +import { IOptions } from './scatter-chart.class'; + +export class ScatterChartSizeCoordinateManager { + private initFromX: number; // for realtime + private widthOfChartSpace: number; + private heightOfChartSpace: number; + private timePerPixel: number; + private pixelPerTime: number; + constructor(private options: IOptions) { + this.initVar(); + } + private initVar(): void { + const bubbleRaduis = this.options.bubbleRadius; + + this.initFromX = this.options.x.from; + this.widthOfChartSpace = (this.options.width - (this.options.padding.left + this.options.padding.right)) - bubbleRaduis * 2; + this.heightOfChartSpace = (this.options.height - (this.options.padding.top + this.options.padding.bottom)) - bubbleRaduis * 2; + + this.calcuUnitValue(); + } + private calcuUnitValue(): void { + this.timePerPixel = this.getGapX() / this.widthOfChartSpace; + this.pixelPerTime = this.widthOfChartSpace / this.getGapX(); + } + getInitFromX(): number { + return this.initFromX; + } + getLeftPadding(): number { + return this.options.padding.left; + } + getTopPadding(): number { + return this.options.padding.top; + } + getCanvasWidth(): number { + return this.widthOfChartSpace; + } + getWidth(): number { + return this.options.width; + } + getHeight(): number { + return this.options.height; + } + getX(): {from: number, to: number} { + return { + from: this.options.x.from, + to: this.options.x.to + }; + } + resetInitX(fromX: number): void { + this.initFromX = fromX; + } + setX(from: number, to: number, bReset?: boolean): void { + this.options.x.from = from; + this.options.x.to = to; + this.calcuUnitValue(); + } + getY(): {from: number, to: number} { + return { + from: this.options.y.from, + to: this.options.y.to + }; + } + setY(from: number, to: number): void { + this.options.y.from = from; + this.options.y.to = to; + } + getPadding(): {top: number, left: number, right: number, bottom: number} { + return { + top: this.options.padding.top, + left: this.options.padding.left, + right: this.options.padding.right, + bottom: this.options.padding.bottom + }; + } + getBubbleHalfSize(): number { + return this.options.bubbleRadius; + } + getBubbleSize(): number { + return this.options.bubbleRadius * 2; + } + getWidthOfChartSpace(): number { + return this.widthOfChartSpace; + } + getHeightOfChartSpace(): number { + return this.heightOfChartSpace; + } + getPixelPerTime(): number { + return this.pixelPerTime; + } + getTimePerPixel(): number { + return this.timePerPixel; + } + parseXDataToXChart(x: number, plusPadding: boolean): number { + return Math.round( ( ( x - this.options.x.from ) / this.getGapX() ) * this.widthOfChartSpace ) + this.getBubbleHalfSize() + ( plusPadding ? this.options.padding.left : 0 ); + } + parseYDataToYChart(y: number): number { + return Math.round(this.heightOfChartSpace - (((y - this.options.y.from) / this.getGapY()) * this.heightOfChartSpace)) + this.getBubbleHalfSize(); + } + parseZDataToZChart(z: number): number { + return Math.round(((z - this.options.z.from) / this.getGapZ()) * this.getBubbleHalfSize()); + } + getXOfPixel(): number { + return Math.round(this.getGapX() / this.widthOfChartSpace); + } + getYOfPixel(): number { + return Math.round(this.getGapY() / this.heightOfChartSpace); + } + parseMouseXToXData(x: number): number { + return Math.round((x / this.widthOfChartSpace) * this.getGapX()) + this.options.x.from; + } + parseMouseYToYData(y: number): number { + return Math.round(this.options.y.from + ((y / this.heightOfChartSpace) * this.getGapY())); + } + getGapX(): number { + return this.options.x.to - this.options.x.from; + } + getGapY(): number { + return this.options.y.to - this.options.y.from; + } + getGapZ(): number { + return this.options.z.to - this.options.z.from; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-transaction-type-manager.class.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-transaction-type-manager.class.ts new file mode 100644 index 000000000000..6219843cb74e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart-transaction-type-manager.class.ts @@ -0,0 +1,53 @@ +import { ITypeInfo } from './scatter-chart.class'; + +export class ScatterChartTransactionTypeManager { + private dataByIndex: { [key: number]: ITypeInfo } = {}; + private dataByName: { [key: string]: ITypeInfo } = {}; + + constructor(typeInfos: ITypeInfo[]) { + this.initData(typeInfos); + } + private initData(typeInfos: ITypeInfo[]): void { + typeInfos.forEach((typeInfo: ITypeInfo, index: number) => { + const newTypeInfo = { + name: typeInfo.name, + color: typeInfo.color, + order: typeInfo.order, + index: index, + checked: true + }; + this.dataByName[typeInfo.name] = newTypeInfo; + this.dataByIndex[index] = newTypeInfo; + }); + } + getTypeNameList(): string[] { + return Object.keys(this.dataByName); + } + setChecked(name: string, checked: boolean): void { + this.dataByName[name].checked = checked; + } + isCheckedByName(name: string): boolean { + return this.dataByName[name].checked; + } + isCheckedByIndex(index: number): boolean { + return this.dataByIndex[index].checked; + } + getColorByIndex(index: number): string { + return this.dataByIndex[index].color; + } + getColorByName(name: string): string { + return this.dataByName[name].color; + } + getNameByIndex(index: number): string { + return this.dataByIndex[index].name; + } + getCheckedTypeNameList(): string[] { + const list = []; + Object.keys(this.dataByName).forEach((name: string) => { + if (this.dataByName[name].checked) { + list.push(name); + } + }); + return list; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart.class.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart.class.ts new file mode 100644 index 000000000000..9e24b05c21f4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/class/scatter-chart.class.ts @@ -0,0 +1,520 @@ +import * as moment from 'moment-timezone'; +import { Subject, BehaviorSubject, Observable } from 'rxjs'; +import { ScatterChartSizeCoordinateManager } from './scatter-chart-size-coordinate-manager.class'; +import { ScatterChartRendererManager } from './scatter-chart-renderer-manager.class'; +import { ScatterChartDataBlock } from './scatter-chart-data-block.class'; +import { ScatterChartGridRenderer } from './scatter-chart-grid-renderer.class'; +import { ScatterChartAxisRenderer } from './scatter-chart-axis-renderer.class'; +import { ScatterChartTransactionTypeManager } from './scatter-chart-transaction-type-manager.class'; +import { ScatterChartMouseManager } from './scatter-chart-mouse-manager.class'; + +export interface IOptions { + mode: string; + prefix: string; + width: number; + height: number; + bubbleRadius: number; + padding: { + top: number; + left: number; + right: number; + bottom: number; + }; + axisLabelStyle: string; + axisColor: string; + lineColor: string; + x: { + from: number; + to: number; + }; + y: { + from: number; + to: number; + }; + z: { + from: number; + to: number; + }; + ticks: { + x: number; + y: number; + }; + tickLength: { + x: number; + y: number; + }; + gridAxisStyle: { + lineDash: number[]; + lineWidth: number; + globalAlpha: number; + strokeStyle: string + }; + axisUnit: { + x: string; + y: string; + }; + showMouseGuideLine: boolean; + animationDuration: number; + timezone: string; + dateFormat: string[]; +} +export interface ITypeInfo { + index?: number; + name?: string; + color: string; + order: number; + checked?: boolean; +} + +export class ScatterChart { + static MODE = { + REALTIME: 'realtime', + STATIC: 'static' + }; + private options: IOptions; + private typeManager: ScatterChartTransactionTypeManager; + private gridRenderer: ScatterChartGridRenderer; + private axisRenderer: ScatterChartAxisRenderer; + private coordinateManager: ScatterChartSizeCoordinateManager; + private rendererManager: ScatterChartRendererManager; + private mouseManager: ScatterChartMouseManager; + private dataBlocks: ScatterChartDataBlock[] = []; + private agentList: string[] = []; + private selectedAgent = ''; + + private downloadElement: HTMLElement; + + private outTransactionCount: BehaviorSubject<{ [key: string]: number }>; + private outSelect: Subject = new Subject(); + private outError: Subject = new Subject(); + private outChangeRangeX: Subject<{from: number, to: number}> = new Subject(); + onSelect$: Observable; + onError$: Observable; + onChangeRangeX$: Observable<{from: number, to: number}>; + onChangeTransactionCount$: Observable<{ [key: string]: number }>; + + constructor( + private mode: string, + private element: any, + private fromX: number, + private toX: number, + private fromY: number, + private toY: number, + typeInfos: ITypeInfo[], + private application: string, + agent: string, + private width: number, + private height: number, + private timezone: string, + private dateFormat: string[] + ) { + this.downloadElement = document.createElement('a'); + this.selectedAgent = agent; + this.setOptions(); + this.initManagers(typeInfos); + + this.onSelect$ = this.outSelect.asObservable(); + this.onError$ = this.outError.asObservable(); + this.onChangeRangeX$ = this.outChangeRangeX.asObservable(); + this.outTransactionCount = new BehaviorSubject(this.getTransactionCount(true)); + this.onChangeTransactionCount$ = this.outTransactionCount.asObservable(); + } + private isAllowedAgent(agent: string): boolean { + return this.selectedAgent === '' || this.selectedAgent === agent; + } + private getTransactionCount(isInit: boolean) { + const count: { [key: string]: number } = { }; + const xRange = this.coordinateManager.getX(); + if (isInit) { + this.typeManager.getTypeNameList().forEach((typeName: string) => { + count[typeName] = 0; + }); + } else { + this.typeManager.getTypeNameList().forEach((typeName: string) => { + count[typeName] = 0; + this.dataBlocks.forEach((dataBlock: ScatterChartDataBlock) => { + const dataBlockXRange = dataBlock.getXRange(); + if (dataBlockXRange.to >= xRange.from) { + const agentList = dataBlock.getAgentList(); + agentList.forEach((agentName: string) => { + if (this.isAllowedAgent(agentName)) { + count[typeName] += dataBlock.getCount(agentName, typeName, xRange.from, xRange.to); + } + }); + } + }); + }); + } + return count; + } + private setOptions() { + this.options = { + mode: this.mode, + prefix: 'scatter-chart-' + (Math.random() * 10000), + width: this.width, + height: this.height, + bubbleRadius: 3, + padding: { + top: 20, + left: 60, + right: 40, + bottom: 40 + }, + axisLabelStyle: 'font-size:10px; line-height: 12px; padding-top: 3px', + axisColor: '#000', + lineColor: '#3D3D3D', + x: { + from: this.fromX, + to: this.toX + }, + y: { + from: this.fromY, + to: this.toY + }, + z: { + from: 0, + to: 5 + }, + ticks: { + x: 5, + y: 5 + }, + tickLength: { + x: 10, + y: 10 + }, + gridAxisStyle: { + lineDash: [1, 0], + lineWidth: 1, + globalAlpha: 1, + strokeStyle : '#e3e3e3' + }, + axisUnit: { + x: '', + y: '(ms)' + }, + showMouseGuideLine: true, + animationDuration: 300, + timezone: this.timezone, + dateFormat: this.dateFormat + }; + } + private initManagers(typeInfos: ITypeInfo[]): void { + this.typeManager = new ScatterChartTransactionTypeManager(typeInfos); + this.coordinateManager = new ScatterChartSizeCoordinateManager(this.options); + this.gridRenderer = new ScatterChartGridRenderer(this.options, this.coordinateManager, this.element); + this.axisRenderer = new ScatterChartAxisRenderer(this.options, this.coordinateManager, this.element); + this.rendererManager = new ScatterChartRendererManager(this.options, this.coordinateManager, this.element, this.typeManager); + this.mouseManager = new ScatterChartMouseManager(this.options, this.coordinateManager, this.element); + this.mouseManager.onDragArea$.subscribe((area: any) => { + const fromX = this.coordinateManager.parseMouseXToXData(area.x.from); + const toX = this.coordinateManager.parseMouseXToXData(area.x.to); + const fromY = this.coordinateManager.parseMouseYToYData(area.y.from); + const toY = this.coordinateManager.parseMouseYToYData(area.y.to); + if (this.hasDataByXY(fromX, toX, fromY, toY)) { + this.outSelect.next({ + x: { + from: fromX, + to: toX + }, + y: { + from: fromY, + to: toY + }, + drag: area, + type: this.typeManager.getCheckedTypeNameList(), + agent: this.selectedAgent + }); + } + }); + } + changeShowType(typeInfo: {name: string, checked: boolean}): void { + this.typeManager.setChecked(typeInfo.name, typeInfo.checked); + this.rendererManager.setTypeView(this.selectedAgent, typeInfo.name, typeInfo.checked); + } + private redrawDataBlock(): void { + this.dataBlocks.forEach((dataBlock: ScatterChartDataBlock) => { + this.drawDataBlock(dataBlock); + }); + } + reset(application: string, agent: string, fromX: number, toX: number, mode: string): void { + this.fromX = fromX; + this.toX = toX; + this.options.mode = this.mode = mode; + this.coordinateManager.resetInitX(fromX); + this.coordinateManager.setX(fromX, toX); + this.application = application; + this.selectedAgent = agent; + + this.dataBlocks = []; + this.agentList = []; + this.axisRenderer.reset(); + this.rendererManager.reset(); + this.outTransactionCount.next(this.getTransactionCount(true)); + } + addAgent(agentList: string[]): void { + agentList.forEach((agentName: string) => { + if (this.agentList.lastIndexOf(agentName) === -1) { + this.agentList.push(agentName); + } + }); + this.agentList.sort(); + } + addData(dataBlock: ScatterChartDataBlock, nextRequestTime?: number): void { + const xRange = this.coordinateManager.getX(); + const dataBlockRange = dataBlock.getXRange(); + if (this.options.mode === ScatterChart.MODE.STATIC && dataBlockRange.from >= xRange.to) { + return; + } + this.addAgent(dataBlock.getAgentList()); + this.dataBlocks.push(dataBlock); + // if (this._bPause === true) return; + this.drawDataBlock(dataBlock); + if (this.options.mode !== ScatterChart.MODE.STATIC) { + this.moveChart(dataBlock, nextRequestTime || 300); + this.removeBubble(); + } + this.changeSelectedAgent(this.selectedAgent); + this.outTransactionCount.next(this.getTransactionCount(false)); + } + private drawDataBlock(dataBlock: ScatterChartDataBlock): void { + const prefix = this.options.prefix; + this.rendererManager.makeDataCanvas(dataBlock, dataBlock.getAgentList()); + this.agentList.forEach((agentName: string) => { + for (let i = 0, nLen = dataBlock.countByAgent(agentName) ; i < nLen ; i++) { + const data = dataBlock.getDataByAgentAndIndex(agentName, i); + const groupCount = dataBlock.getGroupCount(data); + if (groupCount !== 0) { + const typeIndex = dataBlock.getTypeIndex(data); + const typeName = this.typeManager.getNameByIndex(typeIndex); + const typeColor = this.typeManager.getColorByIndex(typeIndex); + this.rendererManager.drawTransaction(`${agentName}-${prefix}-${typeName}`, typeColor, data); + } + } + }); + } + private moveChart(dataBlock: ScatterChartDataBlock, nextRequestTime: number): void { + const xRange = this.coordinateManager.getX(); + const dataBlockXRange = dataBlock.getXRange(); + const duration = this.options.animationDuration; + + if (dataBlockXRange.from >= xRange.to) { + const moveXTime = dataBlockXRange.to - xRange.to; + const moveXValue = moveXTime * this.coordinateManager.getPixelPerTime(); + this.coordinateManager.setX(xRange.from + moveXTime, xRange.to + moveXTime); + this.axisRenderer.updateAxisValue(true, duration * 2); + this.rendererManager.moveChart(Math.floor(moveXValue), nextRequestTime < duration ? 0 : duration); + this.outChangeRangeX.next({ + from: xRange.from + moveXTime, + to: xRange.to + moveXTime + }); + } + } + private removeBubble() { + const fromX = this.coordinateManager.getX().from; + for (let i = 0 ; i < this.dataBlocks.length ; i++) { + const dataBlock = this.dataBlocks[i]; + if (dataBlock.getXRange().to < fromX) { + this.dataBlocks.shift(); + i--; + } else { + break; + } + } + } + getDataByRange(fromX: number, toX: number, fromY: number, toY: number, selectedAgent: string, selectedType: string[]): any[] { + fromX = +fromX; + toX = +toX; + fromY = +fromY; + toY = +toY; + + const data = []; + const yRange = this.coordinateManager.getY(); + const typeChecker = {}; + selectedType.forEach((type: string) => { + typeChecker[type] = true; + }); + for (let i = 0 ; i < this.dataBlocks.length ; i++) { + const dataBlock = this.dataBlocks[i]; + for (let j = 0, len = dataBlock.getTotalCount(); j < len ; j++) { + const xRange = dataBlock.getXRange(); + if ( xRange.to < fromX || xRange.from > toX ) { + continue; + } + const transactionData = dataBlock.getDataByIndex(j); + const agentName = dataBlock.getAgentName(transactionData); + if (selectedAgent === '' || selectedAgent === agentName) { + const x = dataBlock.getX(transactionData); + const y = dataBlock.getY(transactionData); + + if (this.isInRange(fromX, toX, x)) { + if (this.isInRange(fromY, toY, y) || (y > toY && toY <= yRange.to)) { + if (typeChecker[this.typeManager.getNameByIndex(dataBlock.getTypeIndex(transactionData))] === true) { + data.push([dataBlock.getTransactionID(transactionData), x, y]); + } + } + } + } + + } + } + return data; + } + hasDataByXY(fromX: number, toX: number, fromY: number, toY: number): boolean { + fromX = +fromX; + toX = +toX; + fromY = +fromY; + toY = +toY; + + const yRange = this.coordinateManager.getY(); + for (let i = 0 ; i < this.dataBlocks.length ; i++) { + const dataBlock = this.dataBlocks[i]; + for (let j = 0, len = dataBlock.getTotalCount() ; j < len ; j++) { + const transactionData = dataBlock.getDataByIndex(j); + const agentName = dataBlock.getAgentName(transactionData); + if (this.isAllowedAgent(agentName)) { + const x = dataBlock.getX(transactionData); + const y = dataBlock.getY(transactionData); + if (this.isInRange(fromX, toX, x)) { + if (this.typeManager.isCheckedByIndex(dataBlock.getTypeIndex(transactionData))) { + if (this.isInRange(fromY, toY, y) || y > toY && toY <= yRange.to) { + return true; + } + } + } + } + } + } + return false; + } + private isInRange(from: number, to: number, value: number): boolean { + return value >= from && value <= to; + } + // //destroy = function() { + // // var self = this; + // // this._unbindAllEvents(); + // // //this._empty(); + // // $.each(this, function (property, content) { + // // delete self[property]; + // // }); + // // this._bDestroied = true; + // // + // //}; + // //_empty = function() { + // // this._$elContainer.empty(); + // //}; + // //_unbindAllEvents = function() { + // // // this is for drag-selecting. it should be unbinded. + // // jQuery(document).unbind('mousemove').unbind('mouseup'); + // //}; + downloadChartAsImage(extension: string): void { + const xRange = this.coordinateManager.getX(); + const titleArea = 60; + const countArea = 40; + const width = this.coordinateManager.getWidth(); + const height = this.coordinateManager.getHeight() + titleArea + countArea; + + const elementDownloadCanvas = document.createElement('canvas'); + elementDownloadCanvas.setAttribute('width', width + 'px'); + elementDownloadCanvas.setAttribute('height', height + 'px'); + + const ctxDownloadCanvas = elementDownloadCanvas.getContext('2d'); + ctxDownloadCanvas.fillStyle = '#FFFFFF'; + ctxDownloadCanvas.fillRect(0, 0, width, height); + + this.gridRenderer.drawToCanvas(ctxDownloadCanvas, titleArea); + this.axisRenderer.drawToCanvas(ctxDownloadCanvas, titleArea); + this.rendererManager.drawToCanvas(ctxDownloadCanvas, titleArea); + + // draw Title + ctxDownloadCanvas.textAlign = 'left'; + ctxDownloadCanvas.font = '24px monospace'; + ctxDownloadCanvas.fillText(this.application, 10, 10); + ctxDownloadCanvas.font = '14px monospace'; + ctxDownloadCanvas.fillText(moment(xRange.from).tz(this.timezone).format(this.dateFormat[0] + ' ' + this.dateFormat[1]) + ' ~ ' + moment(xRange.to).tz(this.timezone).format(this.dateFormat[0] + ' ' + this.dateFormat[1]), 10, 40); + + // draw Count By Type + this.typeManager.getTypeNameList().forEach((typeName: string, index: number) => { + const color = this.typeManager.getColorByName(typeName); + const check = this.typeManager.isCheckedByName(typeName); + let sumType = 0; + this.dataBlocks.forEach((dataBlock: ScatterChartDataBlock) => { + sumType += dataBlock.getCount(this.selectedAgent, typeName); + }); + ctxDownloadCanvas.textAlign = 'left'; + ctxDownloadCanvas.font = '16px monospace'; + if (check) { + ctxDownloadCanvas.fillStyle = color; + ctxDownloadCanvas.fillRect((index * 200) + 10, height - countArea + 10, 20, 20); + } else { + ctxDownloadCanvas.lineWidth = 4; + ctxDownloadCanvas.strokeStyle = color; + ctxDownloadCanvas.strokeRect((index * 200) + 10, height - countArea + 10, 20, 20); + } + ctxDownloadCanvas.fillStyle = '#000'; + ctxDownloadCanvas.fillText(`${typeName} : ${sumType}`, (index * 200) + 40, height - countArea + 10); + }); + + this.downloadElement.setAttribute('href', elementDownloadCanvas.toDataURL('image/' + extension)); + this.downloadElement.setAttribute('download', `Pinpoint_Scatter_Chart[${moment(xRange.from).tz(this.timezone).format(this.dateFormat[0] + '_' + this.dateFormat[1])}~${moment(xRange.to).tz(this.timezone).format(this.dateFormat[0] + '_' + this.dateFormat[1])}].png`); + this.downloadElement.dispatchEvent(new MouseEvent('click')); + } + // _hideServerError() { + // this._$elContainer.css({ + // 'backgroundImage': 'none' + // }); + // } + // _showServerError() { + // this._$elContainer.css({ + // 'backgroundImage': 'url(' + this.option('errorImage') + ')', + // 'backgroundRepeat': 'no-repeat', + // 'backgroundPosition': '88% 21%', + // 'backgroundSize': '30px 30px' + // }); + // } + redraw() { + this.rendererManager.clear(); + this.axisRenderer.updateAxisValue(); + this.redrawDataBlock(); + } + // abort() { + // this._bPause = true; + // if (this._oDataLoadManager) { + // this._oDataLoadManager.abort(); + // } + // } + changeYRange(newYRange: {from: number, to: number}): void { + this.fromY = +newYRange.from; + this.toY = +newYRange.to; + this.coordinateManager.setY(+newYRange.from, +newYRange.to); + this.redraw(); + } + changeSelectedAgent(agentName: string): void { + this.selectedAgent = agentName; + this.rendererManager.showSelectedAgent(agentName); + this.outTransactionCount.next(this.getTransactionCount(false)); + } + getCurrentAgent(): string { + return this.selectedAgent; + } + isEmpty(): boolean { + if (this.options.mode === ScatterChart.MODE.STATIC) { + let empty = true; + this.dataBlocks.forEach((dataBlock: ScatterChartDataBlock) => { + empty = empty && dataBlock.isEmpty(); + }); + return empty; + } else { + return false; + } + } + getTypeManager(): ScatterChartTransactionTypeManager { + return this.typeManager; + } + setTimezone(timezone: string): void { + this.options.timezone = this.timezone = timezone; + } + setDateFormat(dateFormat: string[]): void { + this.options.dateFormat = this.dateFormat = dateFormat; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/index.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/index.ts new file mode 100644 index 000000000000..1e78ea568c2d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/index.ts @@ -0,0 +1,45 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { ScatterChartSettingPopupComponent } from './scatter-chart-setting-popup.component'; +import { ScatterChartOptionsComponent } from './scatter-chart-options.component'; +import { ScatterChartStateViewComponent } from './scatter-chart-state-view.component'; +import { ScatterChartComponent } from './scatter-chart.component'; +import { ScatterChartContainerComponent } from './scatter-chart-container.component'; +import { ScatterChartForFilteredMapSideBarContainerComponent } from './scatter-chart-for-filtered-map-side-bar-container.component'; +import { ScatterChartForFilteredMapInfoPerServerContainerComponent } from './scatter-chart-for-filtered-map-info-per-server-container.component'; +import { ScatterChartForInfoPerServerContainerComponent } from './scatter-chart-for-info-per-server-container.component'; +import { ScatterChartForFullScreenModeContainerComponent } from './scatter-chart-for-full-screen-mode-container.component'; +import { ScatterChartInteractionService } from './scatter-chart-interaction.service'; +import { ScatterChartDataService } from './scatter-chart-data.service'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + ScatterChartSettingPopupComponent, + ScatterChartOptionsComponent, + ScatterChartStateViewComponent, + ScatterChartComponent, + ScatterChartContainerComponent, + ScatterChartForFilteredMapSideBarContainerComponent, + ScatterChartForFilteredMapInfoPerServerContainerComponent, + ScatterChartForInfoPerServerContainerComponent, + ScatterChartForFullScreenModeContainerComponent + ], + imports: [ + CommonModule, + HelpViewerPopupModule + ], + exports: [ + ScatterChartContainerComponent, + ScatterChartForFilteredMapSideBarContainerComponent, + ScatterChartForFilteredMapInfoPerServerContainerComponent, + ScatterChartForInfoPerServerContainerComponent, + ScatterChartForFullScreenModeContainerComponent + ], + providers: [ + ScatterChartInteractionService, + ScatterChartDataService + ] +}) +export class ScatterChartModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-container.component.css b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-container.component.css new file mode 100644 index 000000000000..5297343d37ac --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-container.component.css @@ -0,0 +1,19 @@ +:host { + position: relative; +} +.l-chart-item { + margin: 15px 0; + height: 285px; + position: relative; + border-top: none; +} +.l-chart-item.l-popup-on:before { + display: block; + z-index: 490; + position: absolute; + content: ''; + width: 100%; + height: 100%; + background: #FFF; + opacity: 0.8; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-container.component.html b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-container.component.html new file mode 100644 index 000000000000..63fe24300af9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-container.component.html @@ -0,0 +1,42 @@ +
+ + + + +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-container.component.ts new file mode 100644 index 000000000000..fd1f6ce17b33 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-container.component.ts @@ -0,0 +1,291 @@ +import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core'; +import { combineLatest, of, Subject } from 'rxjs'; +import { takeUntil, filter, delay } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { + WebAppSettingDataService, + NewUrlStateNotificationService, + UrlRouteManagerService, + StoreHelperService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { EndTime } from 'app/core/models'; +import { ScatterChartDataService } from './scatter-chart-data.service'; +import { ScatterChart } from './class/scatter-chart.class'; +import { ScatterChartComponent } from './scatter-chart.component'; +import { ScatterChartInteractionService } from './scatter-chart-interaction.service'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-scatter-chart-container', + templateUrl: './scatter-chart-container.component.html', + styleUrls: ['./scatter-chart-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ScatterChartContainerComponent implements OnInit, OnDestroy { + instanceKey = 'side-bar'; + addWindow = true; + i18nText: { [key: string]: string } = { + NO_DATA: '' + }; + currentRange: { from: number, to: number } = { + from : 0, + to: 0 + }; + selectedTarget: ISelectedTarget; + selectedApplication: string; + unsubscribe: Subject = new Subject(); + hideSettingPopup = true; + selectedAgent: string; + typeInfo = [{ + name: 'failed', + color: '#E95459', + order: 10 + }, { + name: 'success', + color: '#34B994', + order: 20 + }]; + typeCount: object; + width = 460; + height = 230; + min: number; + max: number; + fromX: number; + toX: number; + fromY: number; + toY: number; + application: string; + scatterChartMode: string; + timezone: string; + dateFormat: string[]; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private scatterChartDataService: ScatterChartDataService, + private scatterChartInteractionService: ScatterChartInteractionService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.setScatterY(); + combineLatest( + this.translateService.get('COMMON.NO_DATA') + ).subscribe((i18n: Array) => { + this.i18nText = { + [ScatterChartComponent.I18NTEXT.NO_DATA]: i18n[0] + }; + }); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.APPLICATION); + }) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.scatterChartMode = urlService.isRealTimeMode() ? ScatterChart.MODE.REALTIME : ScatterChart.MODE.STATIC; + this.scatterChartDataService.setCurrentMode(this.scatterChartMode); + this.application = urlService.getPathValue(UrlPathId.APPLICATION).getKeyStr(); + this.selectedAgent = ''; + this.currentRange.from = this.fromX = urlService.getStartTimeToNumber(); + this.currentRange.to = this.toX = urlService.getEndTimeToNumber(); + this.changeDetectorRef.detectChanges(); + }); + this.scatterChartDataService.outScatterData$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((scatterData: IScatterData) => { + if (this.scatterChartMode === ScatterChart.MODE.STATIC) { + this.scatterChartInteractionService.addChartData(this.instanceKey, scatterData); + if (scatterData.complete === false) { + this.scatterChartDataService.loadData( + this.selectedApplication.split('^')[0], + this.fromX, + scatterData.resultFrom - 1, + this.getGroupUnitX(), + this.getGroupUnitY(), + false + ); + } + } else if (this.scatterChartMode === ScatterChart.MODE.REALTIME) { + if (scatterData.reset === true) { + this.fromX = scatterData.currentServerTime - this.webAppSettingDataService.getSystemDefaultPeriod().getMiliSeconds(); + this.toX = scatterData.currentServerTime; + this.scatterChartInteractionService.reset(this.instanceKey, this.selectedApplication, this.selectedAgent, this.fromX, this.toX, this.scatterChartMode); + of(1).pipe(delay(1000)).subscribe((useless: number) => { + this.getScatterData(); + }); + } else { + this.scatterChartInteractionService.addChartData(this.instanceKey, scatterData); + } + } + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private setScatterY() { + const scatterYData = this.webAppSettingDataService.getScatterY(this.instanceKey); + this.fromY = scatterYData.min; + this.toY = scatterYData.max; + } + private connectStore(): void { + this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => { + this.timezone = timezone; + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getDateFormatArray(this.unsubscribe, 3, 4).subscribe((format: string[]) => { + this.dateFormat = format; + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getAgentSelection(this.unsubscribe).subscribe((agent: string) => { + if (this.selectedAgent !== agent) { + this.selectedAgent = agent; + this.scatterChartInteractionService.changeAgent(this.instanceKey, agent); + } + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.selectedTarget = target; + if (this.isHide() === false) { + this.selectedAgent = ''; + this.selectedApplication = this.selectedTarget.node[0]; + this.scatterChartInteractionService.reset(this.instanceKey, this.selectedApplication, this.selectedAgent, this.fromX, this.toX, this.scatterChartMode); + this.getScatterData(); + } + this.changeDetectorRef.detectChanges(); + }); + } + getScatterData(): void { + if (this.scatterChartMode === ScatterChart.MODE.STATIC) { + this.scatterChartDataService.loadData( + this.selectedApplication.split('^')[0], + this.fromX, + this.toX, + this.getGroupUnitX(), + this.getGroupUnitY() + ); + } else { + this.scatterChartDataService.loadRealTimeData( + this.selectedApplication.split('^')[0], + this.fromX, + this.toX, + this.getGroupUnitX(), + this.getGroupUnitY() + ); + } + } + getGroupUnitX(): number { + return Math.round((this.toX - this.fromX) / this.width); + } + getGroupUnitY(): number { + return Math.round((this.toY - this.fromY) / this.height); + } + isHide(): boolean { + if (this.selectedTarget) { + return !(this.selectedTarget.isNode && this.selectedTarget.isWAS && this.selectedTarget.isMerged === false); + } + return true; + } + checkClass(): string { + return this.hideSettingPopup ? '' : 'l-popup-on'; + } + isHideSettingPopup(): boolean { + return this.hideSettingPopup; + } + onApplySetting(params: any): void { + this.fromY = params.min; + this.toY = params.max; + this.scatterChartInteractionService.changeYRange({ + instanceKey: this.instanceKey, + from: params.min, + to: params.max + }); + this.hideSettingPopup = true; + this.webAppSettingDataService.setScatterY(this.instanceKey, { min: params.min, max: params.max }); + } + onCancelSetting(): void { + this.hideSettingPopup = true; + } + onShowSetting(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_SCATTER_SETTING); + this.hideSettingPopup = false; + } + onDownload(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.DOWNLOAD_SCATTER); + this.scatterChartInteractionService.downloadChart(this.instanceKey); + } + onOpenScatterPage(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.GO_TO_FULL_SCREEN_SCATTER); + if (this.scatterChartMode === ScatterChart.MODE.STATIC) { + this.urlRouteManagerService.openPage([ + UrlPath.SCATTER_FULL_SCREEN_MODE, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + this.selectedAgent + ]); + } else { + this.urlRouteManagerService.openPage([ + UrlPath.SCATTER_FULL_SCREEN_MODE, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + UrlPathId.REAL_TIME + ]); + } + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.SCATTER); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.SCATTER, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } + onChangedSelectType(params: {instanceKey: string, name: string, checked: boolean}): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CHANGE_SCATTER_CHART_STATE, `${params.name}: ${params.checked ? `on` : `off`}`); + this.scatterChartInteractionService.changeViewType(params); + } + onChangeTransactionCount(params: object): void { + this.typeCount = params; + } + onChangeRangeX(params: { from: number, to: number }): void { + this.currentRange.from = params.from; + this.currentRange.to = params.to; + this.storeHelperService.dispatch(new Actions.UpdateRealTimeScatterChartXRange(params)); + } + onSelectArea(params: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_TRANSACTION_LIST); + if (this.newUrlStateNotificationService.isRealTimeMode()) { + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_LIST, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.webAppSettingDataService.getSystemDefaultPeriod().getValueWithTime(), + EndTime.newByNumber(this.currentRange.to).getEndTime(), + ], `${this.selectedApplication}|${params.x.from}|${params.x.to}|${params.y.from}|${params.y.to}|${this.selectedAgent}|${params.type.join(',')}`); + } else { + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_LIST, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ], `${this.selectedApplication}|${params.x.from}|${params.x.to}|${params.y.from}|${params.y.to}|${this.selectedAgent}|${params.type.join(',')}`); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-data.service.ts new file mode 100644 index 000000000000..e201e7b980b9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-data.service.ts @@ -0,0 +1,144 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of, Subject } from 'rxjs'; +import { switchMap, delay } from 'rxjs/operators'; +import { ScatterChart } from 'app/core/components/scatter-chart/class/scatter-chart.class'; + +interface IScatterRequest { + application: string; + fromX: number; + toX: number; + groupUnitX: number; + groupUnitY: number; + backwardDirection: boolean; +} + +@Injectable() +export class ScatterChartDataService { + private url = 'getScatterData.pinpoint'; + private realtime = { + interval: 2000, + // interval: 5000, + resetTimeGap: 20000 + }; + private currentMode: string; + private lastScatterData: IScatterData[] = []; + private requestTime: number; + private application: string; + private groupUnitX: number; + private groupUnitY: number; + private innerDataRequest = new Subject(); + private innerDataRequest$: Observable; + private outScatterData = new Subject(); + outScatterData$: Observable; + constructor(private http: HttpClient) { + this.innerDataRequest$ = this.innerDataRequest.asObservable(); + this.outScatterData$ = this.outScatterData.asObservable(); + this.connectDataRequest(); + } + private connectDataRequest(): void { + this.innerDataRequest$.pipe( + switchMap((params: IScatterRequest) => { + return this.http.get(this.url, this.makeRequestOptionsArgs( + params.application, + params.fromX, + params.toX, + params.groupUnitX, + params.groupUnitY, + params.backwardDirection) + ); + }) + ).subscribe((scatterData: IScatterData) => { + if ( this.currentMode === ScatterChart.MODE.REALTIME) { + this.subscribeRealTimeRequest(scatterData); + } else { + this.subscribeStaticRequest(scatterData); + } + }); + } + private getData(fromX: number, toX: number, backwardDirection: boolean): void { + this.requestTime = Date.now(); + return this.innerDataRequest.next({ + application: this.application, + fromX: fromX, + toX: toX, + groupUnitX: this.groupUnitX, + groupUnitY: this.groupUnitY, + backwardDirection: backwardDirection + }); + } + loadData(application: string, fromX: number, toX: number, groupUnitX: number, groupUnitY: number, initLastData?: boolean): void { + this.application = application; + this.groupUnitX = groupUnitX; + this.groupUnitY = groupUnitY; + this.lastScatterData = initLastData === false ? this.lastScatterData : []; + this.getData(fromX, toX, true); + } + private subscribeStaticRequest(scatterData: IScatterData): void { + this.lastScatterData.push(scatterData); + this.outScatterData.next(scatterData); + } + loadLastData(): IScatterData[] { + return this.lastScatterData; + } + loadRealTimeData(application: string, fromX: number, toX: number, groupUnitX: number, groupUnitY: number): void { + this.application = application; + this.groupUnitX = groupUnitX; + this.groupUnitY = groupUnitY; + this.getData(fromX, toX, false); + } + setCurrentMode(mode: string): void { + this.currentMode = mode; + } + private subscribeRealTimeRequest(scatterData: IScatterData): void { + const roundTripTime = Date.now() - this.requestTime; + let fromNext = 0; + let toNext = 0; + let delayTime = this.realtime.interval - roundTripTime; + + if (scatterData.complete) { + fromNext = scatterData.to; + toNext = fromNext + this.realtime.interval; + if (delayTime > 0) { + const timeGapInterServerAndClient = scatterData.currentServerTime - toNext; + if (timeGapInterServerAndClient >= delayTime) { + if ( timeGapInterServerAndClient >= this.realtime.interval ) { + delayTime = 0; + } else { + delayTime = Math.min(Math.abs(timeGapInterServerAndClient), delayTime); + } + } + } else { + delayTime = 0; + } + } else { + // TODO: 처리해야 함. + fromNext = scatterData.from; + toNext = scatterData.resultFrom; + delayTime = 0; + } + if (scatterData.currentServerTime - toNext >= this.realtime.resetTimeGap) { + scatterData.reset = true; + this.outScatterData.next(scatterData); + } else { + this.outScatterData.next(scatterData); + of(1).pipe(delay(delayTime)).subscribe((useless: number) => { + this.getData(fromNext, toNext, false); + }); + } + } + private makeRequestOptionsArgs(application: string, fromX: number, toX: number, groupUnitX: number, groupUnitY: number, backwardDirection: boolean): object { + return { + params: { + application: application, + from: fromX, + to: toX, + limit: 5000, + filter: '', + xGroupUnit: groupUnitX, + yGroupUnit: groupUnitY, + backwardDirection: backwardDirection + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-download-plugin.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-download-plugin.ts new file mode 100644 index 000000000000..b7923e727f11 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-download-plugin.ts @@ -0,0 +1,52 @@ +export class ScatterChartDownloadPlugin { + constructor(imageData) { + // this._init(imageData); + } + // _init(imageData) { + // this._featureImage = imageData; + // this._aCallback = []; + // this._bDisabled = false; + // } + // initElement($elParent, $elPlugin, option) { + // this._bDisabled = option['realtime']; + // this._$element = $('
').css({ + // 'cursor': 'pointer', + // 'padding': '4px 0px 4px 20px' + // }).append( + // $('').attr({ + // 'alt': 'Download Scatter Chart', + // 'href': '', + // 'title': 'Download Scatter Chart', + // 'download': '' + // }).append( + // $('').attr({ + // 'src': this._featureImage + // }).css({ + // 'opacity': this._bDisabled ? 0.2 : 1 + // }) + // ) + // ).appendTo($elPlugin); + // return this; + // } + // initEvent(oChart) { + // var self = this; + // this._$element.find('a').on('click', function (event) { + // if (self._bDisabled) { + // event.preventDefault(); + // return; + // } + // $(this).attr({ + // 'href': oChart.getChartAsImage('png'), + // 'download': 'Pinpoint_Scatter_Chart[' + moment(oChart.option('minX')).format('YYYYMMDD_HHmm') + '~' + moment(oChart.option('maxX')).format('YYYYMMDD_HHmm') + ']' + // }); + // $.each(self._aCallback, function (index, fn) { + // fn(oChart); + // }); + // }); + // return this; + // } + // addCallback(fn) { + // this._aCallback.push(fn); + // return this; + // } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-info-per-server-container.component.css b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-info-per-server-container.component.css new file mode 100644 index 000000000000..5297343d37ac --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-info-per-server-container.component.css @@ -0,0 +1,19 @@ +:host { + position: relative; +} +.l-chart-item { + margin: 15px 0; + height: 285px; + position: relative; + border-top: none; +} +.l-chart-item.l-popup-on:before { + display: block; + z-index: 490; + position: absolute; + content: ''; + width: 100%; + height: 100%; + background: #FFF; + opacity: 0.8; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-info-per-server-container.component.html b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-info-per-server-container.component.html new file mode 100644 index 000000000000..63fe24300af9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-info-per-server-container.component.html @@ -0,0 +1,42 @@ +
+ + + + +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-info-per-server-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-info-per-server-container.component.ts new file mode 100644 index 000000000000..9c1e71c9cc8e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-info-per-server-container.component.ts @@ -0,0 +1,227 @@ +import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, AfterViewInit, OnDestroy } from '@angular/core'; +import { combineLatest, Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { + WebAppSettingDataService, + NewUrlStateNotificationService, + UrlRouteManagerService, + StoreHelperService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ScatterChartDataService } from './scatter-chart-data.service'; +import { ScatterChart } from './class/scatter-chart.class'; +import { ScatterChartComponent } from './scatter-chart.component'; +import { ScatterChartInteractionService } from './scatter-chart-interaction.service'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-scatter-chart-for-filtered-map-info-per-server-container', + templateUrl: './scatter-chart-for-filtered-map-info-per-server-container.component.html', + styleUrls: ['./scatter-chart-for-filtered-map-info-per-server-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ScatterChartForFilteredMapInfoPerServerContainerComponent implements OnInit, AfterViewInit, OnDestroy { + instanceKey = 'filtered-map-info-per-server'; + addWindow = false; + i18nText: { [key: string]: string } = { + NO_DATA: '' + }; + isChangedTarget: boolean; + selectedTarget: ISelectedTarget; + selectedApplication: string; + unsubscribe: Subject = new Subject(); + hideSettingPopup = true; + selectedAgent: string; + typeInfo = [{ + name: 'failed', + color: '#E95459', + order: 10 + }, { + name: 'success', + color: '#34B994', + order: 20 + }]; + typeCount: object; + width = 460; + height = 230; + min: number; + max: number; + fromX: number; + toX: number; + fromY: number; + toY: number; + application: string; + scatterChartMode: string; + scatterChartDataOfAllNode: any[] = []; + timezone: string; + dateFormat: string[]; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private scatterChartDataService: ScatterChartDataService, + private scatterChartInteractionService: ScatterChartInteractionService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.setScatterY(); + combineLatest( + this.translateService.get('COMMON.NO_DATA') + ).subscribe((i18n: string[]) => { + this.i18nText = { + [ScatterChartComponent.I18NTEXT.NO_DATA]: i18n[0] + }; + }); + this.connectStore(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.APPLICATION); + }) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.scatterChartMode = urlService.isRealTimeMode() ? ScatterChart.MODE.REALTIME : ScatterChart.MODE.STATIC; + this.scatterChartDataService.setCurrentMode(this.scatterChartMode); + this.application = urlService.getPathValue(UrlPathId.APPLICATION).getKeyStr(); + this.selectedAgent = ''; + this.fromX = urlService.getStartTimeToNumber(); + this.toX = urlService.getEndTimeToNumber(); + this.changeDetectorRef.detectChanges(); + }); + } + ngAfterViewInit() { + this.storeHelperService.getInfoPerServerState(this.unsubscribe).subscribe((visibleState: boolean) => { + if (visibleState && this.isChangedTarget) { + this.scatterChartDataOfAllNode.forEach((scatterData: any) => { + this.scatterChartInteractionService.addChartData(this.instanceKey, scatterData[this.selectedApplication]); + }); + this.isChangedTarget = false; + } + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private setScatterY() { + const scatterYData = this.webAppSettingDataService.getScatterY(this.instanceKey); + this.fromY = scatterYData.min; + this.toY = scatterYData.max; + } + private connectStore(): void { + this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => { + this.timezone = timezone; + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getDateFormatArray(this.unsubscribe, 3, 4).subscribe((format: string[]) => { + this.dateFormat = format; + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getAgentSelectionForServerList(this.unsubscribe).pipe( + filter((chartData: IAgentSelection) => { + return (chartData && chartData.agent) ? true : false; + }) + ).subscribe((chartData: IAgentSelection) => { + this.selectedAgent = chartData.agent; + this.changeDetectorRef.detectChanges(); + this.scatterChartInteractionService.changeAgent(this.instanceKey, chartData.agent); + }); + this.storeHelperService.getScatterChartData(this.unsubscribe).subscribe((scatterChartData: IScatterData[]) => { + this.scatterChartDataOfAllNode = scatterChartData; + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.isChangedTarget = true; + this.selectedTarget = target; + this.selectedAgent = ''; + this.selectedApplication = this.selectedTarget.node[0]; + this.scatterChartInteractionService.reset(this.instanceKey, this.selectedApplication, this.selectedAgent, this.fromX, this.toX, this.scatterChartMode); + this.changeDetectorRef.detectChanges(); + }); + } + isHide(): boolean { + return false; + } + checkClass(): string { + return this.hideSettingPopup ? '' : 'l-popup-on'; + } + isHideSettingPopup(): boolean { + return this.hideSettingPopup; + } + onApplySetting(params: any): void { + this.fromY = params.min; + this.toY = params.max; + this.scatterChartInteractionService.changeYRange({ + instanceKey: this.instanceKey, + from: params.min, + to: params.max + }); + this.hideSettingPopup = true; + this.webAppSettingDataService.setScatterY(this.instanceKey, { min: params.min, max: params.max }); + } + onCancelSetting(): void { + this.hideSettingPopup = true; + } + onShowSetting(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_SCATTER_SETTING); + this.hideSettingPopup = false; + } + onDownload(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.DOWNLOAD_SCATTER); + this.scatterChartInteractionService.downloadChart(this.instanceKey); + } + onOpenScatterPage(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.GO_TO_FULL_SCREEN_SCATTER); + this.urlRouteManagerService.openPage([ + UrlPath.SCATTER_FULL_SCREEN_MODE, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + this.selectedAgent + ]); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.SCATTER); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.SCATTER, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } + onChangedSelectType(params: {instanceKey: string, name: string, checked: boolean}): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CHANGE_SCATTER_CHART_STATE, `${params.name}: ${params.checked ? `on` : `off`}`); + this.scatterChartInteractionService.changeViewType(params); + } + onChangeTransactionCount(params: object): void { + this.typeCount = params; + } + onChangeRangeX(params: IScatterXRange): void { + this.storeHelperService.dispatch(new Actions.UpdateRealTimeScatterChartXRange(params)); + } + onSelectArea(params: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_TRANSACTION_LIST); + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_LIST, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ], `${this.selectedApplication}|${params.x.from}|${params.x.to}|${params.y.from}|${params.y.to}|${this.selectedAgent}|${params.type.join(',')}`); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-side-bar-container.component.css b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-side-bar-container.component.css new file mode 100644 index 000000000000..5297343d37ac --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-side-bar-container.component.css @@ -0,0 +1,19 @@ +:host { + position: relative; +} +.l-chart-item { + margin: 15px 0; + height: 285px; + position: relative; + border-top: none; +} +.l-chart-item.l-popup-on:before { + display: block; + z-index: 490; + position: absolute; + content: ''; + width: 100%; + height: 100%; + background: #FFF; + opacity: 0.8; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-side-bar-container.component.html b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-side-bar-container.component.html new file mode 100644 index 000000000000..63fe24300af9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-side-bar-container.component.html @@ -0,0 +1,42 @@ +
+ + + + +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-side-bar-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-side-bar-container.component.ts new file mode 100644 index 000000000000..d8ea285b17d2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-filtered-map-side-bar-container.component.ts @@ -0,0 +1,238 @@ +import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core'; +import { combineLatest, Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { + StoreHelperService, + WebAppSettingDataService, + NewUrlStateNotificationService, + UrlRouteManagerService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ScatterChart } from './class/scatter-chart.class'; +import { ScatterChartComponent } from './scatter-chart.component'; +import { ScatterChartInteractionService } from './scatter-chart-interaction.service'; +import { ScatterChartDataService } from './scatter-chart-data.service'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-scatter-chart-for-filtered-map-side-bar-container', + templateUrl: './scatter-chart-for-filtered-map-side-bar-container.component.html', + styleUrls: ['./scatter-chart-for-filtered-map-side-bar-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ScatterChartForFilteredMapSideBarContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + instanceKey = 'filtered-map-side-bar'; + addWindow = true; + i18nText: { [key: string]: string } = { + NO_DATA: '' + }; + selectedTarget: ISelectedTarget; + selectedApplication: string; + hideSettingPopup = true; + selectedAgent: string; + typeInfo = [{ + name: 'failed', + color: '#E95459', + order: 10 + }, { + name: 'success', + color: '#34B994', + order: 20 + }]; + typeCount: object; + width = 460; + height = 230; + min: number; + max: number; + fromX: number; + toX: number; + fromY: number; + toY: number; + application: string; + scatterChartMode: string; + scatterChartDataOfAllNode: any[] = []; + timezone: string; + dateFormat: string[]; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private scatterChartDataService: ScatterChartDataService, + private scatterChartInteractionService: ScatterChartInteractionService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.setScatterY(); + combineLatest( + this.translateService.get('COMMON.NO_DATA') + ).subscribe((i18n: Array) => { + this.i18nText = { + [ScatterChartComponent.I18NTEXT.NO_DATA]: i18n[0] + }; + }); + this.connectStore(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.APPLICATION); + }) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.scatterChartMode = ScatterChart.MODE.STATIC; + this.scatterChartDataService.setCurrentMode(this.scatterChartMode); + this.application = urlService.getPathValue(UrlPathId.APPLICATION).getKeyStr(); + this.selectedAgent = ''; + this.fromX = urlService.getStartTimeToNumber(); + this.toX = urlService.getEndTimeToNumber(); + this.changeDetectorRef.detectChanges(); + }); + + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private setScatterY(): void { + const scatterYData = this.webAppSettingDataService.getScatterY(this.instanceKey); + this.fromY = scatterYData.min; + this.toY = scatterYData.max; + } + private connectStore(): void { + this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => { + this.timezone = timezone; + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getDateFormatArray(this.unsubscribe, 3, 4).subscribe((format: string[]) => { + this.dateFormat = format; + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getAgentSelection(this.unsubscribe).subscribe((agent: string) => { + if (this.selectedAgent !== agent) { + this.selectedAgent = agent; + this.scatterChartInteractionService.changeAgent(this.instanceKey, agent); + } + }); + this.storeHelperService.getScatterChartData(this.unsubscribe).pipe( + filter((data: any) => { + return data.length > 0 && data.length > this.scatterChartDataOfAllNode.length; + }) + ).subscribe((scatterChartData: IScatterData[]) => { + const startIndex = this.scatterChartDataOfAllNode.length; + this.scatterChartDataOfAllNode = scatterChartData; + if (this.selectedTarget) { + this.getScatterData(startIndex); + } + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.selectedTarget = target; + if (this.isHide() === false) { + this.selectedAgent = ''; + this.selectedApplication = this.selectedTarget.node[0]; + this.scatterChartInteractionService.reset(this.instanceKey, this.selectedApplication, this.selectedAgent, this.fromX, this.toX, this.scatterChartMode); + this.getScatterData(0); + } + this.changeDetectorRef.detectChanges(); + }); + } + getScatterData(startIndex: number): void { + for (let i = startIndex ; i < this.scatterChartDataOfAllNode.length ; i++) { + this.scatterChartInteractionService.addChartData(this.instanceKey, this.scatterChartDataOfAllNode[i][this.selectedApplication]); + } + } + getGroupUnitX(): number { + return Math.round((this.toX - this.fromX) / this.width); + } + getGroupUnitY(): number { + return Math.round((this.toY - this.fromY) / this.height); + } + isHide(): boolean { + if (this.selectedTarget) { + return !(this.selectedTarget.isNode && this.selectedTarget.isWAS && this.selectedTarget.isMerged === false); + } + return true; + } + checkClass(): string { + return this.hideSettingPopup ? '' : 'l-popup-on'; + } + isHideSettingPopup(): boolean { + return this.hideSettingPopup; + } + onApplySetting(params: any): void { + this.fromY = params.min; + this.toY = params.max; + this.scatterChartInteractionService.changeYRange({ + instanceKey: this.instanceKey, + from: params.min, + to: params.max + }); + this.hideSettingPopup = true; + this.webAppSettingDataService.setScatterY(this.instanceKey, { min: params.min, max: params.max }); + } + onCancelSetting(): void { + this.hideSettingPopup = true; + } + onShowSetting(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_SCATTER_SETTING); + this.hideSettingPopup = false; + } + onDownload(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.DOWNLOAD_SCATTER); + this.scatterChartInteractionService.downloadChart(this.instanceKey); + } + onOpenScatterPage(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.GO_TO_FULL_SCREEN_SCATTER); + this.urlRouteManagerService.openPage([ + UrlPath.SCATTER_FULL_SCREEN_MODE, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + this.selectedAgent + ]); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.SCATTER); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.SCATTER, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } + onChangedSelectType(params: {instanceKey: string, name: string, checked: boolean}): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CHANGE_SCATTER_CHART_STATE, `${params.name}: ${params.checked ? `on` : `off`}`); + this.scatterChartInteractionService.changeViewType(params); + } + onChangeTransactionCount(params: object): void { + this.typeCount = params; + } + onChangeRangeX(params: IScatterXRange): void { + this.storeHelperService.dispatch(new Actions.UpdateRealTimeScatterChartXRange(params)); + } + onSelectArea(params: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_TRANSACTION_LIST); + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_LIST, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ], `${this.selectedApplication}|${params.x.from}|${params.x.to}|${params.y.from}|${params.y.to}|${this.selectedAgent}|${params.type.join(',')}`); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component.css b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component.css new file mode 100644 index 000000000000..e179f7e6cf0e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component.css @@ -0,0 +1,17 @@ +:host { + position: relative; +} +.l-chart-item { + margin: 15px 0 0 0; + height: 285px; + position: relative; + border-top: none; +} +.l-layer-background { + top: 0; + display: none; + opacity: 0.8; + z-index: 490; + position: absolute; + background: #FFF; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component.html b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component.html new file mode 100644 index 000000000000..78ce29d11f39 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component.html @@ -0,0 +1,42 @@ +
+ + + + +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component.ts new file mode 100644 index 000000000000..4f17b5134c5a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component.ts @@ -0,0 +1,236 @@ +import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy, ViewChild, ElementRef, Renderer2 } from '@angular/core'; +import { Observable, Subject, of, combineLatest, Subscription } from 'rxjs'; +import { takeUntil, delay } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { + StoreHelperService, + WebAppSettingDataService, + NewUrlStateNotificationService, + UrlRouteManagerService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ScatterChartDataService } from './scatter-chart-data.service'; +import { ScatterChart } from './class/scatter-chart.class'; +import { ScatterChartComponent } from './scatter-chart.component'; +import { ScatterChartInteractionService } from './scatter-chart-interaction.service'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-scatter-chart-for-full-screen-mode-container', + templateUrl: './scatter-chart-for-full-screen-mode-container.component.html', + styleUrls: ['./scatter-chart-for-full-screen-mode-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ScatterChartForFullScreenModeContainerComponent implements OnInit, OnDestroy { + @ViewChild('layerBackground') layerBackground: ElementRef; + private unsubscribe: Subject = new Subject(); + instanceKey = 'full-screen-mode'; + addWindow = true; + i18nText: { [key: string]: string } = { + NO_DATA: '' + }; + selectedTarget: ISelectedTarget; + selectedApplication: string; + scatterDataServiceSubscription: Subscription; + hideSettingPopup = true; + selectedAgent: string; + typeInfo = [{ + name: 'failed', + color: '#E95459', + order: 10 + }, { + name: 'success', + color: '#34B994', + order: 20 + }]; + typeCount: object; + width = 690; + height = 345; + min: number; + max: number; + fromX: number; + toX: number; + fromY: number; + toY: number; + application: string; + scatterChartMode: string; + timezone$: Observable; + dateFormat: string[]; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private renderer: Renderer2, + private translateService: TranslateService, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private scatterChartDataService: ScatterChartDataService, + private scatterChartInteractionService: ScatterChartInteractionService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.setScatterY(); + this.getI18NText(); + this.connectStore(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.scatterChartMode = urlService.isRealTimeMode() ? ScatterChart.MODE.REALTIME : ScatterChart.MODE.STATIC; + this.scatterChartDataService.setCurrentMode(this.scatterChartMode); + this.selectedApplication = this.application = urlService.getPathValue(UrlPathId.APPLICATION).getKeyStr(); + this.selectedAgent = urlService.hasValue(UrlPathId.AGENT_ID) ? urlService.getPathValue(UrlPathId.AGENT_ID) : ''; + this.fromX = urlService.getStartTimeToNumber(); + this.toX = urlService.getEndTimeToNumber(); + of(1).pipe(delay(1)).subscribe((num: number) => { + this.scatterChartInteractionService.reset(this.instanceKey, this.selectedApplication, this.selectedAgent, this.fromX, this.toX, this.scatterChartMode); + this.getScatterData(); + this.changeDetectorRef.detectChanges(); + }); + }); + this.scatterChartDataService.outScatterData$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((scatterData: IScatterData) => { + if (this.scatterChartMode === ScatterChart.MODE.STATIC) { + this.scatterChartInteractionService.addChartData(this.instanceKey, scatterData); + if (scatterData.complete === false) { + this.scatterChartDataService.loadData( + this.selectedApplication.split('^')[0], + this.fromX, + scatterData.resultFrom - 1, + this.getGroupUnitX(), + this.getGroupUnitY(), + false + ); + } + } else { + if (scatterData.reset === true) { + this.fromX = scatterData.currentServerTime - this.webAppSettingDataService.getSystemDefaultPeriod().getMiliSeconds(); + this.toX = scatterData.currentServerTime; + this.scatterChartInteractionService.reset(this.instanceKey, this.selectedApplication, this.selectedAgent, this.fromX, this.toX, this.scatterChartMode); + of(1).pipe(delay(1000)).subscribe((useless: number) => { + this.getScatterData(); + }); + } else { + this.scatterChartInteractionService.addChartData(this.instanceKey, scatterData); + } + } + }); + } + ngOnDestroy() { + if (this.scatterDataServiceSubscription) { + this.scatterDataServiceSubscription.unsubscribe(); + } + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private setScatterY(): void { + const scatterYData = this.webAppSettingDataService.getScatterY(this.instanceKey); + this.fromY = scatterYData.min; + this.toY = scatterYData.max; + } + private getI18NText(): void { + combineLatest( + this.translateService.get('COMMON.NO_DATA') + ).subscribe((i18n: Array) => { + this.i18nText = { + [ScatterChartComponent.I18NTEXT.NO_DATA]: i18n[0] + }; + }); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.storeHelperService.getDateFormatArray(this.unsubscribe, 3, 4).subscribe((dateFormat: string[]) => { + this.dateFormat = dateFormat; + }); + } + getScatterData(): void { + if (this.scatterChartMode === ScatterChart.MODE.STATIC) { + this.scatterChartDataService.loadData( + this.selectedApplication.split('^')[0], + this.fromX, + this.toX, + this.getGroupUnitX(), + this.getGroupUnitY() + ); + } else { + this.scatterChartDataService.loadRealTimeData( + this.selectedApplication.split('^')[0], + this.fromX, + this.toX, + this.getGroupUnitX(), + this.getGroupUnitY() + ); + } + } + getGroupUnitX(): number { + return Math.round((this.toX - this.fromX) / this.width); + } + getGroupUnitY(): number { + return Math.round((this.toY - this.fromY) / this.height); + } + isHideSettingPopup(): boolean { + return this.hideSettingPopup; + } + onApplySetting(params: any): void { + this.fromY = params.min; + this.toY = params.max; + this.scatterChartInteractionService.changeYRange({ + instanceKey: this.instanceKey, + from: params.min, + to: params.max + }); + this.onHideSetting(); + this.webAppSettingDataService.setScatterY(this.instanceKey, { min: params.min, max: params.max }); + } + onHideSetting(): void { + this.renderer.setStyle(this.layerBackground.nativeElement, 'display', 'none'); + this.hideSettingPopup = true; + } + onShowSetting(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_SCATTER_SETTING); + this.renderer.setStyle(this.layerBackground.nativeElement, 'display', 'block'); + this.hideSettingPopup = false; + } + onDownload(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.DOWNLOAD_SCATTER); + this.scatterChartInteractionService.downloadChart(this.instanceKey); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.SCATTER); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.SCATTER, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } + onChangedSelectType(params: {instanceKey: string, name: string, checked: boolean}): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CHANGE_SCATTER_CHART_STATE, `${params.name}: ${params.checked ? `on` : `off`}`); + this.scatterChartInteractionService.changeViewType(params); + } + onChangeTransactionCount(params: object): void { + this.typeCount = params; + } + onChangeRangeX(params: IScatterXRange): void { + this.storeHelperService.dispatch(new Actions.UpdateRealTimeScatterChartXRange(params)); + } + onSelectArea(params: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_TRANSACTION_LIST); + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_LIST, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ], `${this.selectedApplication}|${params.x.from}|${params.x.to}|${params.y.from}|${params.y.to}|${this.selectedAgent}|${params.type.join(',')}`); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-info-per-server-container.component.css b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-info-per-server-container.component.css new file mode 100644 index 000000000000..5297343d37ac --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-info-per-server-container.component.css @@ -0,0 +1,19 @@ +:host { + position: relative; +} +.l-chart-item { + margin: 15px 0; + height: 285px; + position: relative; + border-top: none; +} +.l-chart-item.l-popup-on:before { + display: block; + z-index: 490; + position: absolute; + content: ''; + width: 100%; + height: 100%; + background: #FFF; + opacity: 0.8; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-info-per-server-container.component.html b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-info-per-server-container.component.html new file mode 100644 index 000000000000..ce9cbabe420d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-info-per-server-container.component.html @@ -0,0 +1,42 @@ +
+ + + + +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-info-per-server-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-info-per-server-container.component.ts new file mode 100644 index 000000000000..013c7d1e1266 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-for-info-per-server-container.component.ts @@ -0,0 +1,222 @@ +import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, AfterViewInit, OnDestroy } from '@angular/core'; +import { combineLatest, Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { + StoreHelperService, + WebAppSettingDataService, + NewUrlStateNotificationService, + UrlRouteManagerService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ScatterChart } from './class/scatter-chart.class'; +import { ScatterChartComponent } from './scatter-chart.component'; +import { ScatterChartInteractionService } from './scatter-chart-interaction.service'; +import { ScatterChartDataService } from './scatter-chart-data.service'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-scatter-chart-for-info-per-server-container', + templateUrl: './scatter-chart-for-info-per-server-container.component.html', + styleUrls: ['./scatter-chart-for-info-per-server-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ScatterChartForInfoPerServerContainerComponent implements OnInit, AfterViewInit, OnDestroy { + instanceKey = 'info-per-server'; + addWindow = false; + i18nText: { [key: string]: string } = { + NO_DATA: '' + }; + isChangedTarget: boolean; + selectedTarget: ISelectedTarget; + selectedApplication: string; + unsubscribe: Subject = new Subject(); + hideSettingPopup = true; + selectedAgent: string; + typeInfo = [{ + name: 'failed', + color: '#E95459', + order: 10 + }, { + name: 'success', + color: '#34B994', + order: 20 + }]; + typeCount: object; + width = 460; + height = 230; + min: number; + max: number; + fromX: number; + toX: number; + fromY: number; + toY: number; + application: string; + scatterChartMode: string; + timezone: string; + dateFormat: string[]; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private scatterChartDataService: ScatterChartDataService, + private scatterChartInteractionService: ScatterChartInteractionService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.setScatterY(); + combineLatest( + this.translateService.get('COMMON.NO_DATA') + ).subscribe((i18n: string[]) => { + this.i18nText = { + [ScatterChartComponent.I18NTEXT.NO_DATA]: i18n[0] + }; + }); + this.connectStore(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.APPLICATION); + }) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.scatterChartMode = urlService.isRealTimeMode() ? ScatterChart.MODE.REALTIME : ScatterChart.MODE.STATIC; + this.scatterChartDataService.setCurrentMode(this.scatterChartMode); + this.application = urlService.getPathValue(UrlPathId.APPLICATION).getKeyStr(); + this.selectedAgent = ''; + this.fromX = urlService.getStartTimeToNumber(); + this.toX = urlService.getEndTimeToNumber(); + this.changeDetectorRef.detectChanges(); + }); + } + ngAfterViewInit() { + this.storeHelperService.getInfoPerServerState(this.unsubscribe).subscribe((visibleState: boolean) => { + if (visibleState && this.isChangedTarget) { + this.scatterChartDataService.loadLastData().forEach((data: IScatterData) => { + this.scatterChartInteractionService.addChartData(this.instanceKey, data); + }); + this.isChangedTarget = false; + } + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private setScatterY(): void { + const scatterYData = this.webAppSettingDataService.getScatterY(this.instanceKey); + this.fromY = scatterYData.min; + this.toY = scatterYData.max; + } + private connectStore(): void { + this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => { + this.timezone = timezone; + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getDateFormatArray(this.unsubscribe, 3, 4).subscribe((format: string[]) => { + this.dateFormat = format; + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getAgentSelectionForServerList(this.unsubscribe).pipe( + filter((chartData: IAgentSelection) => { + return (chartData && chartData.agent) ? true : false; + }) + ).subscribe((chartData: IAgentSelection) => { + this.selectedAgent = chartData.agent; + this.scatterChartInteractionService.changeAgent(this.instanceKey, chartData.agent); + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.isChangedTarget = true; + this.selectedTarget = target; + this.selectedAgent = ''; + this.selectedApplication = this.selectedTarget.node[0]; + this.scatterChartInteractionService.reset(this.instanceKey, this.selectedApplication, this.selectedAgent, this.fromX, this.toX, this.scatterChartMode); + this.changeDetectorRef.detectChanges(); + }); + } + isHide(): boolean { + return false; + } + checkClass(): string { + return this.hideSettingPopup ? '' : 'l-popup-on'; + } + isHideSettingPopup(): boolean { + return this.hideSettingPopup; + } + onApplySetting(params: any): void { + this.fromY = params.min; + this.toY = params.max; + this.scatterChartInteractionService.changeYRange({ + instanceKey: this.instanceKey, + from: params.min, + to: params.max + }); + this.hideSettingPopup = true; + this.webAppSettingDataService.setScatterY(this.instanceKey, { min: params.min, max: params.max }); + } + onCancelSetting(): void { + this.hideSettingPopup = true; + } + onShowSetting(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_SCATTER_SETTING); + this.hideSettingPopup = false; + } + onDownload(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.DOWNLOAD_SCATTER); + this.scatterChartInteractionService.downloadChart(this.instanceKey); + } + onOpenScatterPage(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.GO_TO_FULL_SCREEN_SCATTER); + this.urlRouteManagerService.openPage([ + UrlPath.SCATTER_FULL_SCREEN_MODE, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + this.selectedAgent + ]); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.SCATTER); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.SCATTER, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } + onChangedSelectType(params: {instanceKey: string, name: string, checked: boolean}): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CHANGE_SCATTER_CHART_STATE, `${params.name}: ${params.checked ? `on` : `off`}`); + this.scatterChartInteractionService.changeViewType(params); + } + onChangeTransactionCount(params: object): void { + this.typeCount = params; + } + onChangeRangeX(params: IScatterXRange): void { + this.storeHelperService.dispatch(new Actions.UpdateRealTimeScatterChartXRange(params)); + } + onSelectArea(params: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_TRANSACTION_LIST); + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_LIST, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ], `${this.selectedApplication}|${params.x.from}|${params.x.to}|${params.y.from}|${params.y.to}|${this.selectedAgent}|${params.type.join(',')}`); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-interaction.service.ts new file mode 100644 index 000000000000..9b2bc0164c0d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-interaction.service.ts @@ -0,0 +1,86 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; + +export interface IChangedAgentParam { + instanceKey: string; + agent: string; +} +export interface IChangedViewTypeParam { + instanceKey: string; + name: string; + checked: boolean; +} +export interface IRangeParam { + instanceKey: string; + from: number; + to: number; +} +export interface IResetParam { + instanceKey: string; + application: string; + agent: string; + from: number; + to: number; + mode: string; +} + +@Injectable() +export class ScatterChartInteractionService { + + private outChartData = new Subject<{instanceKey: string, data: IScatterData}>(); + public onChartData$: Observable<{instanceKey: string, data: IScatterData}>; + + private outViewType = new Subject(); + public onViewType$: Observable; + + private outChangeYRange = new Subject(); + public onChangeYRange$: Observable; + + private outSelectedAgent = new Subject(); + public onSelectedAgent$: Observable; + + private outInvokeDownloadChart = new Subject(); + public onInvokeDownloadChart$: Observable; + + private outReset = new Subject(); + public onReset$: Observable; + constructor() { + this.onChartData$ = this.outChartData.asObservable(); + this.onViewType$ = this.outViewType.asObservable(); + this.onChangeYRange$ = this.outChangeYRange.asObservable(); + this.onSelectedAgent$ = this.outSelectedAgent.asObservable(); + this.onInvokeDownloadChart$ = this.outInvokeDownloadChart.asObservable(); + this.onReset$ = this.outReset.asObservable(); + } + addChartData(instanceKey: string, data: IScatterData): void { + this.outChartData.next({ + instanceKey: instanceKey, + data: data + }); + } + changeViewType(params: IChangedViewTypeParam): void { + this.outViewType.next(params); + } + changeYRange(yRange: IRangeParam): void { + this.outChangeYRange.next(yRange); + } + changeAgent(instanceKey: string, agent: string): void { + this.outSelectedAgent.next({ + instanceKey: instanceKey, + agent: agent + }); + } + downloadChart(instanceKey: string): void { + this.outInvokeDownloadChart.next(instanceKey); + } + reset(instanceKey: string, application: string, agent: string, from: number, to: number, mode: string): void { + this.outReset.next({ + instanceKey: instanceKey, + application: application, + agent: agent, + from: from, + to: to, + mode: mode + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-message-plugin.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-message-plugin.ts new file mode 100644 index 000000000000..bf2cd6dc037f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-message-plugin.ts @@ -0,0 +1,46 @@ +export class ScatterChartMessagePlugin { + constructor() { + // this._init(); + } + // _init() { + // this._aCallback = []; + // } + // initElement($elParent, $elPlugin, option) { + // this._$element = $('
') + // .css({ + // 'top': 0, + // 'width': option['width'], + // 'height': option['height'], + // 'cursor': 'crosshair', + // 'display': 'none', + // 'z-index': 1100, + // 'position': 'absolute', + // 'background-color': 'rgba(0,0,0,0)' // for ie10 + // }) + // .addClass('message-display') + // .append( + // $('
') + // .css(option['noDataStyle']) + // .css({ + // 'top': (option['height'] / 2) + 'px', + // 'width': option['width'] + 'px', + // 'position': 'absolute', + // 'text-align': 'center' + // } + // )).appendTo($elParent); + + // return this; + // } + // initEvent( /* oChart */) { + // return this; + // } + // addCallback(fn) { + // this._aCallback.push(fn); + // } + // show(message) { + // this._$element.find('> div').html(message).end().show(); + // } + // hide() { + // this._$element.hide(); + // } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-options.component.css b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-options.component.css new file mode 100644 index 000000000000..f4172f3425a5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-options.component.css @@ -0,0 +1,21 @@ +.l-tool-box { + font-size:14px; + color:#b3b5b9; + text-align:right; + position:relative; + padding: 0 25px 0 +} +.l-tool-box button { + margin:0 0 0 14px; +} +.l-tool-box button.l-last-child { + font-size:18px; + +} +.l-tool-box button.l-last-child:before { + padding-left: 14px; + border-left: 1px solid #e9ebec; +} +.fas { + font-size: 18px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-options.component.html b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-options.component.html new file mode 100644 index 000000000000..50bac318f8be --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-options.component.html @@ -0,0 +1,6 @@ +
+ + + + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-options.component.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-options.component.ts new file mode 100644 index 000000000000..9bc36104e042 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-options.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-scatter-chart-options', + templateUrl: './scatter-chart-options.component.html', + styleUrls: ['./scatter-chart-options.component.css'] +}) +export class ScatterChartOptionsComponent implements OnInit { + @Output() outShowSetting: EventEmitter = new EventEmitter(); + @Output() outDownload: EventEmitter = new EventEmitter(); + @Output() outOpenScatterPage: EventEmitter = new EventEmitter(); + @Output() outShowHelp: EventEmitter = new EventEmitter(); + @Input() instanceKey: string; + @Input() hiddenOptions: { setting: boolean, download: boolean, open: boolean, help: boolean }; + constructor() {} + ngOnInit() {} + onShowHelp($event: MouseEvent): void { + this.outShowHelp.emit($event); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-setting-popup.component.css b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-setting-popup.component.css new file mode 100644 index 000000000000..022bf5bed571 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-setting-popup.component.css @@ -0,0 +1,58 @@ +:host { + width: 100%; + z-index: 1000; + position: absolute; +} +.l-chart-setting { + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + width: 60%; + border: 1px solid #E5E8F0; + text-align: left; + box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.15); + background: #FFF; + margin: 15% 20%; +} +.l-title-group { + height:auto; + padding: 10px 20px; + background-color: #FFF; +} +.l-title-group dt { + color: #4A8FD2; + font-size: 20px; + font-weight: 600; +} +.l-contents-group { + padding: 20px 20px 15px 20px +} +.l-contents-group ul li { + margin: 5px 0 0 0; + font-size: 13px; + font-weight: 600; +} +.l-contents-group ul li label { + width: 50%; + display: inline-block +} +.l-contents-group input { + border:1px solid #d7dde4; + font-size:13px; + color:#666; + height:22px; + padding:0 8px; +} +.l-bottom-group { + text-align: center; + margin: 0 0 20px; +} +input { + color: #666; + width: 40%; + height: 22px; + border: 1px solid #d7dde4; + padding: 0 8px; + font-size: 13px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-setting-popup.component.html b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-setting-popup.component.html new file mode 100644 index 000000000000..48e47839ab8f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-setting-popup.component.html @@ -0,0 +1,17 @@ +
+
+
+
Setting
+
+
+
+
    +
  • +
  • +
+
+
+ + +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-setting-popup.component.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-setting-popup.component.ts new file mode 100644 index 000000000000..ef87c85f80af --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-setting-popup.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit, Input, Output, ViewChild, ElementRef, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-scatter-chart-setting-popup', + templateUrl: './scatter-chart-setting-popup.component.html', + styleUrls: ['./scatter-chart-setting-popup.component.css'] +}) +export class ScatterChartSettingPopupComponent implements OnInit { + @ViewChild('minInput') minInput: ElementRef; + @ViewChild('maxInput') maxInput: ElementRef; + @Input() instanceKey: string; + @Input() min: number; + @Input() max: number; + @Output() outApply: EventEmitter<{key: string, min: number, max: number}> = new EventEmitter(); + @Output() outCancel: EventEmitter = new EventEmitter(); + constructor() {} + ngOnInit() {} + onApply(): void { + this.min = Number.parseInt(this.minInput.nativeElement.value, 10); + this.max = Number.parseInt(this.maxInput.nativeElement.value, 10); + this.outApply.emit({ + key: this.instanceKey, + min: this.min, + max: this.max + }); + } + onCancel(): void { + this.minInput.nativeElement.value = this.min; + this.maxInput.nativeElement.value = this.max; + this.outCancel.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-state-view.component.css b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-state-view.component.css new file mode 100644 index 000000000000..f69f6cac9436 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-state-view.component.css @@ -0,0 +1,36 @@ +:host { + display: block; +} +.l-checkbox-group { + color:#b3b3b4; + margin: 24px 0 0 0; + display: flex; + flex-flow: row wrap; + font-size:12px; + justify-content: space-around; +} +.l-checkbox-group span { + color:#333; + font-size:22px; +} +.l-checkbox-group .success:before { + width: 13px; + height: 13px; + margin: 0 6px 0 0; + content: ''; + display: inline-block; + background: #34b994; + border-radius: 50%; +} +.l-checkbox-group .failed:before { + width: 13px; + height: 13px; + margin: 0 6px 0 0; + content: ''; + display: inline-block; + background: #e95459; + border-radius: 50%; +} +input { + margin-left: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-state-view.component.html b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-state-view.component.html new file mode 100644 index 000000000000..ebf8c88bea5e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-state-view.component.html @@ -0,0 +1,8 @@ +
+
+ + +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-state-view.component.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-state-view.component.ts new file mode 100644 index 000000000000..633371a6d97d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-state-view.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-scatter-chart-state-view', + templateUrl: './scatter-chart-state-view.component.html', + styleUrls: ['./scatter-chart-state-view.component.css'] +}) +export class ScatterChartStateViewComponent implements OnInit, OnChanges { + innerTypeInfo: any[] = []; + @Input() instanceKey: string; + @Input() typeInfo: any[]; + @Input() typeCount: object; + @Output() outChanged: EventEmitter<{instanceKey: string, name: string, checked: boolean}> = new EventEmitter(); + constructor() {} + ngOnInit() {} + ngOnChanges(simpleChanges: SimpleChanges) { + if (simpleChanges['typeInfo'] && simpleChanges['typeInfo'].isFirstChange()) { + this.setInnerTypeInfo(); + } + } + private setInnerTypeInfo() { + this.typeInfo.forEach((info: object, index: number) => { + const obj = { + checked: true + }; + Object.keys(info).forEach((key: string) => { + obj[key] = info[key]; + }); + this.innerTypeInfo[index] = obj; + }); + const temp = this.innerTypeInfo[0]; + this.innerTypeInfo[0] = this.innerTypeInfo[1]; + this.innerTypeInfo[1] = temp; + } + upperFirstChar(str: string): string { + return str ? str.substr(0, 1).toUpperCase() + str.substr(1) : ''; + } + getTypeCount(name: string): number { + if (this.typeCount && this.typeCount[name]) { + return this.typeCount[name]; + } else { + return 0; + } + } + onCheck(type: any): void { + type.checked = !type.checked; + this.outChanged.emit({ + instanceKey: this.instanceKey, + name: type.name, + checked: type.checked + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-util.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-util.ts new file mode 100644 index 000000000000..f33edc790378 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart-util.ts @@ -0,0 +1,45 @@ +export default { + // compare(aData: any[][], dataIndex: number, fnCompare: (result: number) => boolean): any { + // let targetIndex = 0; + // for (let i = 1, len = aData.length; i < len; i++) { + // const result = aData[targetIndex][dataIndex] - aData[i][dataIndex]; + // if (fnCompare(result)) { + // targetIndex = i; + // } + // } + // return aData[targetIndex][dataIndex]; + // }, + // min(result: number): boolean { + // return result > 0; + // }, + // max(result: number): boolean { + // return result < 0; + // }, + // indexOf(aData: any[], value: any): number { + // for (let i = 0 ; i < aData.length ; i++) { + // if (aData[i] === value) { + // return i; + // } + // } + // return -1; + // }, + // getBoundaryValue(oRange: { min: number, max: number}, value: number): number { + // return Math.min(oRange.max, Math.max(oRange.min, value)); + // }, + // isInRange(from: number, to: number, value: number): boolean { + // return value >= from && value <= to; + // }, + // isEmpty(obj: any): boolean { + // let count = 0; + // Object.keys(obj).forEach((key: string) => { + // count += obj[key].length; + // }); + // return count === 0; + // }, + // makeKey(a: any, b: any, c: any): string { + // return a + '-' + b + '-' + c; + // }, + // isString(v: any): boolean { + // return typeof v === 'string'; + // }, +}; diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart.component.css b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart.component.css new file mode 100644 index 000000000000..1bc8488bc47f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart.component.css @@ -0,0 +1,30 @@ +:host { + display: block; + height: 100%; +} +.l-wrapper { + display: block; +} +.l-scatter { + width: 100%; + height: 100%; + display: block; + position: relative; +} +.l-message { + top: 0px; + width: 100%; + height: 100%; + display: flex; + position: absolute; + text-align: center; + justify-content: center; + align-items: center; +} +.l-message span { + font-weight: 600; +} +canvas { + width: 100%; + height: 100%; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart.component.html b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart.component.html new file mode 100644 index 000000000000..3bf95e6b02d9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart.component.html @@ -0,0 +1,5 @@ +
+
+
Loading...
+
{{i18nText.NO_DATA}}
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart.component.ts b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart.component.ts new file mode 100644 index 000000000000..91ff5ba94c20 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/scatter-chart/scatter-chart.component.ts @@ -0,0 +1,175 @@ +import { Component, OnInit, OnDestroy, OnChanges, Input, Output, EventEmitter, ViewChild, ElementRef, SimpleChanges } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; +import { WindowRefService } from 'app/shared/services'; +import { ScatterChart, ITypeInfo } from './class/scatter-chart.class'; +import { ScatterChartDataBlock } from './class/scatter-chart-data-block.class'; +import { ScatterChartInteractionService, IChangedViewTypeParam, IRangeParam, IResetParam, IChangedAgentParam } from './scatter-chart-interaction.service'; + +@Component({ + selector: 'pp-scatter-chart', + templateUrl: './scatter-chart.component.html', + styleUrls: ['./scatter-chart.component.css'] +}) +export class ScatterChartComponent implements OnInit, OnDestroy, OnChanges { + static I18NTEXT = { + NO_DATA: 'NO_DATA', + }; + @ViewChild('scatter') elementScatter: ElementRef; + @Input() instanceKey: string; + @Input() addWindow: boolean; + @Input() width: number; + @Input() height: number; + @Input() fromX: number; + @Input() toX: number; + @Input() fromY: number; + @Input() toY: number; + @Input() mode: string; + @Input() application: string; + @Input() agent: string; + @Input() typeInfo: ITypeInfo[]; + @Input() i18nText = { + [ScatterChartComponent.I18NTEXT.NO_DATA]: 'No Data' + }; + @Input() timezone: string; + @Input() dateFormat: string[]; + @Output() outTransactionCount: EventEmitter = new EventEmitter(); + @Output() outSelectArea: EventEmitter = new EventEmitter(); + @Output() outChangeRangeX: EventEmitter = new EventEmitter(); + private unsubscribe: Subject = new Subject(); + dataLoaded = false; + scatterChartInstance: ScatterChart = null; + constructor( + private windowRefService: WindowRefService, + private scatterChartInteractionService: ScatterChartInteractionService + ) {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if (this.mode && (this.fromX >= 0) && (this.toX >= 0) && (this.fromY >= 0) && (this.toY >= 0) && this.typeInfo && this.application && this.timezone && this.dateFormat) { + if (this.scatterChartInstance === null) { + this.scatterChartInstance = new ScatterChart( + this.mode, + this.elementScatter.nativeElement, + this.fromX, + this.toX, + this.fromY, + this.toY, + this.typeInfo, + this.application, + this.agent, + this.width, + this.height, + this.timezone, + this.dateFormat + ); + this.addSubscribeForInstance(); + this.addSubscribeForService(); + this.addToWindow(); + } else { + if (changes['timezone'] && changes['timezone'].currentValue) { + this.scatterChartInstance.setTimezone(this.timezone); + this.scatterChartInstance.redraw(); + } + if (changes['dateFormat'] && changes['dateFormat'].currentValue) { + this.scatterChartInstance.setDateFormat(this.dateFormat); + this.scatterChartInstance.redraw(); + } + } + } + } + private addToWindow(): void { + if (this.addWindow) { + if ('scatterChartInstance' in this.windowRefService.nativeWindow === false) { + this.windowRefService.nativeWindow['scatterChartInstance'] = {}; + } + this.windowRefService.nativeWindow['scatterChartInstance'][this.application] = this.scatterChartInstance; + } + } + private addSubscribeForInstance(): void { + this.scatterChartInstance.onSelect$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((area: any) => { + this.outSelectArea.emit(area); + }); + this.scatterChartInstance.onChangeTransactionCount$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((typeCount: any) => { + this.outTransactionCount.emit(typeCount); + }); + this.scatterChartInstance.onChangeRangeX$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((range: any) => { + this.outChangeRangeX.emit(range); + }); + } + private addSubscribeForService(): void { + this.scatterChartInteractionService.onChartData$.pipe( + takeUntil(this.unsubscribe), + filter((dataWrapper: any) => { + return dataWrapper.instanceKey === this.instanceKey ? true : false; + }) + ).subscribe((dataWrapper: {instanceKey: string, data: IScatterData}) => { + this.scatterChartInstance.addData(new ScatterChartDataBlock(dataWrapper.data, this.scatterChartInstance.getTypeManager())); + this.dataLoaded = true; + }); + this.scatterChartInteractionService.onViewType$.pipe( + takeUntil(this.unsubscribe), + filter((data: any) => { + return data.instanceKey === this.instanceKey ? true : false; + }) + ).subscribe((typeCheck: IChangedViewTypeParam) => { + this.scatterChartInstance.changeShowType(typeCheck); + }); + this.scatterChartInteractionService.onChangeYRange$.pipe( + takeUntil(this.unsubscribe), + filter((data: any) => { + return data.instanceKey === this.instanceKey ? true : false; + }) + ).subscribe((yRange: IRangeParam) => { + this.scatterChartInstance.changeYRange(yRange); + }); + this.scatterChartInteractionService.onSelectedAgent$.pipe( + takeUntil(this.unsubscribe), + filter((data: any) => { + return data.instanceKey === this.instanceKey ? true : false; + }) + ).subscribe((data: IChangedAgentParam) => { + this.scatterChartInstance.changeSelectedAgent(data.agent); + }); + this.scatterChartInteractionService.onInvokeDownloadChart$.pipe( + takeUntil(this.unsubscribe), + filter((instanceKey: string) => { + return instanceKey === this.instanceKey ? true : false; + }) + ).subscribe(() => { + this.scatterChartInstance.downloadChartAsImage('png'); + }); + this.scatterChartInteractionService.onReset$.pipe( + takeUntil(this.unsubscribe), + filter((data: any) => { + return data.instanceKey === this.instanceKey ? true : false; + }) + ).subscribe((params: IResetParam) => { + this.application = params.application; + this.agent = params.agent; + this.fromX = params.from; + this.toX = params.to; + this.mode = params.mode; + this.scatterChartInstance.reset(params.application, params.agent, params.from, params.to, params.mode); + this.addToWindow(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + hideNoData(): boolean { + if (this.dataLoaded === false) { + return true; + } + if (this.mode === ScatterChart.MODE.REALTIME) { + return true; + } + return !this.scatterChartInstance.isEmpty(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/search-period/index.ts b/web/src/main/webapp/v2/src/app/core/components/search-period/index.ts new file mode 100644 index 000000000000..ee06fbf443fe --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/search-period/index.ts @@ -0,0 +1,21 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { SearchPeriodContainerComponent } from './search-period-container.component'; +import { SearchPeriodComponent } from './search-period.component'; + +@NgModule({ + declarations: [ + SearchPeriodContainerComponent, + SearchPeriodComponent + ], + imports: [ + SharedModule + ], + exports: [ + SearchPeriodContainerComponent, + SearchPeriodComponent + ], + providers: [] +}) +export class SearchPeriodModule {} diff --git a/web/src/main/webapp/v2/src/app/core/components/search-period/search-period-container.component.css b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period-container.component.css new file mode 100644 index 000000000000..65dd9f6ca2e9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period-container.component.css @@ -0,0 +1,5 @@ +:host { + display: inline-block; + margin-left: 15px; + width: 115px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/search-period/search-period-container.component.html b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period-container.component.html new file mode 100644 index 000000000000..0603bb09033a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period-container.component.html @@ -0,0 +1,5 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/search-period/search-period-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period-container.component.ts new file mode 100644 index 000000000000..bf5582c0e5f4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period-container.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; + +import { Period } from 'app/core/models/period'; +import { UrlPath } from 'app/shared/models'; +import { WebAppSettingDataService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +@Component({ + selector: 'pp-search-period-container', + templateUrl: './search-period-container.component.html', + styleUrls: ['./search-period-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SearchPeriodContainerComponent implements OnInit { + periodList: Period[]; + userDefaultPeriod: Period; + + constructor( + private webAppSettingDataService: WebAppSettingDataService, + private analyticsService: AnalyticsService, + ) {} + + ngOnInit() { + this.periodList = this.webAppSettingDataService.getPeriodList(UrlPath.MAIN); + this.userDefaultPeriod = this.webAppSettingDataService.getUserDefaultPeriod(); + } + + onChangeUserDefaultPeriod(value: Period): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SET_SEARCH_PERIOD_IN_CONFIGURATION, value.getValueWithTime()); + this.webAppSettingDataService.setUserDefaultPeriod(value); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/search-period/search-period.component.css b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period.component.css new file mode 100644 index 000000000000..dd1adaee7acc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period.component.css @@ -0,0 +1,25 @@ +:host { + display: block; + position: relative; + width: 100%; +} +.fa-angle-down { + position: absolute; + top: 8px; + right: 8px; + font-size: 15px; +} + +.l-app-select { + width: 100%; + cursor: pointer; + padding: 6px 12px; + background-color: #fff; + border: 1px solid #d7dde4 !important; + border-radius: 0px; + font-size: 13px; + color: #666; + appearance: none; + -webkit-appearance: none; + outline: 0; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/search-period/search-period.component.html b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period.component.html new file mode 100644 index 000000000000..5acd8ea60632 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period.component.html @@ -0,0 +1,4 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/search-period/search-period.component.ts b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period.component.ts new file mode 100644 index 000000000000..037125eaf5d0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/search-period/search-period.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Period } from 'app/core/models/period'; + +@Component({ + selector: 'pp-search-period', + templateUrl: './search-period.component.html', + styleUrls: ['./search-period.component.css'], +}) +export class SearchPeriodComponent implements OnInit { + @Input() periodList: Period[]; + @Input() userDefaultPeriod: Period; + @Output() outChangeUserDefaultPeriod = new EventEmitter(); + + constructor() {} + ngOnInit() {} + + onChangeUserDefaultPeriod(value: Period): void { + this.outChangeUserDefaultPeriod.emit(value); + } + + compareFn(o1: Period, o2: Period): boolean { + return o1 && o2 ? o1.equals(o2) : o1 === o2; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/index.ts b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/index.ts new file mode 100644 index 000000000000..8aedf8579a91 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/index.ts @@ -0,0 +1,27 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +// import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MatTooltipModule } from '@angular/material'; +import { ServerAndAgentListComponent } from './server-and-agent-list.component'; +import { ServerAndAgentListContainerComponent } from './server-and-agent-list-container.component'; +import { ServerAndAgentListDataService } from './server-and-agent-list-data.service'; + +@NgModule({ + declarations: [ + ServerAndAgentListComponent, + ServerAndAgentListContainerComponent + ], + imports: [ + CommonModule, + // BrowserAnimationsModule, + MatTooltipModule + ], + exports: [ + ServerAndAgentListContainerComponent + ], + providers: [ + ServerAndAgentListDataService + ] +}) +export class ServerAndAgentListModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.html new file mode 100644 index 000000000000..27f8cf398229 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.ts new file mode 100644 index 000000000000..233a2c32d42a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.ts @@ -0,0 +1,114 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { switchMap, takeUntil, filter } from 'rxjs/operators'; + +import { + NewUrlStateNotificationService, + UrlRouteManagerService, + WebAppSettingDataService, + StoreHelperService, + AnalyticsService, + TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ServerAndAgentListDataService } from './server-and-agent-list-data.service'; + +@Component({ + selector: 'pp-server-and-agent-list-container', + templateUrl: './server-and-agent-list-container.component.html', + styleUrls: ['./server-and-agent-list-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + filterStr: string; + agentId: string; + serverList: { [key: string]: IServerAndAgentData[] } = {}; + serverKeyList: string[]; + filteredServerList: { [key: string]: IServerAndAgentData[] }; + filteredServerKeyList: string[]; + funcImagePath: Function; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private webAppSettingDataService: WebAppSettingDataService, + private storeHelperService: StoreHelperService, + private serverAndAgentListDataService: ServerAndAgentListDataService, + private analyticsService: AnalyticsService, + ) {} + ngOnInit() { + this.funcImagePath = this.webAppSettingDataService.getImagePathMakeFunc(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.END_TIME); + }), + switchMap((urlService: NewUrlStateNotificationService) => { + this.agentId = urlService.hasValue(UrlPathId.AGENT_ID) ? urlService.getPathValue(UrlPathId.AGENT_ID) : ''; + return this.serverAndAgentListDataService.getData(); + }) + ).subscribe((res: { [key: string]: IServerAndAgentData[] }) => { + this.serverKeyList = this.filteredServerKeyList = Object.keys(res).sort(); + this.serverList = this.filteredServerList = res; + if (this.agentId) { + this.dispatchAgentData(); + } + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getServerAndAgentQuery(this.unsubscribe).subscribe((query: string) => { + this.filteringServerList(query); + this.changeDetectorRef.detectChanges(); + }); + } + private dispatchAgentData(): void { + this.serverKeyList.forEach((key: string) => { + this.serverList[key].forEach((agent: IServerAndAgentData) => { + if ( this.agentId === agent.agentId ) { + this.storeHelperService.dispatch(new Actions.UpdateAgentInfo(agent)); + } + }); + }); + } + private filteringServerList(query: string): void { + if ( query === '' ) { + this.filteredServerKeyList = this.serverKeyList; + this.filteredServerList = this.serverList; + } else { + const filteredKeyList: string[] = []; + const filteredServerList: { [key: string]: IServerAndAgentData[] } = {}; + this.serverKeyList.forEach((key: string) => { + let hasKey = false; + this.serverList[key].forEach((agent: IServerAndAgentData) => { + if ( agent.agentId.toLowerCase().indexOf(query.toLowerCase()) !== -1 ) { + if ( hasKey === false ) { + filteredKeyList.push(key); + filteredServerList[key] = []; + hasKey = true; + } + filteredServerList[key].push(agent); + } + }); + }); + this.filteredServerKeyList = filteredKeyList.sort(); + this.filteredServerList = filteredServerList; + } + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + onSelectAgent(agentName: string) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.GO_TO_AGENT_INSPECTOR); + this.urlRouteManagerService.moveOnPage({ + url: [ + UrlPath.INSPECTOR, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + agentName + ] + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-data.service.ts new file mode 100644 index 000000000000..f8dd3c969951 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list-data.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService } from 'app/shared/services'; + +@Injectable() +export class ServerAndAgentListDataService { + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService + ) { } + getData(): Observable<{ [key: string]: IServerAndAgentData[] }> { + return this.http.get<{ [key: string]: IServerAndAgentData[] }>('getAgentList.pinpoint', this.makeRequestOptionsArgs()); + } + private makeRequestOptionsArgs(): object { + return { + params: { + application: this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).applicationName, + from: this.newUrlStateNotificationService.getStartTimeToNumber(), + to: this.newUrlStateNotificationService.getEndTimeToNumber() + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list.component.css b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list.component.css new file mode 100644 index 000000000000..c27ad425bdb7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list.component.css @@ -0,0 +1,68 @@ +.l-servers-list { + height: 100%; + padding: 0 10px; + overflow-y: auto; + overflow-x: hidden; +} +.l-servers-list li { + color:#999; + margin: 15px 0 0 0; + font-size: 13px; + font-weight: 600; +} +.l-servers-list li.first-child { + margin:0; +} +.l-servers-list li.first-child:before { + color: #999; + margin: 0 8px 0 0; + content: '\f0ae'; + font-size: 14px; + font-weight: normal; + font-family: FontAwesome; +} +.l-server-name { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.l-server-name i { + float :left; + margin: 2px 4px 0px 0px; +} +.l-agent-list { + margin: 8px 0 0 10px; +} +.l-agent-list li { + color: #333; + margin: 0px 0px; + display: block; + padding: 4px 4px 4px 10px; + font-size: 12px; + font-weight: 400; + align-items: center; +} +.l-agent-list li:hover { + color: #FFF; + cursor: pointer; + background-color: #418CD3; +} +.l-agent-name { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.l-agent-name i { + float: left; + margin: 4px 4px 0px 0px; +} +.l-icon-alert { + float: right; + margin: 0 0 0 13px; + height: 1em; +} +li.active { + box-shadow: 1px 1px 1px 0px rgba(53,61,117,1); +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list.component.html b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list.component.html new file mode 100644 index 000000000000..f1e72894b462 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list.component.html @@ -0,0 +1,18 @@ +
    +
  • +
    {{serverName}}
    +
      +
    • + +
      + {{agent.agentId}} +
      +
    • +
    +
  • +
+
    +
  • + There is no agent. +
  • +
diff --git a/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list.component.ts new file mode 100644 index 000000000000..1da8aa01f005 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-and-agent-list/server-and-agent-list.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-server-and-agent-list', + templateUrl: './server-and-agent-list.component.html', + styleUrls: ['./server-and-agent-list.component.css'] +}) +export class ServerAndAgentListComponent implements OnInit { + @Input() funcImagePath: Function; + @Input() serverKeyList: string[] = []; + @Input() serverList: any = {}; + @Input() agentId: string; + @Output() outSelectAgent: EventEmitter = new EventEmitter(); + constructor() {} + ngOnInit() {} + getIconPath(iconState: number) { + let iconName = ''; + switch (iconState) { + case 200: + case 201: + iconName = 'icon-down'; + break; + case 300: + iconName = 'icon-disconnect'; + break; + case -1: + iconName = 'icon-error'; + break; + default: + break; + } + return this.funcImagePath(iconName); + } + onSelectAgent(agentName: string) { + this.outSelectAgent.emit(agentName); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-list/index.ts b/web/src/main/webapp/v2/src/app/core/components/server-list/index.ts new file mode 100644 index 000000000000..d00c3d4efb2c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-list/index.ts @@ -0,0 +1,24 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +// import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MatTooltipModule } from '@angular/material'; +import { ServerListComponent } from './server-list.component'; +import { ServerListContainerComponent } from './server-list-container.component'; + +@NgModule({ + declarations: [ + ServerListComponent, + ServerListContainerComponent + ], + imports: [ + CommonModule, + // BrowserAnimationsModule, + MatTooltipModule + ], + exports: [ + ServerListContainerComponent + ], + providers: [] +}) +export class ServerListModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/server-list/server-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/server-list/server-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list-container.component.html new file mode 100644 index 000000000000..8cd50314b116 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list-container.component.html @@ -0,0 +1,8 @@ + diff --git a/web/src/main/webapp/v2/src/app/core/components/server-list/server-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list-container.component.ts new file mode 100644 index 000000000000..e5e7b2b2c430 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list-container.component.ts @@ -0,0 +1,47 @@ +import { Component, OnInit, OnDestroy, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; + +import { WebAppSettingDataService, StoreHelperService } from 'app/shared/services'; + +@Component({ + selector: 'pp-server-list-container', + templateUrl: './server-list-container.component.html', + styleUrls: ['./server-list-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ServerListContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + @Output() outSelectAgent: EventEmitter = new EventEmitter(); + @Output() outOpenInspector: EventEmitter = new EventEmitter(); + serverList: any = {}; + agentData: any = {}; + isWas: boolean; + funcImagePath: Function; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + ) { + this.funcImagePath = this.webAppSettingDataService.getImagePathMakeFunc(); + } + ngOnInit() { + this.storeHelperService.getServerListData(this.unsubscribe).subscribe((agentData: any) => { + if ( agentData && agentData['serverList'] ) { + this.serverList = agentData['serverList']; + this.agentData = agentData['agentHistogram']; + this.isWas = agentData['isWas']; + this.changeDetectorRef.detectChanges(); + } + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + onSelectAgent(agentName: string) { + this.outSelectAgent.emit(agentName); + } + onOpenInspector(agentName: string) { + this.outOpenInspector.emit(agentName); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-list/server-list.component.css b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list.component.css new file mode 100644 index 000000000000..4e3f10d5db9f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list.component.css @@ -0,0 +1,97 @@ +.l-servers-list { + padding: 0 10px; +} +.l-server-name { + display: block; + overflow : hidden; + white-space : nowrap; + text-overflow : ellipsis; +} +.l-servers-list li { + font-size: 13px; + color:#333; + font-weight: 600; +} +.l-servers-list li { + margin: 15px 0 0 0; +} +.l-servers-list li.first-child { + margin:0; +} +.l-servers-list li.first-child:before { + font-family: FontAwesome; + content: '\f0ae'; + font-size: 14px; + margin: 0 8px 0 0; + color: #999; + font-weight: normal; +} +.l-servers-list li .l-category { + margin: 10px 0 0 27px; +} +.l-servers-list li .l-category span { + display: inline-block; + padding: 2px 5px; + font-size: 10px; + color: #fff; + font-weight: 600; +} +.l-servers-list li ul { + margin: 14px 0 0 27px; +} +.l-servers-list li ul li { + font-size: 12px; + color: #666; + font-weight: 400; + height: 20px; + margin: 5px 0px; + color: #999; +} +.l-servers-list li ul li input { + margin: 0 5px; +} +.l-servers-list li ul li div { + float: right; +} +.l-servers-list li ul li label { + display: block; + overflow : hidden; + white-space : nowrap; + text-overflow : ellipsis; +} +.l-servers-list li ul li:hover { + background:#f1f3f7; +} +.l-text-box { + padding: 3px 5px; + font-size: 10px; + color: #FFF; + margin-left: 13px; + background-color: #4b99e3; +} +.l-icon-alert { + height: 1em; +} +.l-servers-list ul li { + height: 30px; +} +.l-servers-list ul span { + cursor: pointer; +} +i.fas { + margin-right: 4px; +} +.l-category { + display: inline-block; + padding: 2px 5px; + font-size: 10px; + color: #fff; + font-weight: 600; + cursor: pointer; +} +.l-category.l-green { + background:#23c6c8; +} +.l-category.l-blue { + background:#5597d5; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-list/server-list.component.html b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list.component.html new file mode 100644 index 000000000000..c834bdb9d05a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list.component.html @@ -0,0 +1,17 @@ +
    +
  • +
    {{ serverName }}
    +
      +
    • +
      + + +
      + +
    • +
    +
  • +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-list/server-list.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list.component.ts new file mode 100644 index 000000000000..b10e66f99e19 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-list/server-list.component.ts @@ -0,0 +1,36 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-server-list', + templateUrl: './server-list.component.html', + styleUrls: ['./server-list.component.css'], +}) +export class ServerListComponent implements OnInit { + @Input() serverList: any = {}; + @Input() agentData: any = {}; + @Input() isWas: boolean; + @Input() funcImagePath: Function; + @Output() outSelectAgent: EventEmitter = new EventEmitter(); + @Output() outOpenInspector: EventEmitter = new EventEmitter(); + isChanged = false; + constructor() {} + ngOnInit() {} + getServerKeys(): string[] { + return Object.keys(this.serverList).sort(); + } + getAgentKeys(serverName: string): string[] { + return Object.keys(this.serverList[serverName]['instanceList']).sort(); + } + hasError(agentName: string): boolean { + return this.agentData[agentName] && this.agentData[agentName]['Error'] > 0; + } + getAlertImgPath(): string { + return this.funcImagePath('icon-alert'); + } + onSelectAgent(agentName: string) { + this.outSelectAgent.emit(agentName); + } + onOpenInspector(agentName: string): void { + this.outOpenInspector.emit(agentName); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/index.ts b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/index.ts new file mode 100644 index 000000000000..25f0f1e020da --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/index.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { ServerMapContextPopupContainerComponent } from 'app/core/components/server-map-context-popup/server-map-context-popup-container.component'; +import { ServerMapContextPopupComponent } from 'app/core/components/server-map-context-popup/server-map-context-popup.component'; + +@NgModule({ + declarations: [ + ServerMapContextPopupContainerComponent, + ServerMapContextPopupComponent + ], + imports: [ + SharedModule + ], + exports: [], + entryComponents: [ + ServerMapContextPopupContainerComponent + ], + providers: [], +}) +export class ServerMapContextPopupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup-container.component.css new file mode 100644 index 000000000000..7f26ddcb5503 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup-container.component.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup-container.component.html new file mode 100644 index 000000000000..6d7b441729d3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup-container.component.html @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup-container.component.ts new file mode 100644 index 000000000000..986badbb2be2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup-container.component.ts @@ -0,0 +1,44 @@ +import { Component, OnInit, Input, Output, EventEmitter, AfterViewInit } from '@angular/core'; + +import { ServerMapInteractionService } from 'app/core/components/server-map/server-map-interaction.service'; +import { ServerMapData } from 'app/core/components/server-map/class'; +import { DynamicPopup } from 'app/shared/services/dynamic-popup.service'; + +@Component({ + selector: 'pp-server-map-context-popup-container', + templateUrl: './server-map-context-popup-container.component.html', + styleUrls: ['./server-map-context-popup-container.component.css'] +}) +export class ServerMapContextPopupContainerComponent implements OnInit, AfterViewInit, DynamicPopup { + @Input() data: ServerMapData; + @Input() coord: ICoordinate; + @Output() outCreated = new EventEmitter(); + @Output() outClose = new EventEmitter(); + + constructor( + private serverMapInteractionService: ServerMapInteractionService, + ) {} + + ngOnInit() {} + ngAfterViewInit() { + this.outCreated.emit(this.coord); + } + + onInputChange({coord}: {coord: ICoordinate}): void { + this.outCreated.emit(coord); + } + + onClickMergeCheck(mergeState: IServerMapMergeState): void { + this.serverMapInteractionService.setMergeState(mergeState); + this.outClose.emit(); + } + + onClickRefresh(): void { + this.serverMapInteractionService.setRefresh(); + this.outClose.emit(); + } + + onClickOutside(): void { + this.outClose.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup.component.css new file mode 100644 index 000000000000..afa53b60d74c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup.component.css @@ -0,0 +1,40 @@ +:host { + display: block; +} +ul { + width: 100%; + margin: 0 0; + padding: 0 0; + list-style-type: none; +} +li { + padding: 10px; + list-style: none; +} +.l-one-depth-list > li { + border-bottom: 1px solid #E5E5E5; +} + +.l-one-depth-list > li > div { + padding: 2px 4px; +} + +.l-one-depth-list > li:last-of-type { + border-bottom: none; +} +li.l-selectable-command { + cursor: pointer; +} +li.l-selectable-command:hover { + color: #FFF; + background-color: #4B99E3; +} +.l-two-depth-list { + padding-top: 10px; +} +.l-two-depth-list li { + padding: 2px 4px 2px 14px; +} +.fas { + margin-right: 4px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup.component.html new file mode 100644 index 000000000000..0eab9a24f83f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup.component.html @@ -0,0 +1,11 @@ +
    +
  • +
    Merge
    +
      +
    • + {{key}} +
    • +
    +
  • +
  • Refresh
  • +
diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup.component.ts new file mode 100644 index 000000000000..460b79b298f5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-context-popup/server-map-context-popup.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +import { ServerMapData } from 'app/core/components/server-map/class'; + +@Component({ + selector: 'pp-server-map-context-popup', + templateUrl: './server-map-context-popup.component.html', + styleUrls: ['./server-map-context-popup.component.css'] +}) +export class ServerMapContextPopupComponent implements OnInit { + @Input() data: ServerMapData; + @Output() outClickMergeCheck = new EventEmitter(); + @Output() outClickRefresh = new EventEmitter(); + + mergeNodeStateMap: { [key: string]: boolean }; + objectKeys = Object.keys; + + constructor() {} + ngOnInit() { + this.mergeNodeStateMap = this.data.getMergeState(); + } + + onClickMergeCheck(name: string): void { + this.mergeNodeStateMap[name] = !this.mergeNodeStateMap[name]; + this.outClickMergeCheck.emit({ + name, + state: this.mergeNodeStateMap[name] + }); + } + onClickRefresh(): void { + this.outClickRefresh.emit(); + } + getClassState(key: string): any { + return this.mergeNodeStateMap[key] ? { + fas: true, + 'fa-check-square': true + } : { + far: true, + 'fa-square': true + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-options/index.ts b/web/src/main/webapp/v2/src/app/core/components/server-map-options/index.ts new file mode 100644 index 000000000000..113d5df62677 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-options/index.ts @@ -0,0 +1,20 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { ServerMapOptionsComponent } from './server-map-options.component'; +import { ServerMapOptionsContainerComponent } from './server-map-options-container.component'; + +@NgModule({ + declarations: [ + ServerMapOptionsComponent, + ServerMapOptionsContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + ServerMapOptionsContainerComponent + ], + providers: [] +}) +export class ServerMapOptionsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options-container.component.css new file mode 100644 index 000000000000..bc32c5675c9f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options-container.component.css @@ -0,0 +1,4 @@ +:host { + display: block; + margin-right: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options-container.component.html new file mode 100644 index 000000000000..bf0dd79b6d2d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options-container.component.html @@ -0,0 +1,11 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options-container.component.ts new file mode 100644 index 000000000000..cfe9bd2bfb67 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options-container.component.ts @@ -0,0 +1,91 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, tap, map } from 'rxjs/operators'; + +import { + WebAppSettingDataService, + NewUrlStateNotificationService, + UrlRouteManagerService, + AnalyticsService, + TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { UrlQuery, UrlPathId } from 'app/shared/models'; + +@Component({ + selector: 'pp-server-map-options-container', + templateUrl: './server-map-options-container.component.html', + styleUrls: ['./server-map-options-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ServerMapOptionsContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + + funcImagePath: Function; + hiddenComponent: boolean; + inboundList: string[]; + outboundList: string[]; + selectedInbound: string; + selectedOutbound: string; + selectedBidirectional: boolean; + selectedWasOnly: boolean; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private analyticsService: AnalyticsService + ) { + this.funcImagePath = this.webAppSettingDataService.getImagePathMakeFunc(); + } + + ngOnInit() { + this.inboundList = this.webAppSettingDataService.getInboundList(); + this.outboundList = this.webAppSettingDataService.getOutboundList(); + + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + tap((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.APPLICATION, UrlPathId.PERIOD, UrlPathId.END_TIME)) { + this.hiddenComponent = false; + } else { + this.hiddenComponent = true; + } + }), + map((urlService: NewUrlStateNotificationService) => { + return { + inbound: urlService.hasValue(UrlQuery.INBOUND) ? urlService.getQueryValue(UrlQuery.INBOUND) : this.webAppSettingDataService.getUserDefaultInbound(), + outbound: urlService.hasValue(UrlQuery.OUTBOUND) ? urlService.getQueryValue(UrlQuery.OUTBOUND) : this.webAppSettingDataService.getUserDefaultOutbound(), + bidirectional: urlService.hasValue(UrlQuery.BIDIRECTIONAL) ? urlService.getQueryValue(UrlQuery.BIDIRECTIONAL) === 'true' : false, + wasOnly: urlService.hasValue(UrlQuery.WAS_ONLY) ? urlService.getQueryValue(UrlQuery.WAS_ONLY) === 'true' : false + }; + }) + ).subscribe(({inbound, outbound, bidirectional, wasOnly}: {inbound: string, outbound: string, bidirectional: boolean, wasOnly: boolean}) => { + this.selectedInbound = inbound; + this.selectedOutbound = outbound; + this.selectedBidirectional = bidirectional; + this.selectedWasOnly = wasOnly; + this.changeDetectorRef.detectChanges(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + onChangeBound(options: { inbound: string, outbound: string, wasOnly: boolean, bidirectional: boolean }): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SET_SERVER_MAP_OPTION, `inbound: ${options.inbound}, outbound: ${options.outbound}, wasOnly: ${options.wasOnly}, bidirectional: ${options.bidirectional}`); + this.urlRouteManagerService.moveOnPage({ + url: [ + this.newUrlStateNotificationService.getStartPath(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ], + queryParam: { + [UrlQuery.INBOUND]: options.inbound, + [UrlQuery.OUTBOUND]: options.outbound, + [UrlQuery.WAS_ONLY]: options.wasOnly, + [UrlQuery.BIDIRECTIONAL]: options.bidirectional + } + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options.component.css new file mode 100644 index 000000000000..e6375865bf3e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options.component.css @@ -0,0 +1,130 @@ +:host { + display: block; + position: relative; + width: 180px; +} +.fa-sign-in-alt, .fa-sign-out-alt { + color: #33b692; +} +button { + outline: none; +} +.l-dropdown-button { + width: 100%; + height: 32px; + border: 1px solid #4488CB; + padding: 4px 0 4px 0; + display: flex; + font-size: 13px; + text-align: left; + align-items: center; + background-color: #fff; +} +.l-dropdown-button .fa-angle-down { + font-size: 15px; + display: inline-block; + margin-left: 16px; + color: #33b692; +} + +.l-bound-text { + display: inline-block; + font-size: 14px; + margin-left: 11px; + color: #333; +} +.l-bound-double-text { + color: #DDD; + font-size: 8px; + text-align: center; + font-weight: 600; +} +.l-bound-double-text.selected { + color: #469ae4; +} + +.l-dropdown-menu-wrapper { + position: absolute; + top: 32px; + left: 0; + z-index: 9999; + display: flex; + flex-wrap: wrap; + border: 1px solid #4488CB; + background-color: #fff; + width: 180px; +} + +.l-inbound-list { + border-right: 1px solid #e5e8f0; +} + +.l-inbound-list, .l-outbound-list { + width: 50%; + text-align: center; +} + +.l-bound-list-item { + font-weight: 400; + color: #333; + font-size: 13px; + padding: 5px 10px; + cursor: pointer; +} + +.l-bound-title { + padding: 5px 10px; + color: #333; + background-color: #edf2f8; + font-weight: 600 !important; + font-size: 12px; + border-bottom: 1px solid #e5e8f0; +} +.l-bidirectional-title { + letter-spacing: -0.5px; +} + +.l-bound-list-item:hover { + background-color: #eee; +} + +.l-bound-list-item.active { + color: #edf2f8; + background-color: #4b99e3; +} +.l-bidirectional-selector { + text-align: center; + color: #469ae4; + margin: 10px; + cursor: pointer; +} +.l-was-only-selector { + text-align: center; + color: #DDD; + margin: 12px 10px 10px 10px; + font-size: 20px; + cursor:pointer; +} +.l-was-only-selector .selected { + color: #469ae4; +} + +.l-button-group-wrapper { + width: 100%; + border-top: 1px solid #e5e8f0; + padding: 6px; + text-align: right; +} +.l-apply-button { + background-color: #4a8fd2; + border: 1px solid #4a8fd2; + padding: 5px 11px; + margin-left: 2px; + color: #fff; +} + +.l-cancel-button { + border: 1px solid #e5e8f0; + padding: 5px 7px; + color: #333; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options.component.html new file mode 100644 index 000000000000..69f9ceb2efd7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options.component.html @@ -0,0 +1,36 @@ +
+ + +
+ \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options.component.ts new file mode 100644 index 000000000000..a6e17e651141 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-options/server-map-options.component.ts @@ -0,0 +1,92 @@ +import { Component, EventEmitter, Input, Output, OnInit, OnChanges, SimpleChanges } from '@angular/core'; + +@Component({ + selector: 'pp-server-map-options', + templateUrl: './server-map-options.component.html', + styleUrls: ['./server-map-options.component.css'] +}) +export class ServerMapOptionsComponent implements OnInit, OnChanges { + hideList = true; + prevWasOnly: boolean; + prevBidirectional: boolean; + prevSelectedInbound: string; + prevSelectedOutbound: string; + bidirectionalPath: string; + @Input() funcImagePath: Function; + @Input() selectedWasOnly: boolean; + @Input() selectedBidirectional: boolean; + @Input() selectedInbound: string; + @Input() selectedOutbound: string; + @Input() inboundList: string[]; + @Input() outboundList: string[]; + @Output() outSelected: EventEmitter<{ + inbound: string, + outbound: string, + wasOnly: boolean, + bidirectional: boolean + }> = new EventEmitter(); + + constructor() {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if (changes['selectedBidirectional']) { + this.prevBidirectional = this.selectedBidirectional = changes['selectedBidirectional'].currentValue; + } + if (changes['selectedWasOnly']) { + this.prevWasOnly = this.selectedWasOnly = changes['selectedWasOnly'].currentValue; + } + if (changes['selectedInbound']) { + this.prevSelectedInbound = this.selectedInbound = changes['selectedInbound'].currentValue; + } + if (changes['selectedOutbound']) { + this.prevSelectedOutbound = this.selectedOutbound = changes['selectedOutbound'].currentValue; + } + } + private close(): void { + this.hideList = true; + } + getBidirectional(): string { + return this.funcImagePath('bidirect_' + (this.selectedBidirectional ? 'on' : 'off')); + } + isWasOnlySelected(): boolean { + return this.selectedWasOnly; + } + onChangeWasOnly(): void { + this.selectedWasOnly = !this.selectedWasOnly; + } + onChangeBidirectional(): void { + this.selectedBidirectional = !this.selectedBidirectional; + } + onSelectInbound(inbound: string): void { + this.selectedInbound = inbound; + } + + onSelectOutbound(outbound: string): void { + this.selectedOutbound = outbound; + } + onApply(): void { + if (!(this.selectedInbound === this.prevSelectedInbound && this.selectedOutbound === this.prevSelectedOutbound && this.selectedWasOnly === this.prevWasOnly && this.selectedBidirectional === this.prevBidirectional)) { + this.outSelected.emit({ + inbound: this.selectedInbound, + outbound: this.selectedOutbound, + wasOnly: this.selectedWasOnly, + bidirectional: this.selectedBidirectional + }); + } + this.close(); + } + + onCancel(): void { + this.selectedWasOnly = this.prevWasOnly; + this.selectedBidirectional = this.prevBidirectional; + this.selectedInbound = this.prevSelectedInbound; + this.selectedOutbound = this.prevSelectedOutbound; + this.close(); + } + toggleList(): void { + this.hideList = !this.hideList; + } + onClose(): void { + this.onCancel(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/index.ts b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/index.ts new file mode 100644 index 000000000000..cc8b5a37a936 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/index.ts @@ -0,0 +1,20 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ServerMapSearchResultViewerComponent } from './server-map-search-result-viewer.component'; +import { ServerMapSearchResultViewerContainerComponent } from './server-map-search-result-viewer-container.component'; + +@NgModule({ + declarations: [ + ServerMapSearchResultViewerComponent, + ServerMapSearchResultViewerContainerComponent + ], + imports: [ + CommonModule + ], + exports: [ + ServerMapSearchResultViewerContainerComponent + ], + providers: [] +}) +export class ServerMapSearchResultViewerModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer-container.component.html new file mode 100644 index 000000000000..e5c0bb15ea8b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer-container.component.html @@ -0,0 +1,7 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer-container.component.ts new file mode 100644 index 000000000000..f93dd123e837 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer-container.component.ts @@ -0,0 +1,72 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Subject, combineLatest } from 'rxjs'; +import { distinctUntilChanged, filter, takeUntil } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; +import { ServerMapInteractionService } from 'app/core/components/server-map/server-map-interaction.service'; +import { ServerMapSearchResultViewerComponent } from './server-map-search-result-viewer.component'; + +@Component({ + selector: 'pp-server-map-search-result-viewer-container', + templateUrl: './server-map-search-result-viewer-container.component.html', + styleUrls: ['./server-map-search-result-viewer-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ServerMapSearchResultViewerContainerComponent implements OnInit { + private unsubscribe: Subject = new Subject(); + private minLength = 3; + i18nText: { [key: string]: string } = {}; + hiddenComponent = true; + searchResultList: IApplication[] = []; + userInput: Subject = new Subject(); + + constructor( + private changeDetectorRef: ChangeDetectorRef, + private translateService: TranslateService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private serverMapInteractionService: ServerMapInteractionService, + private analyticsService: AnalyticsService + ) {} + ngOnInit() { + this.getI18NText(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.hiddenComponent = urlService.hasValue(UrlPathId.PERIOD, UrlPathId.END_TIME) ? false : true; + this.changeDetectorRef.detectChanges(); + }); + this.userInput.pipe( + distinctUntilChanged(), + filter((query: string) => { + return query.length >= this.minLength; + }) + ).subscribe((query: string) => { + this.serverMapInteractionService.setSearchWord(query); + }); + this.serverMapInteractionService.onSearchResult$.subscribe((resultList: IApplication[]) => { + this.searchResultList = resultList; + this.changeDetectorRef.detectChanges(); + }); + } + private getI18NText() { + combineLatest( + this.translateService.get('MAIN.SEARCH_SERVER_MAP_PLACE_HOLDER'), + this.translateService.get('MAIN.EMPTY_RESULT') + ).subscribe((i18n: string[]) => { + this.i18nText = { + [ServerMapSearchResultViewerComponent.I18NTEXT.PLACE_HOLDER]: i18n[0], + [ServerMapSearchResultViewerComponent.I18NTEXT.EMPTY_RESULT]: i18n[1] + }; + }); + } + onSearch($event: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SEARCH_NODE); + this.userInput.next($event); + } + onSelectApplication(app: IApplication): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_APPLICATION_IN_SEARCH_RESULT); + this.serverMapInteractionService.setSelectedApplication(app.getKeyStr()); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer.component.css new file mode 100644 index 000000000000..8722ec0d7084 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer.component.css @@ -0,0 +1,81 @@ +.l-search-group { + background: #fff; + height: 32px; + width: 183px; + color:#b3b3b4; + position:relative; + margin-right: 10px; +} +.l-search-group input { + width: 100%; + height: 100%; + border: 1px solid #d7dde4; + padding: 0 10px 0 10px; +} +.l-search-group input:focus { + border-color:#469ae4; + box-shadow: 0 0 10px -3px #4b99e3; + outline: none; +} +.l-search-group button { + position:absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); +} +.fas { + color:#a8acb5; + font-size:18px; +} +.l-search-div { + position: absolute; + top: 35px; + right: 0px; + width: 231px; + height: 472px; +} +.l-search-div > div { + background-color: #3e506b; + border-bottom: 1px solid #D0D7DF; + padding: 4px 0px 4px 10px; + color: #FFF; +} +.l-search-div > div button { + position: absolute; + top: 13px; + color: white; +} +.l-search-div ul { + top: 26px; + height: 446px; + padding: 15px 10px; +} +.l-search-div li { + cursor: pointer; + padding: 0px 1px; +} +.l-search-div li:hover, .l-search-div li.selected { + color: #FFF; + background-color: #469AE4; +} +.l-search-list { + padding:15px; + background:#f8fafc; + position:absolute; + width:231px; + top: 35px; + right: 0; + overflow-y: auto; + height: 472px; +} +.l-search-list li { + color:#8a939e; + font-size:12px; + margin:8px 0 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.l-search-list li:first-child { + margin:0; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer.component.html new file mode 100644 index 000000000000..acb762155cea --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer.component.html @@ -0,0 +1,13 @@ +
+ +
+
+ Result +
+
    +
  • {{app.applicationName}}({{app.serviceType}})
  • +
  • {{i18nText.EMPTY_RESULT}}
  • +
+
+ +
diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer.component.ts new file mode 100644 index 000000000000..40fe842de36e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map-search-result-viewer/server-map-search-result-viewer.component.ts @@ -0,0 +1,47 @@ +import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; + +@Component({ + selector: 'pp-server-map-search-result-viewer', + templateUrl: './server-map-search-result-viewer.component.html', + styleUrls: ['./server-map-search-result-viewer.component.css'] +}) +export class ServerMapSearchResultViewerComponent implements OnInit, OnChanges { + static I18NTEXT = { + PLACE_HOLDER: 'PLACE_HOLDER', + EMPTY_RESULT: 'EMPTY_RESULT' + }; + selectedApplication: IApplication; + hiddenList = true; + listCountZero = false; + @Input() i18nText = { + [ServerMapSearchResultViewerComponent.I18NTEXT.PLACE_HOLDER]: 'Favorite List' + }; + @Input() hiddenComponent = true; + @Input() applicationResultList: IApplication[]; + @Output() outSearch: EventEmitter = new EventEmitter(); + @Output() outSelectApplication: EventEmitter = new EventEmitter(); + constructor() {} + ngOnChanges(changes: SimpleChanges) { + if (changes['applicationResultList']) { + if (changes['applicationResultList'].firstChange) { + this.hiddenList = true; + } else { + this.hiddenList = false; + this.listCountZero = this.applicationResultList.length === 0; + } + } + } + ngOnInit() {} + onSearch(query: string): void { + if (query !== '') { + this.outSearch.emit(query); + } + } + onCloseResult() { + this.hiddenList = true; + } + onSelectApplication(application: IApplication): void { + this.selectedApplication = application; + this.outSelectApplication.emit(application); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/index.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/index.ts new file mode 100644 index 000000000000..180cbf58d3a4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/index.ts @@ -0,0 +1,10 @@ +export * from './link-group.class'; +export * from './merge-server-map-data.class'; +export * from './multi-connect-node-group.class'; +export * from './node-group.class'; +export * from './server-map-data.class'; +export * from './server-map-diagram.class'; +export * from './server-map-factory'; +export * from './server-map-template-with-gojs.class'; +export * from './server-map-template'; +export * from './server-map-theme'; diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/link-group.class.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/link-group.class.ts new file mode 100644 index 000000000000..ddc2a6b676b8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/link-group.class.ts @@ -0,0 +1,40 @@ +export class LinkGroup { + SEPERATOR = '~'; + linkData: any; + constructor(private fromNodeKey: string, private toNodeKey: string) { + this.init(); + } + init() { + this.linkData = { + 'key': this.fromNodeKey + this.SEPERATOR + this.toNodeKey, + 'from': this.fromNodeKey, + 'to': this.toNodeKey, + 'hasAlert': false, + 'slowCount': 0, + 'histogram': {}, + 'sourceInfo': {}, + 'targetInfo': [], + 'totalCount': 0, + 'errorCount': 0 + }; + } + addLinkData(link: any): void { + this.linkData.totalCount += link.totalCount; + this.linkData.errorCount += link.errorCount; + this.linkData.slowCount += link.slowCount; + this.linkData.sourceInfo = link.sourceInfo; + if (link.hasAlert) { + this.linkData.hasAlert = link.hasAlert; + } + this.linkData['targetInfo'].push(link); + } + sortLinkData(): LinkGroup { + this.linkData['targetInfo'].sort((v1, v2) => { + return v2.totalCount - v1.totalCount; + }); + return this; + } + getLinkGroupData(): any { + return this.linkData; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/merge-server-map-data.class.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/merge-server-map-data.class.ts new file mode 100644 index 000000000000..e91f642c0cc4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/merge-server-map-data.class.ts @@ -0,0 +1,134 @@ +export class MergeServerMapData { + static mergeNodeData(currentNodeData: INodeInfo, newNodeData: INodeInfo): void { + const old = currentNodeData; + const neo = newNodeData; + + old.hasAlert = neo.hasAlert; + old.slowCount += neo.slowCount; + old.errorCount += neo.errorCount; + old.totalCount += neo.totalCount; + old.instanceCount = Math.max(old.instanceCount, neo.instanceCount); + old.instanceErrorCount = Math.max(old.instanceErrorCount, neo.instanceErrorCount); + + mergeAgentIds(old, neo); + mergeHistogram(old, neo); + mergeAgentHistogram(old, neo); + mergeTimeSeriesHistogram(old, neo); + mergeAgentTimeSeriesHistogramByType(old, neo, 'agentTimeSeriesHistogram'); + mergeServerList(old, neo); + } + static mergeLinkData(currentLinkData: ILinkInfo, newLinkData: ILinkInfo): void { + const old = currentLinkData; + const neo = newLinkData; + + old.hasAlert = neo.hasAlert; + old.slowCount += neo.slowCount; + old.errorCount += neo.errorCount; + old.totalCount += neo.totalCount; + + mergeHistogram(old, neo); + mergeTimeSeriesHistogram(old, neo); + mergeAgentTimeSeriesHistogramByType(old, neo, 'sourceTimeSeriesHistogram'); + mergeHistogramByType(old, neo, 'sourceHistogram'); + mergeHistogramByType(old, neo, 'targetHistogram'); + } +} +function mergeAgentIds(old: INodeInfo, neo: INodeInfo): void { + neo.agentIds.forEach((agentId: string) => { + if (old.agentIds.indexOf(agentId) === -1) { + old.agentIds.push(agentId); + } + }); +} +function mergeHistogram(old: INodeInfo | ILinkInfo, neo: INodeInfo | ILinkInfo): void { + if (neo.histogram) { + if (old.histogram) { + mergeHistogramType(old.histogram, neo.histogram); + } else { + old.histogram = neo.histogram; + } + } +} +function mergeHistogramType(oldHistogram: IResponseTime | IResponseMilliSecondTime, neoHistogram: IResponseTime | IResponseMilliSecondTime): void { + if (neoHistogram) { + Object.keys(neoHistogram).forEach((key: string) => { + oldHistogram[key] += neoHistogram[key]; + }); + } +} +function mergeAgentHistogram(old: INodeInfo, neo: INodeInfo): void { + Object.keys(neo.agentHistogram).forEach((key: string) => { + if (old.agentHistogram[key]) { + mergeHistogramType(old.agentHistogram[key], neo.agentHistogram[key]); + } else { + old.agentHistogram[key] = neo.agentHistogram[key]; + } + }); +} +// 내부 값의 순서가 보장되어야한 유의미한 코드 +function mergeTimeSeriesHistogram(old: INodeInfo | ILinkInfo, neo: INodeInfo | ILinkInfo): void { + if (neo.timeSeriesHistogram) { + if (old.timeSeriesHistogram) { + neo.timeSeriesHistogram.forEach((obj: any, outerIndex: number) => { + obj.values.forEach((chartValue: any, innerIndex: number) => { + old.timeSeriesHistogram[outerIndex].values[innerIndex][1] += chartValue[1]; + }); + }); + } else { + old.timeSeriesHistogram = neo.timeSeriesHistogram; + } + } +} +function mergeAgentTimeSeriesHistogramByType(old: INodeInfo | ILinkInfo, neo: INodeInfo | ILinkInfo, type: string): void { + if (neo[type]) { + if (old[type]) { + Object.keys(neo[type]).forEach((agentId: string) => { + if (old[type][agentId]) { + neo[type][agentId].forEach((obj: any, outerIndex: number) => { + obj.values.forEach((chartValue: any, innerIndex: number) => { + old[type][agentId][outerIndex].values[innerIndex][1] += chartValue[1]; + }); + }); + } else { + old[type][agentId] = neo[type][agentId]; + } + }); + } else { + old[type] = neo[type]; + } + } +} +function mergeServerList(old: INodeInfo, neo: INodeInfo): void { + if (neo.serverList) { + if (old.serverList) { + Object.keys(neo.serverList).forEach((key: string) => { + if (old.serverList[key]) { + Object.keys(neo.serverList[key].instanceList).forEach((instanceKey: string) => { + if ((instanceKey in old.serverList[key].instanceList) === false) { + old.serverList[key].instanceList[instanceKey] = neo.serverList[key].instanceList[instanceKey]; + } + }); + } else { + old.serverList[key] = neo.serverList[key]; + } + }); + } else { + old.serverList = neo.serverList; + } + } +} +function mergeHistogramByType(old: ILinkInfo, neo: ILinkInfo, histogramType: string): void { + if (old[histogramType]) { + Object.keys(neo[histogramType]).forEach((key: string) => { + if (old[histogramType][key]) { + Object.keys(neo[histogramType][key]).forEach((histogramKey: string) => { + old[histogramType][key][histogramKey] += neo[histogramType][key][histogramKey]; + }); + } else { + old[histogramType][key] = neo[histogramType][key]; + } + }); + } else { + old[histogramType] = neo[histogramType]; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/multi-connect-node-group.class.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/multi-connect-node-group.class.ts new file mode 100644 index 000000000000..af75157af158 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/multi-connect-node-group.class.ts @@ -0,0 +1,120 @@ +import { NodeGroup } from './node-group.class'; + +interface ShortCutNode { + key: string; + isWas: boolean; + category: string; + serviceType: string; + mergedNodes: Array; + isAuthorized: boolean; + instanceCount: number; + topCountNodes: Array; + applicationName: string; + mergedSourceNodes: Array; + +} +export class MultiConnectNodeGroup extends NodeGroup { + // KEY_SEPERATOR: string = '^'; + // SEPERATOR: string = '_'; + NAME_PREFIX = 'MULTI_MERGE'; + // GROUP_POSTFIX: string = 'GROUP'; + // TOP_LIST_MAX_COUNT: number = 3; + // applicationName: string; + // groupKey: string; + // groupType: string; + // nodeData: any; + nodeData: ShortCutNode; + subNodeGroup: Array; + subNodeGroupMap: any; + constructor(protected type: string) { + super(type); + // this.init(); + } + init() { + super.init(); + // this.applicationName = this.NAME_PREFIX + this.SEPERATOR + this.randomValue(); + // this.groupType = this.type + this.SEPERATOR + this.GROUP_POSTFIX; + // this.groupKey = this.groupType + this.KEY_SEPERATOR + this.applicationName; + this.subNodeGroup = []; + this.subNodeGroupMap = {}; + } + initTemplateSet() { + this.nodeData = { + 'key': this.groupKey, + 'isWas': false, + 'category': this.groupType, + 'serviceType': this.groupType, + 'mergedNodes': [], + 'isAuthorized': false, + 'instanceCount': 0, + 'topCountNodes': [], + 'applicationName': this.applicationName, + 'mergedSourceNodes': [] + }; + } + // private randomValue(): string { + // return Math.floor(Math.random() * 10000000).toString(); + // } + // private setTopCountNodes(): void { + // this.nodeData['topCountNodes'].length = 0; + // this.nodeData['topCountNodes'].push({ + // 'applicationName': `Total : ${this.nodeData['mergedNodes'].length}`, + // 'totalCount': this.nodeData['mergedNodes'].reduce((preValue, nowNode) => { return preValue + nowNode.totalCount; }, 0), + // 'tableHeader': true + // }); + // for (let i = 0 ; i < Math.min(this.nodeData['mergedNodes'].length, this.TOP_LIST_MAX_COUNT) ; i++ ) { + // this.nodeData['topCountNodes'].push(this.nodeData['mergedNodes'][i]); + // } + // if ( this.nodeData['mergedNodes'].length > this.TOP_LIST_MAX_COUNT ) { + // this.nodeData['topCountNodes'].push({ + // 'applicationName': '...', + // 'totalCount': '' + // }); + // } + // } + // addNodeData(node: any): void { + // delete node.category; + // this.nodeData['instanceCount'] += node.instanceCount; + // this.nodeData['mergedNodes'].push(node); + // } + addSubNodeGroup(key: string): void { + const subNodeGroup = { + group: [], + isLast: false, + applicationName: key + }; + this.subNodeGroupMap[key] = subNodeGroup; + this.subNodeGroup.push(subNodeGroup); + } + addSubNodeGroupData(key: string, link: any): void { + this.subNodeGroupMap[key].group.push({ + key: link.to, + hasAlert: link.hasAlert || false, + totalCount: link.totalCount, + serviceType: link.serviceType, + applicationName: link.targetInfo.applicationName + }); + } + sortNodeData(): MultiConnectNodeGroup { + function fnSort(v1, v2) { + return v2.totalCount - v1.totalCount; + } + this.nodeData['mergedNodes'].sort(fnSort); + this.subNodeGroup.sort(fnSort); + this.subNodeGroup.forEach((subGroup) => { + subGroup['group'].sort(fnSort); + }); + return this; + } + // getGroupKey(): string{ + // return this.groupKey; + // } + // getGroupServiceType(): string { + // return this.groupType; + // } + + // getNodeGroupData(): any { + // this.setTopCountNodes(); + // return this.nodeData; + // } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/node-group.class.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/node-group.class.ts new file mode 100644 index 000000000000..eb8da2525d5d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/node-group.class.ts @@ -0,0 +1,99 @@ +interface ShortCutNode { + key: string; + category: string; + serviceType: string; + mergedNodes: Array; + instanceCount: number; + topCountNodes: Array; + applicationName: string; +} + +const SPECIAL_STR = { + KEY_SEPERATOR: '^', + SEPERATOR: '_', + NAME_PREFIX: 'MERGE', + GROUP_POSTFIX: 'GROUP' +}; +export class NodeGroup { + TOP_LIST_MAX_COUNT = 3; + applicationName: string; + groupKey: string; + groupType: string; + nodeData: ShortCutNode; + static isGroupKey(key: string): boolean { + return new RegExp( + '.*' + + '\\' + SPECIAL_STR.SEPERATOR + + SPECIAL_STR.GROUP_POSTFIX + + '\\' + SPECIAL_STR.KEY_SEPERATOR + + SPECIAL_STR.NAME_PREFIX + + '\\' + SPECIAL_STR.SEPERATOR + + '\\' + 'd{7}$', + 'g' + ).test(key); + } + constructor(protected type: string) { + this.init(); + } + init() { + this.applicationName = SPECIAL_STR.NAME_PREFIX + SPECIAL_STR.SEPERATOR + this.randomValue(); + this.groupType = this.type + SPECIAL_STR.SEPERATOR + SPECIAL_STR.GROUP_POSTFIX; + this.groupKey = this.groupType + SPECIAL_STR.KEY_SEPERATOR + this.applicationName; + this.initTemplateSet(); + } + protected initTemplateSet() { + this.nodeData = { + 'key': this.groupKey, + 'category': this.groupType, + 'mergedNodes': [], + 'serviceType': this.groupType, + 'instanceCount': 0, + 'topCountNodes': [], + 'applicationName': this.applicationName + }; + } + protected randomValue(): string { + return Math.random().toString().slice(2, 9); + } + protected setTopCountNodes(): void { + this.nodeData['topCountNodes'].length = 0; + this.nodeData['topCountNodes'].push({ + 'applicationName': `Total: ${this.nodeData['mergedNodes'].length}`, + 'totalCount': this.nodeData['mergedNodes'].reduce((preValue, nowNode) => { return preValue + nowNode.totalCount; }, 0), + 'tableHeader': true + }); + for (let i = 0; i < Math.min(this.nodeData['mergedNodes'].length, this.TOP_LIST_MAX_COUNT); i++) { + this.nodeData['topCountNodes'].push(this.nodeData['mergedNodes'][i]); + } + if (this.nodeData['mergedNodes'].length > this.TOP_LIST_MAX_COUNT) { + this.nodeData['topCountNodes'].push({ + 'applicationName': '...', + 'totalCount': '' + }); + } + } + addNodeData(node: any): void { + delete node.category; + this.nodeData['instanceCount'] += node.instanceCount; + this.nodeData['mergedNodes'].push(node); + } + sortNodeData(): NodeGroup { + this.nodeData['mergedNodes'].sort((v1, v2) => { + return v2.totalCount - v1.totalCount; + }); + return this; + } + getGroupKey(): string { + return this.groupKey; + } + getType(): string { + return this.type; + } + getGroupServiceType(): string { + return this.groupType; + } + getNodeGroupData(): ShortCutNode { + this.setTopCountNodes(); + return this.nodeData; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-data.class.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-data.class.ts new file mode 100644 index 000000000000..cef9ca2e6c1f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-data.class.ts @@ -0,0 +1,382 @@ +import { NodeGroup } from './node-group.class'; +import { LinkGroup } from './link-group.class'; +import { MultiConnectNodeGroup } from './multi-connect-node-group.class'; +import { Filter } from 'app/core/models'; + +export class ServerMapData { + private nodeList: { [key: string]: any }[]; + private linkList: { [key: string]: any }[]; + private usableNodePropertyInServerMap: string[] = [ + 'key', + 'isWas', + 'isQueue', + 'category', + 'hasAlert', + 'slowCount', + 'histogram', + 'errorCount', + 'totalCount', + 'serviceType', + 'isAuthorized', + 'instanceCount', + 'applicationName' + ]; + private usableLinkPropertyInServerMap: string[] = [ + 'to', + 'key', + 'from', + 'hasAlert', + 'slowCount', + 'targetInfo', + 'totalCount', + 'sourceInfo', + 'errorCount' + ]; + private mergeableServiceType: { [key: string]: boolean } = {}; + private canNotMergeableServiceTypeList: string[] = ['USER']; + private countMap: { [key: string]: any } = {}; + private nodeMap: { [key: string]: any } = {}; + private linkMap: { [key: string]: any } = {}; + private mergeStateMap: { [key: string]: boolean } = {}; + private groupServiceTypeMap: { [key: string]: boolean } = {}; + private originalNodeMap: { [key: string]: INodeInfo } = {}; + private originalLinkMap: { [key: string]: ILinkInfo } = {}; + + constructor( + private originalNodeList: INodeInfo[], + private originalLinkList: ILinkInfo[], + private filters?: Filter[]) { + this.init(); + } + reset(originalNodeList: INodeInfo[], originalLinkList: ILinkInfo[]) { + this.originalNodeList = originalNodeList; + this.originalLinkList = originalLinkList; + this.init(); + } + private init() { + this.convertToMap(); + this.extractUsableData(); + this.extractInboundAndOutboundCountOfEachNode(); + this.extractServiceTypeWhichCanMerge(); + // option set merge + // if ( showMergedStatus ) { + this.mergeGroup(); + this.mergeMultiLinkGroup(); + this.addFilterFlag(); + // } + } + private convertToMap(): void { + console.time('convertTimeToMapFromList'); + this.originalNodeList.forEach((value: INodeInfo) => { + this.originalNodeMap[value.key] = value; + }); + this.originalLinkList.forEach((value: ILinkInfo) => { + this.originalLinkMap[value.key] = value; + }); + console.timeEnd('convertTimeToMapFromList'); + } + private extractUsableData(): void { + this.nodeList = this.extractData(this.originalNodeList, this.usableNodePropertyInServerMap, this.nodeMap); + this.linkList = this.extractData(this.originalLinkList, this.usableLinkPropertyInServerMap, this.linkMap); + } + // extract necessary data from source data. + private extractData(dataList: any[], keys: string[], map: any): any[] { + const necessaryData = []; + dataList.forEach((data) => { + const oNew = {}; + keys.forEach((key) => { + oNew[key] = data[key]; + }); + necessaryData.push(oNew); + map[oNew['key']] = oNew; + }); + return necessaryData; + } + /** + * collect in and out node count of each node + */ + private extractInboundAndOutboundCountOfEachNode(): void { + this.nodeList.forEach((node: any) => { + this.countMap[node.key] = { + inCount: 0, + outCount: 0 + }; + }); + this.linkList.forEach((link: any) => { + this.countMap[link.to].inCount++; + this.countMap[link.from].outCount++; + }); + } + // extract serviceType list which can merge from source data. + private extractServiceTypeWhichCanMerge() { + this.mergeableServiceType = {}; + this.nodeList.forEach((node) => { + if (this.canMergeType(node)) { + this.mergeableServiceType[node.serviceType] = true; + } + }); + } + // @TODO 병합 가능한 serviceType에 대한 조건이 좀 이상한 듯... + // @TODO isQueue는 뭐지? + private canMergeType(nodeData: any): boolean { + return nodeData.isWas === false && nodeData.isQueue === false && this.canNotMergeableServiceTypeList.indexOf(nodeData.serviceType) === -1; + } + private isRootNode(key: string) { + return this.countMap[key].inCount === 0; + } + private isLeafNode(key: string) { + return this.countMap[key].outCount === 0; + } + private mergeGroup(): void { + console.time('mergeGroup()'); + // from 이 동일한 servcieType을 하나의 데이터로 묶음. + const collectMergeLink = {}; + this.linkList.forEach((link) => { + if (this.hasMergeableNode(link) === false) { + return; + } + if ((link.from in collectMergeLink) === false) { + collectMergeLink[link.from] = {}; + } + if ((link.targetInfo.serviceType in collectMergeLink[link.from]) === false) { + collectMergeLink[link.from][link.targetInfo.serviceType] = { + relatedLink: [] + }; + } + // same from-key and same service-type + collectMergeLink[link.from][link.targetInfo.serviceType].relatedLink.push(link); + }); + const removeNodeKeys = {}; + const removeLinkKeys = {}; + for (const nodeKey in collectMergeLink) { + if (collectMergeLink.hasOwnProperty(nodeKey)) { + for (const type in collectMergeLink[nodeKey]) { + if (collectMergeLink[nodeKey][type].relatedLink.length < 2 || this.mergeStateMap[type] === false) { + continue; + } + const nodeGroup = new NodeGroup(type); + const linkGroup = new LinkGroup(nodeKey, nodeGroup.getGroupKey()); + + collectMergeLink[nodeKey][type].relatedLink.forEach((link) => { + nodeGroup.addNodeData(this.nodeMap[link.to]); + linkGroup.addLinkData(link); + removeNodeKeys[link.to] = true; // link.to is key of target node. + removeLinkKeys[link.key] = true; + delete this.nodeMap[link.to]; + delete this.linkMap[link.key]; + }); + this.countMap[nodeGroup.getGroupKey()] = { + inCount: 1, + outCount: 0 + }; + this.nodeList.push(nodeGroup.sortNodeData().getNodeGroupData()); + this.linkList.push(linkGroup.sortLinkData().getLinkGroupData()); + this.groupServiceTypeMap[nodeGroup.getGroupServiceType()] = true; + this.mergeStateMap[type] = true; + } + } + } + this.removeByKey(this.nodeList, removeNodeKeys); + this.removeByKey(this.linkList, removeLinkKeys); + console.timeEnd('mergeGroup()'); + } + private hasMergeableNode(link: any): boolean { + if (this.mergeableServiceType[link.targetInfo.serviceType] !== true) { + return false; + } + if (this.countMap[link.to].inCount !== 1) { + return false; + } + if (this.isLeafNode(link.to) === false) { + return false; + } + return true; + } + setMergeState({name, state}: IServerMapMergeState): void { + this.mergeStateMap[name] = state; + } + resetMergeState(): void { + this.extractUsableData(); + this.mergeGroup(); + this.mergeMultiLinkGroup(); + } + removeByKey(data: any, removeList: any) { + const removeIndex: Array = []; + data.forEach((thing, index) => { + if (removeList[thing.key] === true) { + removeIndex.push(index); + } + }); + removeIndex.sort(function (v1, v2) { + return v1 - v2; + }); + for (let i = removeIndex.length - 1; i >= 0; i--) { + data.splice(removeIndex[i], 1); + } + } + mergeMultiLinkGroup(): void { + console.time('mergeMultiLinkGroup()'); + // 일단 두번째 병합 조건에 해당하는 노드들을 추림 + const targetNodeList = this.getMergeTargetNodes(); + const checkedNodes = {}; + const removeNodeKeys = {}; + const removeLinkKeys = {}; + + targetNodeList.forEach((outerNode) => { + const outerLoopNodeKey = outerNode.key; + if (checkedNodes[outerLoopNodeKey] === true) { + return; + } + checkedNodes[outerLoopNodeKey] = true; + const outerLoopNodeFromKeys: Array = this.getFromNodeKeys(outerLoopNodeKey); + const mergeTargetLinks: any = {}; + const mergeTargetNodeList: Array = []; + + targetNodeList.forEach((innerNode) => { + const innerLoopNodeKey = innerNode.key; + if (checkedNodes[innerLoopNodeKey] === true) { + return; + } + if (outerNode.serviceType !== innerNode.serviceType) { + return; + } + const innerLoopNodeFromKeys: Array = this.getFromNodeKeys(innerLoopNodeKey); + if (this.hasSameNodeList(outerLoopNodeFromKeys, innerLoopNodeFromKeys) === false) { + return; + } + checkedNodes[innerLoopNodeKey] = true; + + this.extractConnectLink(mergeTargetLinks, innerLoopNodeFromKeys, innerLoopNodeKey); + mergeTargetNodeList.push(innerNode); + }); + if (mergeTargetNodeList.length > 0) { + this.extractConnectLink(mergeTargetLinks, outerLoopNodeFromKeys, outerLoopNodeKey); + mergeTargetNodeList.push(outerNode); + + const multiConnectNodeGroup = new MultiConnectNodeGroup(outerNode.serviceType); + mergeTargetNodeList.forEach((node) => { + multiConnectNodeGroup.addNodeData(node); + removeNodeKeys[node.key] = true; + delete this.nodeMap[node.key]; + }); + + for (const fromKey in mergeTargetLinks) { + if (mergeTargetLinks.hasOwnProperty(fromKey)) { + multiConnectNodeGroup.addSubNodeGroup(fromKey); + const linkGroup = new LinkGroup(fromKey, multiConnectNodeGroup.getGroupKey()); + for (let i = 0; i < mergeTargetLinks[fromKey].length; i++) { + const link = mergeTargetLinks[fromKey][i]; + multiConnectNodeGroup.addSubNodeGroupData(fromKey, link); + linkGroup.addLinkData(link); + removeLinkKeys[link.key] = true; + delete this.linkMap[link.key]; + } + this.linkList.push(linkGroup.sortLinkData().getLinkGroupData()); + } + } + this.nodeList.push(multiConnectNodeGroup.sortNodeData().getNodeGroupData()); + this.groupServiceTypeMap[multiConnectNodeGroup.getGroupServiceType()] = true; + } + }); + this.removeByKey(this.nodeList, removeNodeKeys); + this.removeByKey(this.linkList, removeLinkKeys); + console.timeEnd('mergeMultiLinkGroup()'); + } + private extractConnectLink(mergeTargetLinks: any, fromKeys: Array, key: string): void { + fromKeys.forEach((fromKey) => { + if ((fromKey in mergeTargetLinks) === false) { + mergeTargetLinks[fromKey] = []; + } + mergeTargetLinks[fromKey].push(this.linkMap[fromKey + '~' + key]); + }); + } + private getMergeTargetNodes(): any { + const targetNodeList = []; + this.nodeList.forEach((node) => { + if (this.countMap[node.key].outCount > 0) { + return; + } + if (this.countMap[node.key].inCount < 2) { + return; + } + targetNodeList.push(node); + }); + return targetNodeList; + } + private hasSameNodeList(firstNodeList: Array, secondNodeList: Array): boolean { + if (firstNodeList.length !== secondNodeList.length) { + return false; + } + for (let i = 0; i < firstNodeList.length; i++) { + if (secondNodeList.indexOf(firstNodeList[i]) === -1) { + return false; + } + } + return true; + } + private getFromNodeKeys(nodeKey: string): any { + const fromNodeKeys = []; + this.linkList.forEach((link) => { + if (link.to === nodeKey) { + fromNodeKeys.push(link.from); + } + }); + return fromNodeKeys; + } + getNodeList(): { [key: string]: any }[] { + return this.nodeList; + } + getLinkList(): { [key: string]: any }[] { + return this.linkList; + } + getGroupTypes(): Array { + const types: Array = []; + for (const type in this.groupServiceTypeMap) { + if (this.groupServiceTypeMap.hasOwnProperty(type)) { + types.push(type); + } + } + return types; + } + getMergeState(): any { + return Object.keys(this.mergeStateMap).reduce((accumulator, key) => { + accumulator[key] = this.mergeStateMap[key]; + return accumulator; + }, {}); + } + getMergedNodeData(key: string): any { + return this.nodeList.find((node: {[key: string]: any}) => node.key === key); + } + getMergedLinkData(key: string): any { + return this.linkList.find((link: {[key: string]: any}) => link.key === key); + } + getNodeData(key: string): INodeInfo { + return this.originalNodeMap[key]; + } + getLinkData(key: string): ILinkInfo { + return this.originalLinkMap[key]; + } + addFilterFlag(): void { + if (this.filters) { + // this.nodeList.forEach((node: any) => { + // this.filters.forEach((filter: Filter) => { + // if ( filter.getFromKey() === node.key || filter.getToKey() === node.key ) { + // node['isFiltered'] = true; + // return; + // } + // }); + // }); + this.linkList.forEach((link: any) => { + this.filters.forEach((filter: Filter) => { + if (filter.getFromKey() === link.from && filter.getToKey() === link.to) { + link['isFiltered'] = true; + return; + } + }); + }); + } + } + getNodeCount(): number { + return this.nodeList.length; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-diagram-with-gojs.class.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-diagram-with-gojs.class.ts new file mode 100644 index 000000000000..21200401f8d0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-diagram-with-gojs.class.ts @@ -0,0 +1,262 @@ +import * as go from 'gojs'; + +import ServerMapTheme from './server-map-theme'; +import { ServerMapTemplateWithGojs } from './server-map-template-with-gojs.class'; +import { ServerMapDiagram } from './server-map-diagram.class'; +import { ServerMapData } from './server-map-data.class'; +import { IServerMapOption } from './server-map-factory'; + +export class ServerMapDiagramWithGojs extends ServerMapDiagram { + private diagram: go.Diagram = null; + private groupServiceTypeList: string[]; + + constructor( + private option: IServerMapOption + ) { + super(); + ServerMapTheme.general.common.funcServerMapImagePath = this.option.funcServerMapImagePath; + this.makeDiagram(); + this.setNodeDefaultTemplate(); + this.setLinkTemplate(); + this.setDiagramEnvironment(); + this.setEvent(); + } + makeDiagram(): void { + this.diagram = go.GraphObject.make(go.Diagram, this.option.container, { + allowDelete: false, + maxSelectionCount: 1, + initialContentAlignment: go.Spot.Center + }); + this.diagram.animationManager.isEnabled = false; + this.diagram.scrollMode = go.Diagram.InfiniteScroll; + } + setNodeDefaultTemplate(): void { + this.diagram.nodeTemplate = ServerMapTemplateWithGojs.makeNodeTemplate(this); + } + setNodeTemplateMap(): void { + this.groupServiceTypeList.forEach((groupType) => { + this.diagram.nodeTemplateMap.add(groupType, ServerMapTemplateWithGojs.makeNodeGroupTemplate(this)); + }); + } + setLinkTemplate(): void { + this.diagram.linkTemplate = ServerMapTemplateWithGojs.makeLinkTemplate(this); + } + setDiagramEnvironment(): void { + const $ = go.GraphObject.make; + + this.diagram.toolManager.mouseWheelBehavior = go.ToolManager.WheelZoom; + this.diagram.allowDrop = false; + + this.diagram.initialAutoScale = go.Diagram.Uniform; + this.diagram.toolManager.draggingTool.doCancel(); + this.diagram.toolManager.draggingTool.doDeactivate(); + this.diagram.toolManager.dragSelectingTool.isEnabled = false; + this.diagram.initialContentAlignment = go.Spot.Center; + this.diagram.padding = new go.Margin(10, 10, 10, 10); + this.diagram.layout = $( + go.LayeredDigraphLayout, + { + isOngoing: false, + layerSpacing: 100, + columnSpacing: 30, + setsPortSpots: false + } + ); + } + setEvent(): void { + const self = this; + this.diagram.addDiagramListener('InitialLayoutCompleted', (event: go.DiagramEvent) => { + if (self.serverMapData) { + self.outRenderCompleted.emit(event.diagram); + } + }); + this.diagram.addDiagramListener('BackgroundSingleClicked', () => { + self.outClickBackground.emit(); + }); + this.diagram.addDiagramListener('BackgroundDoubleClicked', (event: go.DiagramEvent) => { + event.diagram.zoomToFit(); + self.outDoubleClickBackground.emit('dbclickBackground'); + }); + // this.diagram.addDiagramListener('BackgroundContextClicked', (event: go.DiagramEvent) => { + // console.log('Background context click', event); + // self.outContextClickBackground.emit({ + // event: event + // }); + // }); + this.diagram.addDiagramListener('BackgroundContextClicked', (event: go.DiagramEvent) => { + const { pageX, pageY } = event.diagram.lastInput.event as MouseEvent; + + self.outContextClickBackground.emit({ + coordX: pageX, + coordY: pageY + }); + }); + } + setMapData(serverMapData: ServerMapData, baseApplicationKey = '') { + this.serverMapData = serverMapData; + this.groupServiceTypeList = serverMapData.getGroupTypes(); + this.baseApplicationKey = baseApplicationKey; + this.setNodeTemplateMap(); + + this.diagram.model = go.Model.fromJson({ + nodeDataArray: this.serverMapData.getNodeList(), + linkDataArray: this.serverMapData.getLinkList() + }); + // this.diagram.undoManager.isEnabled = true; + this.selectBaseApplication(); + } + private selectBaseApplication() { + if (this.baseApplicationKey !== '') { + const node = this.diagram.findNodeForKey(this.baseApplicationKey); + if (node) { + const part = this.diagram.findPartForKey(this.baseApplicationKey); + this.diagram.select(part); + this.onClickNodeManually(part); + } + } + } + private onClickNodeManually(obj: go.Part): void { + this.updateHighlights(obj); + this.outClickNode.emit(obj['data']); + } + private updateHighlights(selection: go.Part): void { + this.removeHighlightMark(); + selection['highlight'] = 'self'; + if (selection instanceof go.Node) { + this.addHighlightMarkToLink(selection); + } else if (selection instanceof go.Link) { + this.addHighlightMarkToNode(selection); + } + this.drawHighlight(); + } + private removeHighlightMark(): void { + const allNodes = this.diagram.nodes; + const allLinks = this.diagram.links; + while (allNodes.next()) { + delete allNodes.value['highlight']; + } + while (allLinks.next()) { + delete allLinks.value['highlight']; + } + } + private addHighlightMarkToLink(selection: go.Part) { + const intoLinks = (selection).findLinksInto(); + while (intoLinks.next()) { + intoLinks.value['highlight'] = 'from'; + } + const outofLinks = (selection).findLinksOutOf(); + while (outofLinks.next()) { + outofLinks.value['highlight'] = 'to'; + } + } + private addHighlightMarkToNode(selection: go.Part) { + (selection).fromNode['highlight'] = 'from'; + (selection).toNode['highlight'] = 'to'; + } + private drawHighlight(): void { + const allNodes = this.diagram.nodes; + const allLinks = this.diagram.links; + + while (allNodes.next()) { + this.highlightNode(allNodes.value); + } + while (allLinks.next()) { + this.highlightLink(allLinks.value); + } + } + private highlightNode(targetNode: go.Node): void { + const shape: go.Shape = targetNode.findObject('BORDER_SHAPE'); + const nodeStyle = targetNode['highlight'] ? ServerMapTheme.general.node.highlight : ServerMapTheme.general.node.normal; + + shape['stroke'] = nodeStyle.border.stroke; + shape['strokeWidth'] = nodeStyle.border.strokeWidth; + shape.part.isShadowed = false; + } + private highlightLink(selectedLink: go.Link, theme?: any, toFill?: any): void { + const line: go.Shape = selectedLink.findObject('LINK'); + const arrow: go.Shape = selectedLink.findObject('ARROW'); + const text: go.TextBlock = selectedLink.findObject('LINK_TEXT'); + const linkStyle = selectedLink['highlight'] ? ServerMapTheme.general.link.highlight : ServerMapTheme.general.link.normal; + + line['stroke'] = linkStyle.line.stroke; + arrow['stroke'] = linkStyle.arrow.stroke; + arrow['fill'] = linkStyle.arrow.fill; + text['font'] = linkStyle.fontFamily; + } + isBaseApplication(key: string): boolean { + return this.baseApplicationKey === key; + } + selectNodeBySearch(highlightApplicationKey: string): void { + const node: go.Node = this.searchHighlightNode(highlightApplicationKey); + this.diagram.select(node); + this.diagram.centerRect(node.actualBounds); + this.updateHighlights(node); + this.outClickNode.emit(node['data']); + } + private searchHighlightNode(highlightApplicationKey: string): go.Node { + const allNodes = this.diagram.nodes; + let resultNode: go.Node; + + while (allNodes.next()) { + const node: go.Node = allNodes.value; + if (node.data.mergedNodes) { + const mergedNodes = node.data.mergedNodes; + for (let i = 0; i < mergedNodes.length ; i++ ) { + if (mergedNodes[i].key === highlightApplicationKey) { + resultNode = node; + break; + } + } + } else { + if (node.data.key === highlightApplicationKey) { + resultNode = node; + break; + } + } + } + return resultNode; + } + refresh(): void { + this.diagram.model = go.Model.fromJson({ + nodeDataArray: this.serverMapData.getNodeList(), + linkDataArray: this.serverMapData.getLinkList() + }); + this.diagram.rebuildParts(); + this.selectBaseApplication(); + } + clear(): void { + this.diagram.model = go.Model.fromJson({}); + } + onClickNode(event: go.DiagramEvent, obj: go.GraphObject): void { + this.updateHighlights(obj); + this.outClickNode.emit(obj['data']); + } + onDoubleClickNode(event: go.DiagramEvent, obj: go.GraphObject): void { + console.log('onDoubleClick-Node :', event, obj); + this.diagram.centerRect(obj.actualBounds); + this.diagram.scale *= 1.3; + } + onContextClickNode(event: go.DiagramEvent, obj: go.GraphObject): void { + console.log('onContextClick-Node :', event, obj); + this.outContextClickNode.emit(obj); + } + onClickLink(event: go.DiagramEvent, obj: go.GraphObject): void { + console.log('onClick-Link :', event, obj); + this.updateHighlights(obj); + this.outClickLink.emit(obj['data']); + } + onContextClickLink(event: any, obj: go.GraphObject): void { + const { key, targetInfo } = (obj as go.Link).data; + const { pageX, pageY } = event.event; + + if (!Array.isArray(targetInfo)) { + this.outContextClickLink.emit({ + key, + coord: { + coordX: pageX, + coordY: pageY + } + }); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-diagram-with-visjs.class.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-diagram-with-visjs.class.ts new file mode 100644 index 000000000000..b8723254feb1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-diagram-with-visjs.class.ts @@ -0,0 +1,299 @@ +import { Network, NodeOptions, EdgeOptions, Color, Node, Edge, Options } from 'vis'; +import { from as fromArray, fromEvent, iif, zip, merge } from 'rxjs'; +import { mergeMap, map, pluck, take, reduce, switchMap } from 'rxjs/operators'; + +import ServerMapTheme from './server-map-theme'; +import { ServerMapDiagram } from './server-map-diagram.class'; +import { ServerMapData } from './server-map-data.class'; +import { IServerMapOption } from './server-map-factory'; +import { ServerMapTemplate } from './server-map-template'; +import { NodeGroup } from './node-group.class'; + +export class ServerMapDiagramWithVisjs extends ServerMapDiagram { + private diagram: Network; + + constructor( + private option: IServerMapOption + ) { + super(); + ServerMapTheme.general.common.funcServerMapImagePath = this.option.funcServerMapImagePath; + this.makeDiagram(); + this.setEvent(); + } + + private makeDiagram(): void { + const container = this.option.container; + const data = { + nodes: [] as Node[], + edges: [] as Edge[] + }; + const options = { + interaction: { + hover: true, + }, + nodes: { + borderWidth: 2.5, + color: { + border: 'transparent', + highlight: { + border: ServerMapTheme.general.node.highlight.border.stroke, + } + } as Color, + labelHighlightBold: false, + shape: 'circularImage', + shapeProperties: { + useBorderWithImage: false, + useImageSize: true + }, + size: 65 + } as NodeOptions, + edges: { + arrows: { + to: { + enabled: true, + scaleFactor: 0.75 + } + }, + // arrowStrikethrough: false, + color: { + color: ServerMapTheme.general.link.normal.line.stroke, + highlight: ServerMapTheme.general.link.highlight.line.stroke + }, + font: { + align: 'horizontal', + size: 18, + background: ServerMapTheme.general.link.normal.textBox.fill, + }, + } as EdgeOptions, + groups: { + main: { + color: { + background: ServerMapTheme.general.node.main.fill.top, + highlight: { + background: ServerMapTheme.general.node.main.fill.top + } + } + }, + normal: { + color: { + background: ServerMapTheme.general.node.normal.fill.top, + highlight: { + background: ServerMapTheme.general.node.normal.fill.top + } + } + } + }, + layout: { + hierarchical: { + enabled: true, + levelSeparation: 300, + nodeSpacing: 235, + // blockShifting: false, + // edgeMinimization: false, + direction: 'LR', + sortMethod: 'directed' + } + }, + physics: { + enabled: false + } + } as Options; + this.diagram = new Network(container, data, options); + } + + private setEvent(): void { + this.diagram.on('afterDrawing', () => { + if (!this.serverMapData) { + return; + } + + this.outRenderCompleted.emit(); + }); + this.diagram.on('click', (({nodes, edges}: {nodes: string[], edges: string[]}) => { + const isNodeClicked = nodes.length !== 0; + const isEdgeClicked = !isNodeClicked && edges.length !== 0; + const isBackgroundClicked = !(isNodeClicked || isEdgeClicked); + + if (isNodeClicked) { + const nodeId = nodes[0]; + const nodeData = this.getNodeData(nodeId); + + this.outClickNode.emit(nodeData); + } else if (isEdgeClicked) { + const edgeId = edges[0]; + const linkData = this.getLinkData(edgeId); + + this.outClickLink.emit(linkData); + } else if (isBackgroundClicked) { + this.outClickBackground.emit(); + } + })); + this.diagram.on('hoverNode', () => this.changeCursor('pointer')); + this.diagram.on('hoverEdge', () => this.changeCursor('pointer')); + this.diagram.on('blurNode', () => this.changeCursor('default')); + this.diagram.on('blurEdge', () => this.changeCursor('default')); + this.diagram.on('oncontext', (({event, pointer}: {event: MouseEvent, pointer: {[key: string]: any}}) => { + event.preventDefault(); + const { x, y } = pointer.DOM; + const nodeId = this.diagram.getNodeAt({x, y}) as string; + const edgeId = this.diagram.getEdgeAt({x, y}) as string; + + if (nodeId || NodeGroup.isGroupKey(edgeId)) { + return; + } + + edgeId ? ( + this.outContextClickLink.emit({ + key: edgeId, + coord: { + coordX: x, + coordY: y + } + }) + ) : ( + this.outContextClickBackground.emit({ + coordX: x, + coordY: y + }) + ); + })); + } + + setMapData(serverMapData: ServerMapData, baseApplicationKey = ''): void { + const nodeList = serverMapData.getNodeList(); + const isDataEmpty = nodeList.length === 0; + + if (isDataEmpty) { + return; + } + + const edges = serverMapData.getLinkList().map((link: {[key: string]: any}) => { + const { from, to, key, totalCount, isFiltered, hasAlert } = link; + + return { + from, + to, + id: key, + // [임시]label에서 이미지를 지원하지않아서, filteredMap페이지에서 필터아이콘을 "Filtered" 텍스트로 대체. + label: isFiltered ? ` [Filtered]\n${totalCount.toLocaleString()} ` : ` ${totalCount.toLocaleString()} `, + font: { + color: hasAlert ? ServerMapTheme.general.link.normal.fontColor.alert : ServerMapTheme.general.link.normal.fontColor.normal + } + }; + }); + + fromArray(nodeList).pipe( + mergeMap((node: {[key: string]: any}) => { + const { key, applicationName, serviceType, isAuthorized, topCountNodes, hasAlert } = node; + const isMergedNode = NodeGroup.isGroupKey(key); + const serviceTypeImg = new Image(); + + serviceTypeImg.src = ServerMapTheme.general.common.funcServerMapImagePath(serviceType); + + const serviceTypeImgLoadEvent$ = merge( + fromEvent(serviceTypeImg, 'load'), + fromEvent(serviceTypeImg, 'error').pipe( + switchMap(() => { + // 해당 serviceType 이름의 이미지가 없을 경우, NO_IMAGE_FOUND 이미지로 대체 + const tempImg = new Image(); + + tempImg.src = ServerMapTheme.general.common.funcServerMapImagePath('NO_IMAGE_FOUND'); + return fromEvent(tempImg, 'load'); + }) + ) + ); + const innerObs$ = iif(() => hasAlert && isAuthorized, + (() => { + const alertImg = new Image(); + + alertImg.src = ServerMapTheme.general.common.funcServerMapImagePath(ServerMapTheme.general.common.icon.error); + return zip( + serviceTypeImgLoadEvent$.pipe(pluck('target')), + fromEvent(alertImg, 'load').pipe(pluck('target')) + ); + })(), + serviceTypeImgLoadEvent$.pipe(map((v: Event) => [v.target])) + ); + + return innerObs$.pipe( + map((img: HTMLImageElement[]) => { + const svg = ServerMapTemplate.getSVGString(img, node); + + return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg); + }), + map((image: string) => { + return { + id: key, + label: isMergedNode ? this.getMergedNodeLabel(topCountNodes) : applicationName, + image, + group: key === baseApplicationKey ? 'main' : 'normal' + }; + }) + ); + }), + take(nodeList.length), + reduce((acc: Node[], curr: Node) => { + return [...acc, curr]; + }, [] as Node[]), + ).subscribe((nodes: Node[]) => { + this.diagram.setData({nodes, edges}); + this.serverMapData = serverMapData; + this.baseApplicationKey = baseApplicationKey; + this.selectBaseApplication(); + }); + } + + private getMergedNodeLabel(topCountNodes: {[key: string]: any}[]): string { + // [임시] 일단은 "Total: 4(Merge된 노드 개수)"만 표시 + return topCountNodes[0].applicationName; + } + + private getNodeData(key: string): {[key: string]: any} { + return NodeGroup.isGroupKey(key) ? this.serverMapData.getMergedNodeData(key) : this.serverMapData.getNodeData(key); + } + + private getLinkData(key: string): {[key: string]: any} { + return NodeGroup.isGroupKey(key) ? this.serverMapData.getMergedLinkData(key) : this.serverMapData.getLinkData(key); + } + + private selectBaseApplication(): void { + if (this.baseApplicationKey === '') { + return; + } + + this.setNodeClicked(this.baseApplicationKey); + } + private setNodeClicked(key: string): void { + this.diagram.selectNodes([key]); + this.outClickNode.emit(this.getNodeData(key)); + } + + private changeCursor(cursorStyle: string): void { + this.option.container.querySelector('canvas').style.cursor = cursorStyle; + } + + refresh(): void { + this.setMapData(this.serverMapData, this.baseApplicationKey); + } + + clear(): void { + this.diagram.destroy(); + } + + selectNodeBySearch(selectedAppKey: string): void { + let selectedNodeId = selectedAppKey; + const isMergedNode = this.diagram.findNode(selectedAppKey).length === 0; + + if (isMergedNode) { + const groupKey = selectedAppKey.split('^')[1]; + const selectedMergedNode = this.serverMapData.getNodeList().find(({key}: {key: string}) => { + return NodeGroup.isGroupKey(key) && key.includes(groupKey); + }); + + selectedNodeId = selectedMergedNode.key; + } + + this.diagram.focus(selectedNodeId); + this.setNodeClicked(selectedNodeId); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-diagram.class.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-diagram.class.ts new file mode 100644 index 000000000000..d1ba06f102c2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-diagram.class.ts @@ -0,0 +1,51 @@ +import { EventEmitter } from '@angular/core'; + +import { ServerMapData } from './server-map-data.class'; +import { NodeGroup } from './node-group.class'; +import { Application } from 'app/core/models'; + +export abstract class ServerMapDiagram { + protected serverMapData: ServerMapData; + protected baseApplicationKey: string; + + outClickNode: EventEmitter = new EventEmitter(); + outClickGroupNode: EventEmitter = new EventEmitter(); + outContextClickNode: EventEmitter = new EventEmitter(); + outClickLink: EventEmitter = new EventEmitter(); + outContextClickLink: EventEmitter = new EventEmitter(); + outClickBackground: EventEmitter = new EventEmitter(); + outContextClickBackground: EventEmitter = new EventEmitter(); + outDoubleClickBackground: EventEmitter = new EventEmitter(); + outRenderCompleted: EventEmitter = new EventEmitter(); + + abstract setMapData(mapData: ServerMapData, baseApplicationKey?: string): void; + abstract selectNodeBySearch(appKey: string): void; + abstract refresh(): void; + abstract clear(): void; + + searchNode(query: string): IApplication[] { + return this.serverMapData.getNodeList() + .reduce((prev: {[key: string]: any}[], curr: {[key: string]: any}) => { + const { key, mergedNodes } = curr; + + return NodeGroup.isGroupKey(key) ? [...prev, ...mergedNodes] : [...prev, curr]; + }, []) + .filter(({applicationName}: {applicationName: string}) => { + const regCheckQuery = new RegExp(query, 'i'); + + return regCheckQuery.test(applicationName); + }) + .map(({key, applicationName, serviceType}: {key: string, applicationName: string, serviceType: string}) => { + return new Application(applicationName, serviceType, 0, key); + }); + } + + setMergeState(mergeState: IServerMapMergeState): void { + this.serverMapData.setMergeState(mergeState); + } + + resetMergeState(): void { + this.serverMapData.resetMergeState(); + this.refresh(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-factory.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-factory.ts new file mode 100644 index 000000000000..fdeebc4c7d92 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-factory.ts @@ -0,0 +1,26 @@ +import { InjectionToken } from '@angular/core'; + +import { ServerMapDiagram } from 'app/core/components/server-map/class/server-map-diagram.class'; +import { ServerMapDiagramWithGojs } from 'app/core/components/server-map/class/server-map-diagram-with-gojs.class'; +import { ServerMapDiagramWithVisjs } from 'app/core/components/server-map/class/server-map-diagram-with-visjs.class'; + +export const SERVER_MAP_TYPE = new InjectionToken('server-map-type'); +export const enum ServerMapType { + GOJS = 'gojs', + VISJS = 'visjs' +} +export interface IServerMapOption { + container: HTMLElement; + funcServerMapImagePath: Function; +} + +export class ServerMapFactory { + static createServerMap(type: ServerMapType, option: IServerMapOption): ServerMapDiagram { + switch (type) { + case ServerMapType.GOJS: + return new ServerMapDiagramWithGojs(option); + case ServerMapType.VISJS: + return new ServerMapDiagramWithVisjs(option); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-template-with-gojs.class.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-template-with-gojs.class.ts new file mode 100644 index 000000000000..51d59e2b99ab --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-template-with-gojs.class.ts @@ -0,0 +1,635 @@ +import * as go from 'gojs'; +import ServerMapTheme from './server-map-theme'; + +export class ServerMapTemplateWithGojs { + public static NO_IMAGE_FOUND = 'NO_IMAGE_FOUND'; + public static circleMaxSize = 360; + public static circleMinPercentage = 5; + public static calcuResponseSummaryCircleSize(sum: number, value: number) { + let size = 0; + if (value === 0) { + return 0; + } + const percentage = (value * 100) / sum; + if (percentage < ServerMapTemplateWithGojs.circleMinPercentage) { + size = Math.floor((ServerMapTemplateWithGojs.circleMaxSize * ServerMapTemplateWithGojs.circleMinPercentage) / 100); + } else { + size = Math.floor((ServerMapTemplateWithGojs.circleMaxSize * percentage) / 100); + } + return size; + } + public static makeNodeTemplate(serverMapComponent: any) { + /* + template structure + Node + Panel.Auto + Shape + Panel.Table + Shape ( row: 0 ) - background + Picture ( row: 0, col: 0 ) - icon + Shape ( row: 0, col: 0 ) - red circle + Shape( yellow circle ) + Shape( green circle) + Shape ( row: 1 ) - background + TextBlock ( applicationName ) + Panel.Auto + Shape + TextBlock ( instance count ) + Panel.Vertical + Picture ( error.png ) + Picture ( filter.png ) + */ + const $ = go.GraphObject.make; + return $( + go.Node, + go.Panel.Auto, + { + position: new go.Point(0, 0), + selectionAdorned: false, + click: function (event: go.DiagramEvent, obj: go.GraphObject) { + serverMapComponent.onClickNode(event, obj); + }, + doubleClick: function(event: go.DiagramEvent, obj: go.GraphObject) { + serverMapComponent.onDoubleClickNode(event, obj); + }, + contextClick: function(event: go.DiagramEvent, obj: go.GraphObject) { + serverMapComponent.onContextClickNode(event, obj); + } + }, + new go.Binding('key', 'key'), + new go.Binding('category', 'serviceType'), + $( + go.Shape, + 'Rectangle', + { + name: 'BORDER_SHAPE', + stroke: '#D0D7DF', + strokeWidth: 0, + portId: '', + fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true, + toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true, + } + ), + $( + go.Panel, + go.Panel.Table, + { + cursor: 'pointer' + // locationSpot: go.Spot.Center, + // selectionAdorned: false, + }, + // new go.Binding('scale', 'isSelected', (isSelected) => { + // return isSelected ? 1.2 : 1.0; + // }).ofObject(), + $(go.RowColumnDefinition, {column: 0, minimum: 140}), + $( + go.Shape, + 'Rectangle', + { + row: 0, + column: 0, + height: 95, + stretch: go.GraphObject.Horizontal, + }, + new go.Binding('fill', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].fill.top; + }), + new go.Binding('stroke', 'key', function(key, node) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].stroke; + }), + new go.Binding('strokeWidth', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].strokeWidth; + }) + ), + $( + go.Picture, + { + row: 0, + column: 0, + width: 90, + height: 90, + imageStretch: go.GraphObject.Uniform, + errorFunction: function(e: any) { + e.source = ServerMapTheme.general.common.funcServerMapImagePath(ServerMapTemplateWithGojs.NO_IMAGE_FOUND); + } + }, + new go.Binding('source', 'serviceType', function (type) { + return ServerMapTheme.general.common.funcServerMapImagePath(type); + }) + ), + $( + go.Shape, { + row: 0, + column: 0, + stroke: ServerMapTheme.general.circle.bad.stroke, + strokeWidth: ServerMapTheme.general.circle.bad.strokeWidth + }, + new go.Binding('visible', '', function (data) { + return data.isAuthorized && data.isWas; + }), + new go.Binding('geometry', 'histogram', function (histogram) { + return go.Geometry.parse('M30 0 B270 360 30 30 30 30'); + }) + ), + $( + go.Shape, { + row: 0, + column: 0, + stroke: ServerMapTheme.general.circle.slow.stroke, + strokeWidth: ServerMapTheme.general.circle.slow.strokeWidth + }, + new go.Binding('visible', '', function (data) { + return data.isAuthorized && data.isWas; + }), + new go.Binding('geometry', 'histogram', function (histogram) { + if (histogram['Slow'] === 0) { + return go.Geometry.parse('M30 0'); + } + const sum = Object.keys(histogram).reduce((prevSum: number, curKey: string) => { + return prevSum + histogram[curKey]; + }, 0); + return go.Geometry.parse('M30 0 B270 ' + (ServerMapTemplateWithGojs.circleMaxSize - ServerMapTemplateWithGojs.calcuResponseSummaryCircleSize(sum, histogram['Error'])) + ' 30 30 30 30'); + }) + ), + $( + go.Shape, { + row: 0, + column: 0, + stroke: ServerMapTheme.general.circle.good.stroke, + strokeWidth: ServerMapTheme.general.circle.good.strokeWidth + }, + new go.Binding('visible', '', function (data) { + return data.isAuthorized && data.isWas; + }), + new go.Binding('geometry', 'histogram', function (histogram) { + const sum = Object.keys(histogram).reduce((prevSum: number, curKey: string) => { + return prevSum + histogram[curKey]; + }, 0); + const size = ServerMapTemplateWithGojs.circleMaxSize - ServerMapTemplateWithGojs.calcuResponseSummaryCircleSize(sum, histogram['Slow']) - ServerMapTemplateWithGojs.calcuResponseSummaryCircleSize(sum, histogram['Error']); + if (size >= 180) { + return go.Geometry.parse('M30 0 B270 ' + size + ' 30 30 30 30'); + } else { + return go.Geometry.parse('M30 -60 B270 ' + size + ' 30 -30 30 30'); + } + }) + ), + $( + go.Shape, + 'Rectangle', + { + row: 1, + column: 0, + height: 34, + margin: new go.Margin(-1, 0, 0, 0), + stretch: go.GraphObject.Horizontal, + }, + new go.Binding('fill', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].fill.bottom; + }), + new go.Binding('stroke', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].stroke; + }), + new go.Binding('strokeWidth', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].strokeWidth; + }) + ), + $( + go.TextBlock, + { + row: 1, + column: 0, + margin: new go.Margin(0, 10, 0, 10) + }, + new go .Binding('font', '', function() { + return ServerMapTheme.general.common.font.normal; + }), + new go .Binding('stroke', '', function() { + return ServerMapTheme.general.node.normal.text.stroke; + }), + new go.Binding('text', 'applicationName') + ), + $( + go.Panel, + go.Panel.Auto, + { + minSize: new go.Size(20, 20), + alignment: go.Spot.TopRight + }, + new go.Binding('visible', 'instanceCount', function (v) { + return v > 1 ? true : false; + }), + $( + go.Shape, + { + figure: 'Rectangle' + }, + new go .Binding('fill', '', function() { + return ServerMapTheme.general.instance.shape.fill; + }), + new go .Binding('stroke', '', function() { + return ServerMapTheme.general.instance.shape.stroke; + }), + new go .Binding('strokeWidth', '', function() { + return ServerMapTheme.general.instance.shape.strokeWidth; + }) + ), + $( + go.TextBlock, + { + height: 20, + editable: false, + textAlign: 'center', + verticalAlignment: go.Spot.Center + }, + new go .Binding('font', '', function() { + return ServerMapTheme.general.common.font.small; + }), + new go .Binding('stroke', '', function() { + return ServerMapTheme.general.instance.text.stroke; + }), + new go.Binding('text', 'instanceCount') + ) + ), + $( + go.Panel, + go.Panel.Vertical, + { + margin: new go.Margin(0, 0, 0, 0), + alignment: go.Spot.TopLeft + }, + $( + go.Picture, + { + width: 20, + height: 20, + source: ServerMapTheme.general.common.funcServerMapImagePath(ServerMapTheme.general.common.icon.error), + imageStretch: go.GraphObject.Uniform + }, + new go.Binding('visible', '', function (data) { + return data.isAuthorized && data.hasAlert; + }) + ), + $( + go.Picture, + { + width: 28, + height: 28, + source: ServerMapTheme.general.common.funcServerMapImagePath(ServerMapTheme.general.common.icon.filter), + visible: false, + imageStretch: go.GraphObject.Uniform + }, + new go.Binding('visible', 'isFiltered') + ) + ) + ) + ); + } + public static makeNodeGroupTemplate(serverMapComponent: any) { + /* + template structure + Node + Panel.Auto + Shape + Panel.Table + Shape ( row: 0, col: 0 ) - background + Button + Picture ( row: 1, col: 0 ) - icon + Shape ( row: 1, col: 0 ) - background + Panel.Vertical( row: 1, col: 0 ) : bind>mergedNodes + Panel.Table + Panel.TableRow + Picture ( Error.png ) + TextBlock ( index ) + TextBlock ( applicationName ) + TextBlock ( totalCount ) + + Panel.Vertical( row: 1, col: 0 ) : bind>mergedMultiSourceNodes + Panel.Vertical + TextBlock - applicaitonName + Panel.Table : bind>group + Panel.TableRow + Picture ( Error.png ) + TextBlock ( index ) + TextBlock ( applicationName ) + TextBlock ( totalCount ) + Panel + Shape + TextBlock ( instaceCount ) + */ + const $ = go.GraphObject.make; + const groupTableTemplate = $( + go.Panel, + go.Panel.TableRow, + $( + go.Picture, { + column: 1, + source: ServerMapTheme.general.common.funcServerMapImagePath(ServerMapTheme.general.common.icon.error), + margin: new go.Margin(1, 2), + visible: false, + desiredSize: new go.Size(10, 10), + imageStretch: go.GraphObject.Uniform + }, + new go.Binding('visible', 'hasAlert') + ), + $( + go.TextBlock, { + name: 'NODE_APPLICATION_NAME', + font: ServerMapTheme.general.common.font.small, + margin: new go.Margin(1, 2), + column: 2, + alignment: go.Spot.Left + }, + new go.Binding('stroke', 'tableHeader', function( tableHeader ) { + return tableHeader === true ? '#1BABF4' : '#000'; + }), + new go.Binding('text', 'applicationName') + ), + $( + go.TextBlock, { + font: ServerMapTheme.general.common.font.small, + margin: new go.Margin(1, 2), + column: 3, + alignment: go.Spot.Right + }, + new go.Binding('text', 'totalCount', function (val) { + return val === '' ? '' : parseInt(val, 10).toLocaleString(); + }) + ) + ); + return $( + go.Node, + go.Panel.Auto, + { + position: new go.Point(0, 0), + selectionAdorned: false, + click: function (event: go.DiagramEvent, obj: go.GraphObject) { + serverMapComponent.onClickNode(event, obj); + }, + doubleClick: function(event: go.DiagramEvent, obj: go.GraphObject) { + serverMapComponent.onDoubleClickNode(event, obj); + }, + contextClick: function(event: go.DiagramEvent, obj: go.GraphObject) { + serverMapComponent.onContextClickNode(event, obj); + } + }, + $( + go.Shape, + 'Rectangle', + { + name: 'BORDER_SHAPE', + fill: '#FFF', + stroke: '#D0D7DF', + strokeWidth: 0 + } + ), + $( + go.Panel, + go.Panel.Table, + { + cursor: 'pointer' + }, + $(go.RowColumnDefinition, {row: 0, column: 0, width: 140, height: 95}), + $(go.RowColumnDefinition, {row: 1, minimum: 30, stretch: go.GraphObject.Fill}), + $( + go.Shape, + 'Rectangle', + { + row: 0, + name: 'TOP_RECT', + column: 0, + height: 95, + strokeWidth: 1, + stretch: go.GraphObject.Horizontal, + stroke: '#D0D7DF' + }, + new go.Binding('fill', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].fill.top; + }), + new go.Binding('stroke', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].stroke; + }), + new go.Binding('strokeWidth', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].strokeWidth; + }) + ), + $( + go.Picture, + { + row: 0, + column: 0, + width: 90, + height: 90, + imageStretch: go.GraphObject.Uniform, + errorFunction: function(e: any) { + e.source = ServerMapTheme.general.common.funcServerMapImagePath(ServerMapTemplateWithGojs.NO_IMAGE_FOUND); + } + }, + new go.Binding('source', 'serviceType', function (type) { + return ServerMapTheme.general.common.funcServerMapImagePath(type); + }) + ), + $( + go.Shape, + 'Rectangle', + { + row: 1, + name: 'BOTTOM_RECT', + column: 0, + margin: new go.Margin(-1, 0, 0, 0), + minSize: new go.Size(140, 30), + stretch: go.GraphObject.Fill + }, + new go.Binding('fill', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].fill.bottom; + }), + new go.Binding('stroke', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].stroke; + }), + new go.Binding('strokeWidth', 'key', function(key) { + const type = serverMapComponent.isBaseApplication(key) ? 'main' : 'normal'; + return ServerMapTheme.general.node[type].strokeWidth; + }) + ), + $( + go.Panel, + go.Panel.Vertical, { + row: 1, + column: 0, + margin: new go.Margin(3, 0, 3, 0), + minSize: new go.Size(138, NaN), + alignment: go.Spot.TopLeft, + alignmentFocus: go.Spot.TopLeft + }, + $( + go.Panel, + go.Panel.Table, { + padding: 6, + visible: true, + minSize: new go.Size(138, NaN), + itemTemplate: groupTableTemplate, + defaultStretch: go.GraphObject.Horizontal + }, + new go.Binding('itemArray', 'topCountNodes') + ) + ), + $( + go.Panel, + go.Panel.Auto, + { + minSize: new go.Size(20, 20), + alignment: go.Spot.TopRight + }, + new go.Binding('visible', 'instanceCount', function (v) { + return v > 1 ? true : false; + }), + $( + go.Shape, + { + figure: 'Rectangle' + }, + new go .Binding('fill', '', function() { + return ServerMapTheme.general.instance.shape.fill; + }), + new go .Binding('stroke', '', function() { + return ServerMapTheme.general.instance.shape.stroke; + }), + new go .Binding('strokeWidth', '', function() { + return ServerMapTheme.general.instance.shape.strokeWidth; + }) + ), + $( + go.TextBlock, + { + height: 20, + editable: false, + textAlign: 'center', + verticalAlignment: go.Spot.Center + }, + new go .Binding('font', '', function() { + return ServerMapTheme.general.common.font.small; + }), + new go .Binding('stroke', '', function() { + return ServerMapTheme.general.instance.text.stroke; + }), + new go.Binding('text', 'instanceCount') + ) + ) + ) + ); + } + public static makeLinkTemplate(serverMapComponent: any) { + const $ = go.GraphObject.make; + return $( + go.Link, + { + corner: 10, + cursor: 'pointer', + layerName: 'Foreground', + reshapable: false, + selectionAdorned: false, + click: function (event: go.DiagramEvent, obj: go.GraphObject) { + serverMapComponent.onClickLink(event, obj); + }, + doubleClick: function(event: go.DiagramEvent, obj: go.GraphObject) { + serverMapComponent.onDoubleClickLink(event, obj); + }, + contextClick: function (event: go.DiagramEvent, obj: go.GraphObject) { + serverMapComponent.onContextClickLink(event, obj); + } + }, + $( + go.Shape, + { + name: 'LINK', + stroke: ServerMapTheme.general.link.normal.line.stroke, + isPanelMain: true, + strokeWidth: ServerMapTheme.general.link.normal.line.strokeWidth + } + ), + $( + go.Shape, + { + name: 'ARROW', + fill: ServerMapTheme.general.link.normal.arrow.fill, + scale: 1.5, + stroke: ServerMapTheme.general.link.normal.arrow.stroke, + toArrow: 'standard' + } + ), + $( + go.Panel, + go.Panel.Auto, + $( + go.Shape, + 'Rectangle', + { + fill: ServerMapTheme.general.link.normal.textBox.fill, + stroke: ServerMapTheme.general.link.normal.textBox.stroke, + portId: '', + fromLinkable: true, + toLinkable: true + } + ), + $( + go.Panel, + go.Panel.Horizontal, + { + margin: 4 + }, + $( + go.Picture, + { + source: ServerMapTheme.general.common.funcServerMapImagePath(ServerMapTheme.general.common.icon.filter), + width: 28, + height: 28, + margin: 1, + visible: false, + imageStretch: go.GraphObject.Uniform + }, + new go.Binding('visible', 'isFiltered') + ), + $( + go.TextBlock, + { + name: 'LINK_TEXT', + font: ServerMapTheme.general.common.font.normal, + margin: new go.Margin(1), + textAlign: 'center' + }, + new go.Binding('text', 'totalCount', function (val) { + return parseInt(val, 10).toLocaleString(); + }), + new go.Binding('stroke', 'hasAlert', function (hasAlert) { + if ( hasAlert ) { + return ServerMapTheme.general.link.normal.fontColor.alert; + } else { + return ServerMapTheme.general.link.normal.fontColor.normal; + } + }) + ) + ) + ), + new go.Binding('curve', 'curve', function (val) { + console.log( 'curve', val ); + return go.Link[val]; + }), + new go.Binding('routing', 'routing', function (val) { + console.log( 'routing', val ); + return go.Link[val]; + }), + new go.Binding('curviness', 'curviness') + ); + } + +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-template.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-template.ts new file mode 100644 index 000000000000..f67c64baf94b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-template.ts @@ -0,0 +1,111 @@ +import ServerMapTheme from './server-map-theme'; +import { NodeGroup } from './node-group.class'; + +export abstract class ServerMapTemplate { + private static readonly MIN_ARC_RATIO = 0.05; + private static readonly RADIUS = 62; + private static readonly DIAMETER = 2 * Math.PI * ServerMapTemplate.RADIUS; + + private static getCompleteSVGCircleString(showDefault: boolean, responseTime: IResponseTime): string { + if (showDefault) { + return ServerMapTemplate.getSVGCircleString({ + stroke: ServerMapTheme.general.circle.default.stroke, + strokeWidth: ServerMapTheme.general.circle.default.strokeWidth + }); + } else { + const sum = Object.keys(responseTime).reduce((prev: number, curr: keyof IResponseTime) => prev + responseTime[curr], 0); + const slowArc = ServerMapTemplate.calcArc(sum, responseTime.Slow); + const badArc = ServerMapTemplate.calcArc(sum, responseTime.Error); + // 원의 중심을 (0,0)이라고 할때, stroke-dashoffset 시작점이 12시방향(0,r)이 아니라 3시방향(r,0)이라서 3/4지름을 기준으로 사용 + const slowArcOffset = -1 * (0.75 * ServerMapTemplate.DIAMETER - (slowArc + badArc)); + const badArcOffset = -1 * (0.75 * ServerMapTemplate.DIAMETER - badArc); + + return ServerMapTemplate.getSVGCircleString({ + stroke: ServerMapTheme.general.circle.good.stroke, + strokeWidth: ServerMapTheme.general.circle.default.strokeWidth, + }) + ServerMapTemplate.getSVGCircleString({ + stroke: ServerMapTheme.general.circle.slow.stroke, + strokeWidth: ServerMapTheme.general.circle.default.strokeWidth, + strokeDashOffset: slowArcOffset, + strokeDashArray: slowArc + }) + ServerMapTemplate.getSVGCircleString({ + stroke: ServerMapTheme.general.circle.bad.stroke, + strokeWidth: ServerMapTheme.general.circle.default.strokeWidth, + strokeDashOffset: badArcOffset, + strokeDashArray: badArc + }); + } + } + + private static getSVGCircleString(styleOption: {[key: string]: any}): string { + const { stroke, strokeWidth, strokeDashOffset = 0, strokeDashArray = 'none' } = styleOption; + + return ` + `; + } + + private static calcArc(sum: number, value: number): number { + return value === 0 ? 0 + : value / sum < ServerMapTemplate.MIN_ARC_RATIO ? ServerMapTemplate.DIAMETER * ServerMapTemplate.MIN_ARC_RATIO + : value / sum * ServerMapTemplate.DIAMETER; + } + + private static getAlertSVGImgString(img: HTMLImageElement): string { + const dataURL = this.getDataURLFromImg(img); + + return ``; + } + + private static getDataURLFromImg(img: HTMLImageElement): string { + const canvas = document.createElement('canvas'); + + canvas.getContext('2d').drawImage(img, 0, 0); + return canvas.toDataURL(); + } + + private static getSVGImageStyle(width: number, height: number): {[key: string]: any} { + /** + * ServerMap Image Size Group + * 1. 100 * 65 + * 2. 142 * 74 + * 3. 92 * 25 + * 4. 63 * 87? + */ + // TODO: static한 수치가아니라 비율로? + return { + size: width > 100 ? 200 : 300, + transform: { + translateX: width > 100 ? -45 : -50, + translateY: height > 65 ? -20 : height > 30 ? -45 : -20 + } + }; + } + + private static getServiceTypeSVGImgString(img: HTMLImageElement): string { + const dataURL = ServerMapTemplate.getDataURLFromImg(img); + const { size, transform } = ServerMapTemplate.getSVGImageStyle(img.width, img.height); + + return ``; + } + + private static getInstanceCountTextString(instanceCount: number): string { + return `${instanceCount >= 2 ? instanceCount : ''}`; + } + + public static getSVGString(img: HTMLImageElement[], nodeData: {[key: string]: any}): string { + const { key, isAuthorized, isWas, histogram, instanceCount } = nodeData; + const isMergedNode = NodeGroup.isGroupKey(key); + + return `` + + ServerMapTemplate.getCompleteSVGCircleString(isMergedNode || !(isAuthorized && isWas), histogram) + + (img[1] ? ServerMapTemplate.getAlertSVGImgString(img[1]) : ``) + + ServerMapTemplate.getServiceTypeSVGImgString(img[0]) + + ServerMapTemplate.getInstanceCountTextString(instanceCount) + + ``; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-theme.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-theme.ts new file mode 100644 index 000000000000..98c9e15a9c9e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/class/server-map-theme.ts @@ -0,0 +1,122 @@ +export default { + general: { + common: { + funcServerMapImagePath: null, + icon: { + error: 'ERROR_s', + filter: 'FILTER' + }, + font: { + small: '8pt avn55,NanumGothic,ng,dotum,AppleGothic,sans-serif', + normal: '10pt avn85,NanumGothic,ng,dotum,AppleGothic,sans-serif', + big: 'bold 12pt avn55,NanumGothic,ng,dotum,AppleGothic,sans-serif' + } + }, + circle: { + default: { + stroke: '#D0D7DF', + strokeWidth: 6 + }, + good: { + stroke: '#32BA94', + strokeWidth: 4, + }, + slow: { + stroke: '#E48022', + strokeWidth: 4, + }, + bad: { + stroke: '#F0515B', + strokeWidth: 4, + } + }, + instance: { + shape: { + fill: '#90A1AB', + stroke: '#90A1AB', + strokeWidth: 1 + }, + text: { + stroke: '#FFF' + } + }, + node: { + main: { + border: { + stroke: '#FFF', + strokeWidth: 0 + }, + stroke: '#D0D7DF', + strokeWidth: 1, + fill: { + top: '#F3F4F6', + bottom: '#FFF' + }, + text: { + stroke: '#000' + } + }, + normal: { + border: { + stroke: '#FFF', + strokeWidth: 0 + }, + stroke: '#D0D7DF', + strokeWidth: 1, + fill: { + top: '#FFF', + bottom: '#F3F4F6' + }, + text: { + stroke: '#000' + } + }, + highlight: { + border: { + stroke: '#4A61D1', + strokeWidth: 4 + } + } + }, + link: { + normal: { + line: { + stroke: '#C0C3C8', + strokeWidth: 1.5 + }, + arrow: { + fill: '#C0C3C8', + stroke: '#C0C3C8', + }, + textBox: { + fill: '#EDF2F8', + stroke: '#EDF2F8' + }, + fontColor: { + normal: '#000', + alert: '#FF1300' + }, + fontFamily: '10pt avn85,NanumGothic,ng,dotum,AppleGothic,sans-serif' + }, + highlight: { + line: { + stroke: '#4763D0', + strokeWidth: 1.5 + }, + arrow: { + fill: '#4763D0', + stroke: '#4763D0', + }, + textBox: { + fill: '#EDF2F8', + stroke: '#EDF2F8' + }, + fontColor: { + normal: '#000', + alert: '#FF1300' + }, + fontFamily: 'bold 12pt avn55,NanumGothic,ng,dotum,AppleGothic,sans-serif' + } + } + } +}; diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/index.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/index.ts new file mode 100644 index 000000000000..d09335130791 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/index.ts @@ -0,0 +1,44 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { ServerMapInteractionService } from './server-map-interaction.service'; +import { ServerMapOverviewComponent } from './server-map-overview.component'; +import { ServerMapComponent } from './server-map.component'; +import { ServerMapContainerComponent } from './server-map-container.component'; +import { ServerMapForFilteredMapContainerComponent } from './server-map-for-filtered-map-container.component'; +import { ServerMapForTransactionListContainerComponent } from './server-map-for-transaction-list-container.component'; +import { ServerMapForTransactionViewContainerComponent } from './server-map-for-transaction-view-container.component'; +import { ServerMapDataService } from './server-map-data.service'; +import { ServerMapForFilteredMapDataService } from './server-map-for-filtered-map-data.service'; +import { LinkContextPopupModule } from 'app/core/components/link-context-popup'; +import { ServerMapContextPopupModule } from 'app/core/components/server-map-context-popup'; + +@NgModule({ + declarations: [ + ServerMapComponent, + ServerMapOverviewComponent, + ServerMapContainerComponent, + ServerMapForFilteredMapContainerComponent, + ServerMapForTransactionListContainerComponent, + ServerMapForTransactionViewContainerComponent + ], + imports: [ + SharedModule, + LinkContextPopupModule, + ServerMapContextPopupModule + ], + exports: [ + ServerMapComponent, + ServerMapOverviewComponent, + ServerMapContainerComponent, + ServerMapForFilteredMapContainerComponent, + ServerMapForTransactionListContainerComponent, + ServerMapForTransactionViewContainerComponent + ], + providers: [ + ServerMapInteractionService, + ServerMapDataService, + ServerMapForFilteredMapDataService + ] +}) +export class ServerMapModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-container.component.css new file mode 100644 index 000000000000..36925ca792b8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-container.component.css @@ -0,0 +1,56 @@ +.l-popup-section { + display: flex; + z-index: 9; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; +} +.l-popup-section:before { + content:''; + display:block; + height:100%; + width:100%; + background:#000; + opacity:0.6; + position:absolute; + left:0; + top:0; +} +.l-popup-section article { + width: 90%; + min-width: 500px; + max-width: 1000px; + box-shadow: 1 1 10px 0 rgba(0, 0, 0, 0.75); + position: static; + background: #fff; + border: 1px solid #e5e8f0; + text-align: left; + z-index: 10; +} +.l-contents-group { + background: #F6F8FB; + padding: 17px 18px; + overflow-y: auto; +} +.l-sql-list { + margin: 0; +} +.l-sql-list dt { + font-size: 13px; + font-weight: 600; + color: #333; + margin: 0 0 12px; +} +.l-sql-list dd { + border: 1px solid #cfd7e1; + background: #fff; + padding: 28px 28px; + font-size: 13px; + color: #999; + line-height: 2em; + position: relative; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-container.component.html new file mode 100644 index 000000000000..d84fdd0920b8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-container.component.html @@ -0,0 +1,27 @@ + + + + + +
+
+
+
+
Notice
+
{{i18nText['NO_AGENTS']}}
+
+
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-container.component.ts new file mode 100644 index 000000000000..97e72fe62b4c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-container.component.ts @@ -0,0 +1,215 @@ +import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; +import { Router, NavigationStart, RouterEvent } from '@angular/router'; +import { Subject } from 'rxjs'; +import { takeUntil, filter, map, switchMap } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { + StoreHelperService, + NewUrlStateNotificationService, + WebAppSettingDataService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPathId } from 'app/shared/models'; +import { EndTime } from 'app/core/models'; +import { SERVER_MAP_TYPE, ServerMapType, NodeGroup, ServerMapData } from 'app/core/components/server-map/class'; +import { ServerMapDataService } from './server-map-data.service'; +import { LinkContextPopupContainerComponent } from 'app/core/components/link-context-popup/link-context-popup-container.component'; +import { ServerMapContextPopupContainerComponent } from 'app/core/components/server-map-context-popup/server-map-context-popup-container.component'; + +@Component({ + selector: 'pp-server-map-container', + templateUrl: './server-map-container.component.html', + styleUrls: ['./server-map-container.component.css'] +}) +export class ServerMapContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + i18nText: { [key: string]: string } = { + NO_AGENTS: '' + }; + funcServerMapImagePath: Function; + baseApplicationKey: string; + showOverview = false; + showLoading = true; + useDisable = true; + mapData: ServerMapData; + endTime: string; + period: string; + constructor( + private router: Router, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private serverMapDataService: ServerMapDataService, + private webAppSettingDataService: WebAppSettingDataService, + private dynamicPopupService: DynamicPopupService, + private analyticsService: AnalyticsService, + @Inject(SERVER_MAP_TYPE) public type: ServerMapType + ) {} + ngOnInit() { + this.funcServerMapImagePath = this.webAppSettingDataService.getServerMapIconPathMakeFunc(); + this.addPageLoadingHandler(); + this.getI18NText(); + + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + map((urlService: NewUrlStateNotificationService) => { + if (urlService.isRealTimeMode()) { + const endTime = urlService.getUrlServerTimeData(); + const period = this.webAppSettingDataService.getSystemDefaultPeriod(); + this.initVarBeforeDataLoad( + EndTime.formatDate(endTime), + period.getValueWithTime(), + urlService.getPathValue(UrlPathId.APPLICATION) + ); + return [endTime - (period.getValue() * 60 * 1000), endTime]; + } else { + this.storeHelperService.dispatch(new Actions.UpdateServerMapTargetSelected(null)); + this.initVarBeforeDataLoad( + urlService.getPathValue(UrlPathId.END_TIME).getEndTime(), + urlService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + urlService.getPathValue(UrlPathId.APPLICATION) + ); + return [urlService.getStartTimeToNumber(), urlService.getEndTimeToNumber()]; + } + }), + switchMap((range: number[]) => { + return this.serverMapDataService.getData(range); + }) + ).subscribe((res: IServerMapInfo) => { + this.mapData = new ServerMapData(res.applicationMapData.nodeDataArray, res.applicationMapData.linkDataArray); + this.storeHelperService.dispatch(new Actions.UpdateServerMapData(this.mapData)); + if (this.hasNodeData() === false) { + this.storeHelperService.dispatch(new Actions.UpdateServerMapTargetSelected(null)); + } + }); + this.storeHelperService.getServerMapDisableState(this.unsubscribe).subscribe((disabled: boolean) => { + this.useDisable = disabled; + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private addPageLoadingHandler(): void { + this.router.events.pipe( + filter((e: RouterEvent) => { + return e instanceof NavigationStart; + }) + ).subscribe((e) => { + this.showLoading = true; + this.useDisable = true; + }); + } + private getI18NText(): void { + this.translateService.get('COMMON.NO_AGENTS').subscribe((i18n: string) => { + this.i18nText['NO_AGENTS'] = i18n; + }); + } + private initVarBeforeDataLoad(endTime: string, period: string, application: IApplication): void { + this.endTime = endTime; + this.period = period; + this.showLoading = true; + this.useDisable = true; + this.baseApplicationKey = application.getKeyStr(); + } + private hasNodeData(): boolean { + return this.mapData && this.mapData.getNodeCount() !== 0; + } + showGuide(): boolean { + return this.hasNodeData() === false && this.showLoading === false; + } + onRenderCompleted({showOverView}: {showOverView: boolean}): void { + this.showLoading = false; + this.useDisable = false; + this.showOverview = this.hasNodeData() && showOverView; + } + onClickBackground($event: any): void { + } + onClickNode(nodeData: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_NODE); + let payload; + if (NodeGroup.isGroupKey(nodeData.key)) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SHOW_GROUPED_NODE_VIEW); + payload = { + period: this.period, + endTime: this.endTime, + isAuthorized: nodeData.isAuthorized, + isNode: true, + isLink: false, + isMerged: true, + isWAS: nodeData.isWas, + node: nodeData.mergedNodes.map((nodeInfo: any) => { + return nodeInfo.key; + }) + }; + } else { + payload = { + period: this.period, + endTime: this.endTime, + isAuthorized: nodeData.isAuthorized, + isNode: true, + isLink: false, + isMerged: false, + isWAS: nodeData.isWas, + node: [nodeData.key], + hasServerList: nodeData.instanceCount > 0 ? true : false + }; + } + this.storeHelperService.dispatch(new Actions.UpdateServerMapTargetSelected(payload)); + } + onClickLink(linkData: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_LINK); + let payload; + if (NodeGroup.isGroupKey(linkData.key)) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SHOW_GROUPED_LINK_VIEW); + payload = { + period: this.period, + endTime: this.endTime, + isAuthorized: linkData.isAuthorized, + isNode: false, + isLink: true, + isMerged: true, + isWAS: false, + node: [linkData.from], + link: linkData.targetInfo.map((linkInfo: any) => { + return linkInfo.key; + }), + hasServerList: false + }; + } else { + payload = { + period: this.period, + endTime: this.endTime, + isAuthorized: linkData.isAuthorized, + isNode: false, + isLink: true, + isMerged: false, + isWAS: false, + node: [linkData.from], + link: [linkData.key], + hasServerList: false + }; + } + this.storeHelperService.dispatch(new Actions.UpdateServerMapTargetSelected(payload)); + } + onDoubleClickBackground($event: any): void {} + onContextClickBackground(coord: ICoordinate): void { + this.dynamicPopupService.openPopup({ + data: this.mapData, + coord, + component: ServerMapContextPopupContainerComponent + }); + } + onContextClickNode($event: any): void {} + onContextClickLink({key, coord}: {key: string, coord: ICoordinate}): void { + this.dynamicPopupService.openPopup({ + data: this.mapData.getLinkData(key), + coord, + component: LinkContextPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-data.service.ts new file mode 100644 index 000000000000..09bb15e843fa --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-data.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { UrlQuery, UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService, WebAppSettingDataService } from 'app/shared/services'; + +@Injectable() +export class ServerMapDataService { + private url = 'getServerMapDataV2.pinpoint'; + constructor( + private http: HttpClient, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService + ) {} + + getData([from, to]: number[]): Observable { + return this.http.get(this.url, this.makeRequestOptionsArgs(from, to)); + } + + private makeRequestOptionsArgs(from: number, to: number): object { + return { + params: { + applicationName: this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getApplicationName(), + serviceTypeName: this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getServiceType(), + from, + to, + calleeRange: this.newUrlStateNotificationService.hasValue(UrlQuery.INBOUND) ? this.newUrlStateNotificationService.getQueryValue(UrlQuery.INBOUND) : this.webAppSettingDataService.getSystemDefaultInbound(), + callerRange: this.newUrlStateNotificationService.hasValue(UrlQuery.OUTBOUND) ? this.newUrlStateNotificationService.getQueryValue(UrlQuery.OUTBOUND) : this.webAppSettingDataService.getSystemDefaultOutbound(), + wasOnly: this.newUrlStateNotificationService.hasValue(UrlQuery.WAS_ONLY) ? this.newUrlStateNotificationService.getQueryValue(UrlQuery.WAS_ONLY) : false, + bidirectional: this.newUrlStateNotificationService.hasValue(UrlQuery.BIDIRECTIONAL) ? this.newUrlStateNotificationService.getQueryValue(UrlQuery.BIDIRECTIONAL) : false + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-container.component.css new file mode 100644 index 000000000000..36925ca792b8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-container.component.css @@ -0,0 +1,56 @@ +.l-popup-section { + display: flex; + z-index: 9; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; +} +.l-popup-section:before { + content:''; + display:block; + height:100%; + width:100%; + background:#000; + opacity:0.6; + position:absolute; + left:0; + top:0; +} +.l-popup-section article { + width: 90%; + min-width: 500px; + max-width: 1000px; + box-shadow: 1 1 10px 0 rgba(0, 0, 0, 0.75); + position: static; + background: #fff; + border: 1px solid #e5e8f0; + text-align: left; + z-index: 10; +} +.l-contents-group { + background: #F6F8FB; + padding: 17px 18px; + overflow-y: auto; +} +.l-sql-list { + margin: 0; +} +.l-sql-list dt { + font-size: 13px; + font-weight: 600; + color: #333; + margin: 0 0 12px; +} +.l-sql-list dd { + border: 1px solid #cfd7e1; + background: #fff; + padding: 28px 28px; + font-size: 13px; + color: #999; + line-height: 2em; + position: relative; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-container.component.html new file mode 100644 index 000000000000..469691ba1cc9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-container.component.html @@ -0,0 +1,28 @@ + + + + + +
+
+
+
+
Notice
+
{{i18nText['NO_AGENTS']}}
+
+
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-container.component.ts new file mode 100644 index 000000000000..c95d22b0e35b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-container.component.ts @@ -0,0 +1,258 @@ +import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; +import { Router, NavigationStart } from '@angular/router'; +import { Subject, combineLatest } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { Actions } from 'app/shared/store'; +import { + StoreHelperService, + NewUrlStateNotificationService, + WebAppSettingDataService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { UrlPathId } from 'app/shared/models'; +import { Filter } from 'app/core/models'; +import { SERVER_MAP_TYPE, ServerMapType, NodeGroup, ServerMapData, MergeServerMapData } from 'app/core/components/server-map/class'; +import { ServerMapForFilteredMapDataService } from './server-map-for-filtered-map-data.service'; +import { LinkContextPopupContainerComponent } from 'app/core/components/link-context-popup/link-context-popup-container.component'; +import { ServerMapContextPopupContainerComponent } from 'app/core/components/server-map-context-popup/server-map-context-popup-container.component'; + +@Component({ + selector: 'pp-server-map-for-filtered-map-container', + templateUrl: './server-map-for-filtered-map-container.component.html', + styleUrls: ['./server-map-for-filtered-map-container.component.css'] +}) +export class ServerMapForFilteredMapContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + i18nText: { [key: string]: string } = { + NO_AGENTS: '' + }; + mergedNodeDataList: INodeInfo[] = []; + mergedLinkDataList: ILinkInfo[] = []; + mergedServerMapData: any = {}; + mergedScatterData: any; + loadingCompleted = false; + + mapData: ServerMapData; + baseApplicationKey: string; + showOverview = false; + useDisable = true; + showLoading = true; + funcServerMapImagePath: Function; + endTime: string; + period: string; + constructor( + private router: Router, + private storeHelperService: StoreHelperService, + private translateService: TranslateService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private serverMapForFilteredMapDataService: ServerMapForFilteredMapDataService, + private webAppSettingDataService: WebAppSettingDataService, + private dynamicPopupService: DynamicPopupService, + private analyticsService: AnalyticsService, + @Inject(SERVER_MAP_TYPE) public type: ServerMapType + ) {} + ngOnInit() { + this.funcServerMapImagePath = this.webAppSettingDataService.getServerMapIconPathMakeFunc(); + this.getI18NText(); + this.router.events.pipe( + filter(e => e instanceof NavigationStart) + ).subscribe((e) => { + this.showLoading = true; + this.useDisable = true; + }); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.endTime = urlService.getPathValue(UrlPathId.END_TIME).getEndTime(); + this.period = urlService.getPathValue(UrlPathId.PERIOD).getValueWithTime(); + this.showLoading = true; + this.useDisable = true; + this.baseApplicationKey = urlService.getPathValue(UrlPathId.APPLICATION).getKeyStr(); + this.serverMapForFilteredMapDataService.startDataLoad(); + }); + this.serverMapForFilteredMapDataService.onServerMapData$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((serverMapAndScatterData: any) => { + this.storeHelperService.dispatch(new Actions.AddScatterChartData(serverMapAndScatterData.applicationScatterData)); + this.mergeServerMapData(serverMapAndScatterData); + this.mapData = new ServerMapData(this.mergedNodeDataList, this.mergedLinkDataList, Filter.instanceFromString(this.newUrlStateNotificationService.hasValue(UrlPathId.FILTER) ? this.newUrlStateNotificationService.getPathValue(UrlPathId.FILTER) : '')); + this.storeHelperService.dispatch(new Actions.UpdateServerMapData(this.mapData)); + if (this.hasNodeData() === false) { + this.storeHelperService.dispatch(new Actions.UpdateServerMapTargetSelected(null)); + } + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getServerMapLoadingState(this.unsubscribe).subscribe((state: string) => { + switch (state) { + case 'loading': + this.loadingCompleted = false; + this.showLoading = true; + this.useDisable = true; + break; + case 'pause': + this.loadingCompleted = false; + this.showLoading = false; + this.useDisable = false; + break; + case 'completed': + this.loadingCompleted = true; + break; + } + }); + this.storeHelperService.getServerMapDisableState(this.unsubscribe).subscribe((disabled: boolean) => { + this.useDisable = disabled; + }); + } + private getI18NText(): void { + combineLatest( + this.translateService.get('COMMON.NO_AGENTS') + ).pipe(takeUntil(this.unsubscribe)).subscribe((i18n: string[]) => { + this.i18nText['NO_AGENTS'] = i18n[0]; + }); + } + private hasNodeData(): boolean { + return this.mapData && this.mapData.getNodeCount() !== 0; + } + showGuide(): boolean { + return this.hasNodeData() === false && this.showLoading === false; + } + onRenderCompleted({showOverView}: {showOverView: boolean}): void { + if (this.loadingCompleted === true) { + this.showLoading = false; + this.useDisable = false; + this.showOverview = this.hasNodeData() && showOverView; + } + } + onClickBackground($event: any): void { + } + onClickGroupNode($event: any): void { + } + onClickNode(nodeData: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_NODE); + let payload; + if (NodeGroup.isGroupKey(nodeData.key)) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SHOW_GROUPED_NODE_VIEW); + payload = { + period: this.period, + endTime: this.endTime, + isAuthorized: nodeData.isAuthorized, + isNode: true, + isLink: false, + isMerged: true, + isWAS: nodeData.isWas, + node: nodeData.mergedNodes.map((nodeInfo: any) => { + return nodeInfo.key; + }) + }; + } else { + payload = { + period: this.period, + endTime: this.endTime, + isAuthorized: nodeData.isAuthorized, + isNode: true, + isLink: false, + isMerged: false, + isWAS: nodeData.isWas, + node: [nodeData.key], + hasServerList: nodeData.instanceCount > 0 ? true : false + }; + } + this.storeHelperService.dispatch(new Actions.UpdateServerMapTargetSelected(payload)); + } + onClickLink(linkData: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_LINK); + let payload; + if (NodeGroup.isGroupKey(linkData.key)) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SHOW_GROUPED_LINK_VIEW); + payload = { + period: this.period, + endTime: this.endTime, + isAuthorized: linkData.isAuthorized, + isNode: false, + isLink: true, + isMerged: true, + isWAS: false, + node: [linkData.from], + link: linkData.targetInfo.map((linkInfo: any) => { + return linkInfo.key; + }) + }; + } else { + payload = { + period: this.period, + endTime: this.endTime, + isAuthorized: linkData.isAuthorized, + isNode: false, + isLink: true, + isMerged: false, + isWAS: false, + node: [linkData.from], + link: [linkData.key] + }; + } + this.storeHelperService.dispatch(new Actions.UpdateServerMapTargetSelected(payload)); + } + onDoubleClickBackground($event: any): void {} + onContextClickBackground(coord: ICoordinate): void { + this.dynamicPopupService.openPopup({ + data: this.mapData, + coord, + component: ServerMapContextPopupContainerComponent + }); + } + onContextClickNode($event: any): void {} + onContextClickLink({key, coord}: {key: string, coord: ICoordinate}): void { + this.dynamicPopupService.openPopup({ + data: this.mapData.getLinkData(key), + coord, + component: LinkContextPopupContainerComponent + }); + } + mergeServerMapData(serverMapAndScatterData: any): void { + console.time('merge server-map data'); + const newNodeDataList = serverMapAndScatterData.applicationMapData.nodeDataArray; + const newLinkDataList = serverMapAndScatterData.applicationMapData.linkDataArray; + + if (this.mergedNodeDataList.length === 0) { + this.mergedNodeDataList = newNodeDataList; + } else { + this.mergeNodeDataList(newNodeDataList); + } + if (this.mergedLinkDataList.length === 0) { + this.mergedLinkDataList = newLinkDataList; + } else { + this.mergeLinkDataList(newLinkDataList); + } + console.timeEnd('merge server-map data'); + } + mergeNodeDataList(newNodeData: INodeInfo[]): void { + newNodeData.forEach((nodeData: INodeInfo) => { + if (this.mapData && this.mapData.getNodeData(nodeData.key)) { + const currentNodeData = this.mapData.getNodeData(nodeData.key); + MergeServerMapData.mergeNodeData(currentNodeData, nodeData); + } else { + this.mergedNodeDataList.push(nodeData); + } + }); + } + mergeLinkDataList(newLinkData: ILinkInfo[]): void { + newLinkData.forEach((linkData: ILinkInfo) => { + if (this.mapData && this.mapData.getLinkData(linkData.key)) { + const currentLinkData = this.mapData.getLinkData(linkData.key); + MergeServerMapData.mergeLinkData(currentLinkData, linkData); + } else { + this.mergedLinkDataList.push(linkData); + } + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-data.service.ts new file mode 100644 index 000000000000..239845f88b8f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-filtered-map-data.service.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Subject, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { Actions } from 'app/shared/store'; +import { UrlQuery, UrlPathId } from 'app/shared/models'; +import { WebAppSettingDataService, NewUrlStateNotificationService, StoreHelperService } from 'app/shared/services'; + +@Injectable() +export class ServerMapForFilteredMapDataService { + private url = 'getFilteredServerMapDataMadeOfDotGroup.pinpoint'; + private REQUEST_LIMIT = 5000; + // 아래 두 값은 scatter-chart에서 사용되는 파라미터 값 + private X_GROUP_UNIT = 987; + private Y_GROUP_UNIT = 57; + private requsting = false; + private flagLoadData = true; + private nextTo: number; + private serverMapData = new Subject(); + onServerMapData$: Observable; + constructor( + private http: HttpClient, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService + ) { + this.onServerMapData$ = this.serverMapData.asObservable(); + } + startDataLoad(to?: number): void { + this.loadData(to); + } + stopDataLoad(): void { + this.flagLoadData = false; + } + resumeDataLoad(): void { + if (this.requsting === false) { + this.flagLoadData = true; + this.startDataLoad(this.nextTo); + } + } + private loadData(to?: number): void { + this.requsting = true; + this.storeHelperService.dispatch(new Actions.UpdateServerMapLoadingState('loading')); + this.http.get(this.url, this.makeRequestOptionsArgs(to)).pipe( + map(res => { + return res || {}; + }) + ).subscribe((res: any) => { + if (res['lastFetchedTimestamp'] > res['applicationMapData']['range']['from']) { + this.nextTo = res['lastFetchedTimestamp'] - 1; + if (this.flagLoadData) { + this.loadData(this.nextTo); + } else { + this.storeHelperService.dispatch(new Actions.UpdateServerMapLoadingState('pause')); + } + } else { + this.storeHelperService.dispatch(new Actions.UpdateServerMapLoadingState('completed')); + } + this.serverMapData.next(res); + this.requsting = false; + }); + } + private makeRequestOptionsArgs(to?: number): any { + return { + params: { + applicationName: this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).applicationName, + serviceTypeName: this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).serviceType, + from: this.newUrlStateNotificationService.getStartTimeToNumber(), + to: (to || this.newUrlStateNotificationService.getEndTimeToNumber()), + originTo: this.newUrlStateNotificationService.getEndTimeToNumber(), + calleeRange: this.newUrlStateNotificationService.hasValue(UrlQuery.INBOUND) ? this.newUrlStateNotificationService.getQueryValue(UrlQuery.INBOUND) : this.webAppSettingDataService.getUserDefaultInbound(), + callerRange: this.newUrlStateNotificationService.hasValue(UrlQuery.OUTBOUND) ? this.newUrlStateNotificationService.getQueryValue(UrlQuery.OUTBOUND) : this.webAppSettingDataService.getUserDefaultOutbound(), + filter: this.newUrlStateNotificationService.hasValue(UrlPathId.FILTER) ? encodeURIComponent(this.newUrlStateNotificationService.getPathValue(UrlPathId.FILTER)) : '', + hint: this.newUrlStateNotificationService.hasValue(UrlPathId.HINT) ? encodeURIComponent(this.newUrlStateNotificationService.getPathValue(UrlPathId.HINT)) : '', + v: 4, + limit: this.REQUEST_LIMIT, + xGroupUnit: this.X_GROUP_UNIT, // for scatter-chart + yGroupUnit: this.Y_GROUP_UNIT // for scatter-chart + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-list-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-list-container.component.html new file mode 100644 index 000000000000..1c3f9895ce2b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-list-container.component.html @@ -0,0 +1,16 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-list-container.component.ts new file mode 100644 index 000000000000..505d0f11a353 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-list-container.component.ts @@ -0,0 +1,96 @@ +import { Component, OnInit, OnDestroy, Inject, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { StoreHelperService, NewUrlStateNotificationService, WebAppSettingDataService , TransactionViewTypeService, VIEW_TYPE } from 'app/shared/services'; +import { ServerMapData } from './class/server-map-data.class'; +import { SERVER_MAP_TYPE, ServerMapType } from './class/server-map-factory'; + + +@Component({ + selector: 'pp-server-map-for-transaction-list-container', + templateUrl: './server-map-for-transaction-list-container.component.html', + styleUrls: ['./server-map-for-transaction-list-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ServerMapForTransactionListContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private transactionDetailInfo: ITransactionDetailData; + transactionInfo: ITransactionMetaData; + hiddenComponent = false; + baseApplicationKey = ''; + mapData: ServerMapData; + showLoading = true; + funcServerMapImagePath: Function; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private transactionViewTypeService: TransactionViewTypeService, + @Inject(SERVER_MAP_TYPE) public type: ServerMapType + ) { + this.funcServerMapImagePath = this.webAppSettingDataService.getServerMapIconPathMakeFunc(); + this.showLoading = false; + } + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.APPLICATION); + }) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.baseApplicationKey = urlService.getPathValue(UrlPathId.APPLICATION).getKeyStr(); + this.changeDetectorRef.detectChanges(); + }); + this.transactionViewTypeService.onChangeViewType$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((viewType: string) => { + if ( viewType === VIEW_TYPE.SERVER_MAP ) { + this.hiddenComponent = false; + this.initCheck(); + } else { + this.hiddenComponent = true; + } + this.changeDetectorRef.detectChanges(); + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTransactionDetailData(this.unsubscribe).pipe( + filter((transactionDetailInfo: ITransactionDetailData) => { + return transactionDetailInfo && transactionDetailInfo.transactionId ? true : false; + }) + ).subscribe((transactionDetailInfo: ITransactionDetailData) => { + this.mapData = null; + this.transactionDetailInfo = transactionDetailInfo; + this.initCheck(); + this.changeDetectorRef.detectChanges(); + }); + } + private initCheck() { + if (this.hiddenComponent === false && this.transactionDetailInfo) { + this.loadTransactionData(); + } + } + private loadTransactionData(): void { + this.mapData = new ServerMapData(this.transactionDetailInfo.applicationMapData.nodeDataArray, this.transactionDetailInfo.applicationMapData.linkDataArray); + } + onRenderCompleted(msg: string): void { + this.showLoading = false; + this.changeDetectorRef.detectChanges(); + } + onClickBackground($event: any): void {} + onClickGroupNode($event: any): void {} + onClickNode($event: any): void {} + onClickLink($event: any): void {} + onDoubleClickBackground($event: any): void {} + onContextClickBackground($event: any): void {} + onContextClickNode($event: any): void {} + onContextClickLink($param: any): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-view-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-view-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-view-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-view-container.component.html new file mode 100644 index 000000000000..b3c53702c8f6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-view-container.component.html @@ -0,0 +1,18 @@ + + + + + diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-view-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-view-container.component.ts new file mode 100644 index 000000000000..c1341bfbdaf8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-for-transaction-view-container.component.ts @@ -0,0 +1,61 @@ +import { Component, OnInit, Inject, ChangeDetectionStrategy } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { switchMap, map, takeUntil } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService, WebAppSettingDataService, TransactionDetailDataService, GutterEventService } from 'app/shared/services'; +import { ServerMapInteractionService } from './server-map-interaction.service'; +import { ServerMapData } from './class/server-map-data.class'; +import { SERVER_MAP_TYPE, ServerMapType } from './class/server-map-factory'; + +@Component({ + selector: 'pp-server-map-for-transaction-view-container', + templateUrl: './server-map-for-transaction-view-container.component.html', + styleUrls: ['./server-map-for-transaction-view-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ServerMapForTransactionViewContainerComponent implements OnInit { + private unsubscribe$: Subject = new Subject(); + baseApplicationKey = ''; + mapData$: Observable; + funcServerMapImagePath: Function; + showLoading = true; + constructor( + private newUrlStateNotificationService: NewUrlStateNotificationService, + private webAppSettingDataService: WebAppSettingDataService, + private transactionDetailDataService: TransactionDetailDataService, + private gutterEventService: GutterEventService, + private serverMapInteractionService: ServerMapInteractionService, + @Inject(SERVER_MAP_TYPE) public type: ServerMapType + ) { } + + ngOnInit() { + this.funcServerMapImagePath = this.webAppSettingDataService.getServerMapIconPathMakeFunc(); + // TODO: ServiceType Empty이슈 체크 #174 + this.mapData$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe( + switchMap((urlService: NewUrlStateNotificationService) => this.transactionDetailDataService.getData( + urlService.getPathValue(UrlPathId.AGENT_ID), + urlService.getPathValue(UrlPathId.SPAN_ID), + urlService.getPathValue(UrlPathId.TRACE_ID), + urlService.getPathValue(UrlPathId.FOCUS_TIMESTAMP) + )), + map((applicationMapData: ITransactionDetailData) => { + return new ServerMapData(applicationMapData.applicationMapData.nodeDataArray, applicationMapData.applicationMapData.linkDataArray); + }) + ); + this.gutterEventService.onGutterResized$.pipe( + takeUntil(this.unsubscribe$) + ).subscribe(() => this.serverMapInteractionService.setRefresh()); + } + onRenderCompleted(msg: string): void { + this.showLoading = false; + } + onClickBackground($event: any): void {} + onClickGroupNode($event: any): void {} + onClickNode($event: any): void {} + onClickLink($event: any): void {} + onDoubleClickBackground($event: any): void {} + onContextClickBackground($event: any): void {} + onContextClickNode($event: any): void {} + onContextClickLink($param: any): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-interaction.service.ts new file mode 100644 index 000000000000..77c57361273f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-interaction.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; +import * as go from 'gojs'; +import { Subject, Observable } from 'rxjs'; + +@Injectable() +export class ServerMapInteractionService { + private outSearchWordSource = new Subject(); + private outSearchResultSource = new Subject(); + private outSelectedApplicationSource = new Subject(); + private outCurrentDiagramSource = new Subject(); + private outRefresh = new Subject(); + private outChangeMergeState = new Subject(); + + public onSearchWord$: Observable; + public onSearchResult$: Observable; + public onSelectedApplication$: Observable; + public onCurrentDiagram$: Observable; + public onRefresh$: Observable; + public onChangeMergeState$: Observable; + + constructor() { + this.onSearchWord$ = this.outSearchWordSource.asObservable(); + this.onSearchResult$ = this.outSearchResultSource.asObservable(); + this.onSelectedApplication$ = this.outSelectedApplicationSource.asObservable(); + this.onCurrentDiagram$ = this.outCurrentDiagramSource.asObservable(); + this.onRefresh$ = this.outRefresh.asObservable(); + this.onChangeMergeState$ = this.outChangeMergeState.asObservable(); + } + + setSearchWord(word: string): void { + this.outSearchWordSource.next(word); + } + setSearchResult(result: IApplication[]): void { + this.outSearchResultSource.next(result); + } + setSelectedApplication(appKey: string): void { + this.outSelectedApplicationSource.next(appKey); + } + setCurrentDiagram(diagram: go.Diagram): void { + this.outCurrentDiagramSource.next(diagram); + } + setRefresh(): void { + this.outRefresh.next(); + } + setMergeState(mergeState: IServerMapMergeState): void { + this.outChangeMergeState.next(mergeState); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-overview.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-overview.component.css new file mode 100644 index 000000000000..dadedc20f03a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-overview.component.css @@ -0,0 +1,10 @@ +div { + width: 180px; + height: 160px; + right: 20px; + bottom: 20px; + z-index: 8; + position: absolute; + background-color: #FFF; + border: 2px solid #BCBFC5; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-overview.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-overview.component.html new file mode 100644 index 000000000000..edc0374881cd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-overview.component.html @@ -0,0 +1 @@ +
diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-overview.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-overview.component.ts new file mode 100644 index 000000000000..262a1b15c17d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map-overview.component.ts @@ -0,0 +1,47 @@ +import { Component, OnInit, ElementRef, Input, ViewChild, OnDestroy } from '@angular/core'; +import * as go from 'gojs'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { ServerMapInteractionService } from './server-map-interaction.service'; + +@Component({ + selector: 'pp-server-map-overview', + templateUrl: './server-map-overview.component.html', + styleUrls: ['./server-map-overview.component.css'] +}) +export class ServerMapOverviewComponent implements OnInit, OnDestroy { + @ViewChild('wrapper') overviewWrapper: ElementRef; + @Input() showOverview = false; + overview: go.Overview; + initialized = false; + unsubscribe: Subject = new Subject(); + constructor( + private serverMapInteractionService: ServerMapInteractionService + ) {} + ngOnInit() { + this.serverMapInteractionService.onCurrentDiagram$.pipe( + takeUntil(this.unsubscribe), + filter((diagram: go.Diagram) => { + // TODO: visjs구현체에서 overview핸들링이 되기전까지 visjs옵션의 경우(null)엔 잠시 막아둠. + return !!diagram; + }) + ).subscribe((diagram: go.Diagram) => { + if (this.initialized === true) { + this.overview.observed = diagram; + } else { + this.overview = go.GraphObject.make(go.Overview, this.overviewWrapper.nativeElement, { + observed: diagram + }); + this.overview.box.elt(0)['figure'] = 'Rectangle'; + this.overview.box.elt(0)['stroke'] = '#E7555A'; + this.overview.box.elt(0)['strokeWidth'] = .5; + this.initialized = true; + } + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.component.css b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.component.css new file mode 100644 index 000000000000..8b14713a6ba5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.component.css @@ -0,0 +1,14 @@ +div { + width: 100%; + height: 100%; + position: relative; +} +.show-map { + visibility: visible; + opacity: 1; + transition: all 1.5s; +} +.hide-map { + visibility: hidden; + opacity: 0; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.component.html b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.component.html new file mode 100644 index 000000000000..b07125417d4f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.component.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.component.ts new file mode 100644 index 000000000000..75c1542fa3fd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.component.ts @@ -0,0 +1,129 @@ +import { Component, Input, Output, EventEmitter, OnInit, OnChanges, OnDestroy, AfterViewInit, SimpleChanges, ElementRef, ViewChild } from '@angular/core'; +import * as go from 'gojs'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { ServerMapData } from './class/server-map-data.class'; +import { ServerMapInteractionService } from './server-map-interaction.service'; +import { ServerMapDiagram } from './class/server-map-diagram.class'; +import { AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; +import { ServerMapFactory, ServerMapType } from './class/server-map-factory'; + +@Component({ + selector: 'pp-server-map', + templateUrl: './server-map.component.html', + styleUrls: ['./server-map.component.css'], +}) + +export class ServerMapComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit { + @ViewChild('serverMap') el: ElementRef; + @Input() mapData: ServerMapData; + @Input() baseApplicationKey: string; + @Input() funcImagePath: Function; + @Input() funcServerMapImagePath: Function; + @Input() type: ServerMapType; + @Output() outClickNode: EventEmitter = new EventEmitter(); + @Output() outClickGroupNode: EventEmitter = new EventEmitter(); + @Output() outContextClickNode: EventEmitter = new EventEmitter(); + @Output() outClickLink: EventEmitter = new EventEmitter(); + @Output() outContextClickLink: EventEmitter = new EventEmitter(); + @Output() outClickBackground: EventEmitter = new EventEmitter(); + @Output() outDoubleClickBackground: EventEmitter = new EventEmitter(); + @Output() outContextClickBackground: EventEmitter = new EventEmitter(); + @Output() outRenderCompleted: EventEmitter<{[key: string]: boolean}> = new EventEmitter(); + + private hasRenderData = false; + private serverMapDiagram: ServerMapDiagram; + private unsubscribe: Subject = new Subject(); + + constructor( + private serverMapInteractionService: ServerMapInteractionService, + private analyticsService: AnalyticsService, + ) {} + ngOnChanges(changes: SimpleChanges) { + if (changes['mapData'] && changes['mapData']['currentValue']) { + if (this.serverMapDiagram) { + this.serverMapDiagram.setMapData(this.mapData, this.baseApplicationKey); + this.hasRenderData = false; + } else { + this.hasRenderData = true; + } + } + } + ngOnInit() { + this.serverMapInteractionService.onSearchWord$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((query: string) => { + this.serverMapInteractionService.setSearchResult(this.serverMapDiagram.searchNode(query)); + }); + this.serverMapInteractionService.onSelectedApplication$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((appKey: string) => { + this.serverMapDiagram.selectNodeBySearch(appKey); + }); + this.serverMapInteractionService.onRefresh$.pipe( + takeUntil(this.unsubscribe) + ).subscribe(() => { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.REFRESH_SERVER_MAP); + this.serverMapDiagram.refresh(); + }); + this.serverMapInteractionService.onChangeMergeState$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((params: IServerMapMergeState) => { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_SERVER_MAP_MERGE_STATE, `${params.state}`); + this.serverMapDiagram.setMergeState(params); + this.serverMapDiagram.resetMergeState(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + ngAfterViewInit() { + this.serverMapDiagram = ServerMapFactory.createServerMap(this.type, { + container: this.el.nativeElement, + funcServerMapImagePath: this.funcServerMapImagePath + }); + this.addEventHandler(); + if (this.hasRenderData) { + this.serverMapDiagram.setMapData(this.mapData, this.baseApplicationKey); + this.hasRenderData = false; + } + } + addEventHandler(): void { + this.serverMapDiagram.outRenderCompleted.subscribe((diagram: go.Diagram) => { + this.serverMapInteractionService.setCurrentDiagram(diagram); + this.outRenderCompleted.emit({ + showOverView: !!diagram + }); + }); + this.serverMapDiagram.outClickNode.subscribe((nodeData: any) => { + this.outClickNode.emit(nodeData); + }); + this.serverMapDiagram.outClickGroupNode.subscribe((nodeData: any) => { + this.outClickGroupNode.emit(nodeData); + }); + this.serverMapDiagram.outContextClickNode.subscribe((node: any) => { + this.outContextClickNode.emit(node); + }); + this.serverMapDiagram.outClickLink.subscribe((linkData: any) => { + this.outClickLink.emit(linkData); + }); + this.serverMapDiagram.outContextClickLink.subscribe((linkObj: any) => { + this.outContextClickLink.emit(linkObj); + }); + this.serverMapDiagram.outClickBackground.subscribe(() => { + this.outClickBackground.emit(); + }); + this.serverMapDiagram.outDoubleClickBackground.subscribe((msg: any) => { + this.outDoubleClickBackground.emit(msg); + }); + this.serverMapDiagram.outContextClickBackground.subscribe((coord: ICoordinate) => { + this.outContextClickBackground.emit(coord); + }); + } + clear(): void { + this.serverMapDiagram.clear(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.interface.ts b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.interface.ts new file mode 100644 index 000000000000..9269f65024b9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-map/server-map.interface.ts @@ -0,0 +1,18 @@ +export interface IHistogramType1 { + '1s': number; + '3s': number; + '5s': number; + 'Slow': number; + 'Error': number; +} +export interface IHistogramType2 { + '100ms': number; + '300ms': number; + '500ms': number; + Error: number; + Slow: number; +} +export interface IHistogramType3 { + key: string; + values: number[]; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/server-status/index.ts b/web/src/main/webapp/v2/src/app/core/components/server-status/index.ts new file mode 100644 index 000000000000..33dcee3bcb53 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-status/index.ts @@ -0,0 +1 @@ +export * from './server-status-container.component'; diff --git a/web/src/main/webapp/v2/src/app/core/components/server-status/server-status-container.component.css b/web/src/main/webapp/v2/src/app/core/components/server-status/server-status-container.component.css new file mode 100644 index 000000000000..1214e029d60e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-status/server-status-container.component.css @@ -0,0 +1,41 @@ +.l-status { + padding: 10px 16px 10px 16px; +} +.l-status:after { + content:""; + display:block; + width:100%; + height:0; + visibility:hidden; + clear:both; +} +.l-status button { + float: left; +} +.l-status button.disable { + background-color: #CCC; +} +.l-status-count { + float: right; +} +.l-status-count li { + font-size: 12px; + color: #B3B3B4; + float: left; +} +.l-status-count li:first-child { + margin: 0 15px 0 0; + padding: 0 15px 0 0; + position: relative; +} +.l-status-count span { + font-size: 18px; + font-weight: 600; + color: #EB4747; +} +.l-status-count span.l-total { + color: #000; +} +button:first-child { + margin-right: 10px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-status/server-status-container.component.html b/web/src/main/webapp/v2/src/app/core/components/server-status/server-status-container.component.html new file mode 100644 index 000000000000..2d89e1754818 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-status/server-status-container.component.html @@ -0,0 +1,8 @@ +
+ + +
    +
  • Total {{node.instanceCount}}
  • +
  • Error {{node.instanceErrorCount || 0}}
  • +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/server-status/server-status-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/server-status/server-status-container.component.ts new file mode 100644 index 000000000000..417c93778100 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/server-status/server-status-container.component.ts @@ -0,0 +1,91 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { Actions } from 'app/shared/store'; +import { StoreHelperService, NewUrlStateNotificationService, UrlRouteManagerService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; +import { ServerMapData } from 'app/core/components/server-map/class/server-map-data.class'; + +@Component({ + selector: 'pp-server-status-container', + templateUrl: './server-status-container.component.html', + styleUrls: ['./server-status-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ServerStatusContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private enableRealTime: boolean; + node: INodeInfo; + isInfoPerServerShow = false; + isLoading = false; + serverMapData: ServerMapData; + selectedTarget: ISelectedTarget; + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private analyticsService: AnalyticsService + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.enableRealTime = urlService.isRealTimeMode(); + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: ServerMapData) => { + this.serverMapData = serverMapData; + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return (target) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.selectedTarget = target; + this.node = (target.isNode === true ? this.serverMapData.getNodeData(target.node[0]) : null); + this.changeDetector.detectChanges(); + }); + this.storeHelperService.getInfoPerServerState(this.unsubscribe).subscribe((visibleState: boolean) => { + this.isLoading = false; + this.isInfoPerServerShow = visibleState; + this.changeDetector.detectChanges(); + }); + } + onClickViewServer(): void { + if (this.isLoading === true || this.enableRealTime === true) { + return; + } + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SHOW_SERVER_LIST); + this.isLoading = !this.isLoading; + this.storeHelperService.dispatch(new Actions.ChangeServerMapDisableState(!this.isInfoPerServerShow)); + this.storeHelperService.dispatch(new Actions.ChangeInfoPerServerVisibleState(!this.isInfoPerServerShow)); + } + hasServerList(): boolean { + if (this.selectedTarget) { + if (this.selectedTarget.isNode && this.selectedTarget.isMerged === false) { + return this.selectedTarget.hasServerList; + } + } + return false; + } + enableViewServer(): boolean { + return !this.enableRealTime; + } + onClickOpenInspector(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_INSPECTOR); + this.urlRouteManagerService.openInspectorPage(this.enableRealTime); + } + getAngle(): string { + return this.isInfoPerServerShow ? 'right' : 'left'; + } + isWAS(): boolean { + return this.selectedTarget.isWAS; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar-title/index.ts b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/index.ts new file mode 100644 index 000000000000..f877102d8044 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/index.ts @@ -0,0 +1,24 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +// import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MatTooltipModule } from '@angular/material'; +import { SideBarTitleComponent } from './side-bar-title.component'; +import { SideBarTitleContainerComponent } from './side-bar-title-container.component'; + +@NgModule({ + declarations: [ + SideBarTitleComponent, + SideBarTitleContainerComponent + ], + imports: [ + CommonModule, + // BrowserAnimationsModule, + MatTooltipModule + ], + exports: [ + SideBarTitleContainerComponent + ], + providers: [] +}) +export class SideBarTitleModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title-container.component.css b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title-container.component.css new file mode 100644 index 000000000000..10e12cc739ba --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title-container.component.css @@ -0,0 +1,5 @@ +:host { + width: 100%; + align-items: center; + justify-content: space-between; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title-container.component.html b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title-container.component.html new file mode 100644 index 000000000000..e33587b64538 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title-container.component.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title-container.component.ts new file mode 100644 index 000000000000..a419e569dd61 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title-container.component.ts @@ -0,0 +1,121 @@ +import { Component, OnInit, OnDestroy, HostBinding, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +import { Actions } from 'app/shared/store'; +import { WebAppSettingDataService, StoreHelperService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +interface IAppData { + applicationName: string; + serviceType: string; + agentList?: string[]; +} + +@Component({ + selector: 'pp-side-bar-title-container', + templateUrl: './side-bar-title-container.component.html', + styleUrls: ['./side-bar-title-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SideBarTitleContainerComponent implements OnInit, OnDestroy { + @HostBinding('class.flex-container') flexContainerClass = true; + @HostBinding('class.flex-row') flexRowClass = true; + isWAS: boolean; + isNode: boolean; + fromAppData: IAppData = null; + toAppData: IAppData = null; + selectedTarget: ISelectedTarget; + serverMapData: any; + funcImagePath: Function; + unsubscribe: Subject = new Subject(); + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private analyticsService: AnalyticsService, + ) { + } + ngOnInit() { + this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc(); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: IServerMapInfo) => { + this.serverMapData = serverMapData; + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + if ( target.isNode || target.isLink ) { + this.selectedTarget = target; + this.makeFromToData(); + this.changeDetector.detectChanges(); + } + }); + } + makeFromToData() { + if ( this.selectedTarget.isNode ) { + this.isWAS = this.selectedTarget.isWAS; + this.isNode = true; + const node = this.serverMapData.getNodeData(this.selectedTarget.node[0]); + this.toAppData = this.formatToAppData({ node: node }); + } else if ( this.selectedTarget.isLink ) { + this.isWAS = false; + this.isNode = false; + const link = this.serverMapData.getLinkData(this.selectedTarget.link[0]); + this.fromAppData = this.formatFromAppData(link); + this.toAppData = this.formatToAppData({ link: link }); + } + } + private isUserType(type: string): boolean { + return type.toUpperCase() === 'USER'; + } + private formatFromAppData(link: any): IAppData { + return { + applicationName: this.isUserType(link.sourceInfo.serviceType) ? link.sourceInfo.serviceType : link.sourceInfo.applicationName, + serviceType: link.sourceInfo.serviceType + }; + } + private formatToAppData({ node, link }: { node?: any, link?: any }): IAppData { + if (this.isNode) { + if (this.selectedTarget.isMerged) { + return { + applicationName: `[ ${this.selectedTarget.node.length} ] ${node.serviceType} GROUP`, + serviceType: node.serviceType, + agentList: [] + }; + } else { + return { + applicationName: node.applicationName, + serviceType: node.serviceType, + agentList: node.agentIds.sort() + }; + } + } else { + if (this.selectedTarget.isMerged) { + return { + applicationName: `[ ${this.selectedTarget.link.length} ] ${link.targetInfo.serviceType} GROUP`, + serviceType: link.targetInfo.serviceType, + agentList: [] + }; + } else { + return { + applicationName: link.targetInfo.applicationName, + serviceType: link.targetInfo.serviceType, + agentList: [] + }; + } + } + } + onChangeAgent(agentName: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_AGENT); + agentName = agentName === 'All' ? '' : agentName; + this.storeHelperService.dispatch(new Actions.ChangeAgent(agentName)); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title.component.css b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title.component.css new file mode 100644 index 000000000000..4c5a7a6365a5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title.component.css @@ -0,0 +1,45 @@ +:host { + width: 100%; +} + +.l-wrapper { + width: 100%; + display:flex; + flex-wrap: nowrap; + flex-flow: row; + align-items: center; + justify-content: space-between; +} +.l-wrapper select { + width: 152px; + border: 1px solid #d7dde4; + height: 32px; + padding: 0 9px; + font-size: 13px; + border-radius: 2px; + color: #666; + appearance: none; + -webkit-appearance:none; + background:url(../../../../assets/img/select-down-arrow.png) no-repeat right 10px center; +} +.l-wrapper-grid { + display: grid; + grid-template-columns: 50% 20px 50%; + grid-template-rows: auto; +} + +.l-wrapper .l-title, .l-wrapper-grid .l-title { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: 16px; + font-weight: 600; + padding: 0px; +} +.l-wrapper .l-title img, .l-wrapper-grid .l-title img { + float: left; +} +.l-wrapper .l-title span, .l-wrapper-grid .l-title span { + margin-left: 6px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title.component.html b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title.component.html new file mode 100644 index 000000000000..d1e4293279c6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title.component.html @@ -0,0 +1,18 @@ +
+
+ {{toAppData?.applicationName}} +
+ +
+
+
+ {{fromAppData?.applicationName}} +
+
+
+ {{toAppData?.applicationName}} +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title.component.ts b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title.component.ts new file mode 100644 index 000000000000..a883d6f649a7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar-title/side-bar-title.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-side-bar-title', + templateUrl: './side-bar-title.component.html', + styleUrls: ['./side-bar-title.component.css'] +}) +export class SideBarTitleComponent implements OnInit { + @Input() isWAS: boolean; + @Input() isNode: boolean; + @Input() fromAppData: any; + @Input() toAppData: any; + @Input() funcImagePath: Function; + @Output() outChangeAgent: EventEmitter = new EventEmitter(); + constructor() {} + ngOnInit() {} + getIconPath(serviceType: string): string { + return this.funcImagePath(serviceType); + } + onChangeAgent($agent: string): void { + this.outChangeAgent.emit($agent); + } + showAgentList(): boolean { + if (this.toAppData) { + return this.toAppData.agentList.length > 0; + } else { + return false; + } + } + onLoadError(img: HTMLImageElement): void { + img.src = this.funcImagePath('NO_IMAGE_FOUND'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar/index.ts b/web/src/main/webapp/v2/src/app/core/components/side-bar/index.ts new file mode 100644 index 000000000000..ed1446f66d9f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar/index.ts @@ -0,0 +1,37 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { LoadChartModule } from 'app/core/components/load-chart'; +import { TargetListModule } from 'app/core/components/target-list'; +import { SideBarTitleModule } from 'app/core/components/side-bar-title'; +import { ScatterChartModule } from 'app/core/components/scatter-chart'; +import { InfoPerServerModule } from 'app/core/components/info-per-server'; +import { ResponseSummaryChartModule } from 'app/core/components/response-summary-chart'; +import { ServerStatusContainerComponent } from 'app/core/components/server-status'; +import { FilterInformationContainerComponent } from 'app/core/components/filter-information'; +import { SideBarContainerComponent } from './side-bar-container.component'; +import { SideBarForFilteredMapContainerComponent } from './side-bar-for-filtered-map-container.component'; + +@NgModule({ + declarations: [ + SideBarContainerComponent, + SideBarForFilteredMapContainerComponent, + ServerStatusContainerComponent, + FilterInformationContainerComponent + ], + imports: [ + SharedModule, + InfoPerServerModule, + SideBarTitleModule, + ScatterChartModule, + TargetListModule, + ResponseSummaryChartModule, + LoadChartModule + ], + exports: [ + SideBarContainerComponent, + SideBarForFilteredMapContainerComponent + ], + providers: [] +}) +export class SideBarModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-container.component.css b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-container.component.css new file mode 100644 index 000000000000..15cfddb82d08 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-container.component.css @@ -0,0 +1,40 @@ +:host { + position: relative; + border-left: 1px solid #e5e8f0; + border-right: 1px solid #e5e8f0; +} +.l-sidemenu-wrap { + display: flex; + flex-flow: column nowrap; + height: 100%; + overflow-y: auto; + overflow-x: hidden; +} +.l-sidemenu { + height: 100%; + z-index: 10; + background-color: #fff; +} +.l-title-group { + display: flex; + flex-flow: row wrap; + color: #666; + justify-content: space-between; + align-items: center; + padding: 0 15px; + background: #F9FAFC; + border-bottom: 1px solid #EAEEF4; + height: 50px; +} +.l-contents-group { + padding-bottom: 16px; + overflow-y: auto; + overflow-x: hidden; +} +.l-chart-group-list { + flex: 1; +} +hr { + height: 1px; + border-top: 1px solid #EAEEF4; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-container.component.html b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-container.component.html new file mode 100644 index 000000000000..76d48d8c726b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-container.component.html @@ -0,0 +1,21 @@ +
+
+
+ +
+
+ +
+ + +
+ +
+ +
+
+
+ +
+ + diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-container.component.ts new file mode 100644 index 000000000000..50b4e2dbaa96 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-container.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { StoreHelperService, NewUrlStateNotificationService } from 'app/shared/services'; + +@Component({ + selector: 'pp-side-bar-container', + templateUrl: './side-bar-container.component.html', + styleUrls: ['./side-bar-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SideBarContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + target: any; + sideBarWidth = 0; + useDisable = true; + showLoading = true; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.APPLICATION)) { + this.showLoading = true; + this.useDisable = true; + } else { + this.sideBarWidth = 0; + this.showLoading = false; + this.useDisable = false; + } + this.changeDetectorRef.detectChanges(); + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getServerMapData(this.unsubscribe).pipe( + filter((target: any) => { + return target.nodeList ? true : false; + }) + ).subscribe((target: any) => { + if (target.nodeList.length === 0) { + this.sideBarWidth = 0; + } + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.target = target; + if ( target.isNode === true || target.isLink === true ) { + this.sideBarWidth = 461; + } else { + this.sideBarWidth = 0; + } + this.showLoading = false; + this.useDisable = false; + this.changeDetectorRef.detectChanges(); + }); + } + hasTopElement(): boolean { + return this.target && (this.target.isNode || this.target.isMerged); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-for-filtered-map-container.component.css b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-for-filtered-map-container.component.css new file mode 100644 index 000000000000..15cfddb82d08 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-for-filtered-map-container.component.css @@ -0,0 +1,40 @@ +:host { + position: relative; + border-left: 1px solid #e5e8f0; + border-right: 1px solid #e5e8f0; +} +.l-sidemenu-wrap { + display: flex; + flex-flow: column nowrap; + height: 100%; + overflow-y: auto; + overflow-x: hidden; +} +.l-sidemenu { + height: 100%; + z-index: 10; + background-color: #fff; +} +.l-title-group { + display: flex; + flex-flow: row wrap; + color: #666; + justify-content: space-between; + align-items: center; + padding: 0 15px; + background: #F9FAFC; + border-bottom: 1px solid #EAEEF4; + height: 50px; +} +.l-contents-group { + padding-bottom: 16px; + overflow-y: auto; + overflow-x: hidden; +} +.l-chart-group-list { + flex: 1; +} +hr { + height: 1px; + border-top: 1px solid #EAEEF4; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-for-filtered-map-container.component.html b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-for-filtered-map-container.component.html new file mode 100644 index 000000000000..f31272f073fe --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-for-filtered-map-container.component.html @@ -0,0 +1,22 @@ +
+
+
+ +
+
+ + +
+ + +
+ +
+ +
+
+
+ +
+ + diff --git a/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-for-filtered-map-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-for-filtered-map-container.component.ts new file mode 100644 index 000000000000..7673ad852248 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/side-bar/side-bar-for-filtered-map-container.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +import { StoreHelperService } from 'app/shared/services'; + +@Component({ + selector: 'pp-side-bar-for-filtered-map-container', + templateUrl: './side-bar-for-filtered-map-container.component.html', + styleUrls: ['./side-bar-for-filtered-map-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SideBarForFilteredMapContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + target: any; + sideBarWidth = 0; + useDisable = true; + showLoading = true; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService + ) {} + ngOnInit() { + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getServerMapLoadingState(this.unsubscribe).subscribe((state: string) => { + switch (state) { + case 'loading': + this.showLoading = true; + this.useDisable = true; + break; + case 'pause': + case 'completed': + this.showLoading = false; + this.useDisable = false; + break; + } + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: any) => { + this.target = target; + this.sideBarWidth = 461; + this.changeDetectorRef.detectChanges(); + }); + } + hasTopElement(): boolean { + return this.target && (this.target.isNode || this.target.isMerged); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/index.ts b/web/src/main/webapp/v2/src/app/core/components/state-button/index.ts new file mode 100644 index 000000000000..6ea0bfd30820 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/index.ts @@ -0,0 +1,25 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { StateButtonComponent } from './state-button.component'; +import { StateButtonForFilteredMapContainerComponent } from './state-button-for-filtered-map-container.component'; +import { StateButtonForTransactionListContainerComponent } from './state-button-for-transaction-list-container.component'; +import { TransactionTableGridModule } from 'app/core/components/transaction-table-grid'; + +@NgModule({ + declarations: [ + StateButtonComponent, + StateButtonForFilteredMapContainerComponent, + StateButtonForTransactionListContainerComponent + ], + imports: [ + CommonModule, + TransactionTableGridModule + ], + exports: [ + StateButtonForFilteredMapContainerComponent, + StateButtonForTransactionListContainerComponent + ], + providers: [] +}) +export class StateButtonModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-filtered-map-container.component.css b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-filtered-map-container.component.css new file mode 100644 index 000000000000..c3329127e37a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-filtered-map-container.component.css @@ -0,0 +1,3 @@ +:host { + display: flex; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-filtered-map-container.component.html b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-filtered-map-container.component.html new file mode 100644 index 000000000000..559ca402504e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-filtered-map-container.component.html @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-filtered-map-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-filtered-map-container.component.ts new file mode 100644 index 000000000000..9600c6eedbc3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-filtered-map-container.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; + +import { StoreHelperService } from 'app/shared/services'; +import { ServerMapForFilteredMapDataService } from 'app/core/components/server-map/server-map-for-filtered-map-data.service'; +import { BUTTON_STATE } from './state-button.component'; + +@Component({ + selector: 'pp-state-button-for-filtered-map-container', + templateUrl: './state-button-for-filtered-map-container.component.html', + styleUrls: ['./state-button-for-filtered-map-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class StateButtonForFilteredMapContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + showCountInfo = false; + currentState = BUTTON_STATE.PAUSE; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private serverMapForFilteredMapDataService: ServerMapForFilteredMapDataService + ) {} + ngOnInit() { + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getServerMapLoadingState(this.unsubscribe).subscribe((state: string) => { + switch (state) { + case 'loading': + this.currentState = BUTTON_STATE.PAUSE; + break; + case 'pause': + this.currentState = BUTTON_STATE.RESUME; + break; + case 'completed': + this.currentState = BUTTON_STATE.COMPLETED; + break; + } + this.changeDetectorRef.detectChanges(); + }); + } + onChangeState(event: string) { + if ( event === BUTTON_STATE.RESUME ) { + this.serverMapForFilteredMapDataService.resumeDataLoad(); + } else if ( event === BUTTON_STATE.PAUSE ) { + this.serverMapForFilteredMapDataService.stopDataLoad(); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-transaction-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-transaction-list-container.component.css new file mode 100644 index 000000000000..c3329127e37a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-transaction-list-container.component.css @@ -0,0 +1,3 @@ +:host { + display: flex; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-transaction-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-transaction-list-container.component.html new file mode 100644 index 000000000000..1ce6d47d1b6c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-transaction-list-container.component.html @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-transaction-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-transaction-list-container.component.ts new file mode 100644 index 000000000000..7f09a26009c4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button-for-transaction-list-container.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; + +import { TransactionMetaDataService } from 'app/core/components/transaction-table-grid/transaction-meta-data.service'; +import { BUTTON_STATE } from './state-button.component'; + +@Component({ + selector: 'pp-state-button-for-transaction-list-container', + templateUrl: './state-button-for-transaction-list-container.component.html', + styleUrls: ['./state-button-for-transaction-list-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class StateButtonForTransactionListContainerComponent implements OnInit { + countInfo = [0, 0]; + showCountInfo = true; + currentState = BUTTON_STATE.MORE; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private transactionMetaDataService: TransactionMetaDataService + ) {} + ngOnInit() { + this.transactionMetaDataService.onTransactionDataCount$.subscribe((counter: number[]) => { + this.countInfo = counter.concat(); + this.currentState = this.isLoadCompleted() ? BUTTON_STATE.DONE : BUTTON_STATE.MORE; + this.changeDetectorRef.detectChanges(); + }); + } + private isLoadCompleted(): boolean { + return this.countInfo[0] === this.countInfo[1]; + } + onChangeState(state: string) { + this.transactionMetaDataService.loadData(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/state-button.component.css b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button.component.css new file mode 100644 index 000000000000..16c293648f0c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button.component.css @@ -0,0 +1,4 @@ +.l-wrapper { + display: flex; + padding: 10px 20px 10px 10px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/state-button.component.html b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button.component.html new file mode 100644 index 000000000000..d178ffb2d67c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button.component.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/state-button/state-button.component.ts b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button.component.ts new file mode 100644 index 000000000000..a8cc1cc19449 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/state-button/state-button.component.ts @@ -0,0 +1,64 @@ +import { Component, OnInit, Output, OnChanges, EventEmitter, SimpleChanges, Input } from '@angular/core'; + +import { AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +export enum BUTTON_STATE { + PAUSE = 'Pause', + RESUME = 'Resume', + COMPLETED = 'Completed', + MORE = 'More', + DONE = 'Done' +} + +@Component({ + selector: 'pp-state-button', + templateUrl: './state-button.component.html', + styleUrls: ['./state-button.component.css'] +}) +export class StateButtonComponent implements OnInit, OnChanges { + currentStateText = BUTTON_STATE.PAUSE.toString(); + @Input() width: number; + @Input() showCountInfo: boolean; + @Input() countInfo: number[]; + @Input() currentState = BUTTON_STATE.PAUSE; + @Output() outChangeState: EventEmitter = new EventEmitter(); + constructor( + private analyticsService: AnalyticsService, + ) { + this.currentStateText = this.currentState.toString(); + } + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if (changes['currentState'] && changes['currentState']['currentValue']) { + this.currentStateText = this.currentState.toString(); + } + } + private changeState(state: BUTTON_STATE): void { + this.currentState = state; + this.currentStateText = this.currentState.toString(); + } + onClick() { + switch (this.currentState) { + case BUTTON_STATE.COMPLETED: + this.outChangeState.emit(this.currentState); + this.changeState(BUTTON_STATE.COMPLETED); + break; + case BUTTON_STATE.DONE: + this.outChangeState.emit(this.currentState); + this.changeState(BUTTON_STATE.DONE); + break; + case BUTTON_STATE.PAUSE: + this.outChangeState.emit(this.currentState); + this.changeState(BUTTON_STATE.RESUME); + break; + case BUTTON_STATE.RESUME: + this.outChangeState.emit(this.currentState); + this.changeState(BUTTON_STATE.PAUSE); + break; + case BUTTON_STATE.MORE: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_MORE_STATE_BUTTON); + this.outChangeState.emit(this.currentState); + break; + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/index.ts b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/index.ts new file mode 100644 index 000000000000..c317c0713f54 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/index.ts @@ -0,0 +1,31 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ClipboardModule } from 'ngx-clipboard'; +import { HighlightModule } from 'ngx-highlightjs'; + +import { SyntaxHighlightPopupComponent } from './syntax-highlight-popup.component'; +import { SyntaxHighlightPopupContainerComponent } from './syntax-highlight-popup-container.component'; +import { SyntaxHighlightDataService } from './syntax-highlight-data.service'; + +@NgModule({ + declarations: [ + SyntaxHighlightPopupComponent, + SyntaxHighlightPopupContainerComponent + ], + imports: [ + CommonModule, + ClipboardModule, + HighlightModule.forRoot({ + theme: 'sunburst' + }), + ], + exports: [], + entryComponents: [ + SyntaxHighlightPopupContainerComponent + ], + providers: [ + SyntaxHighlightDataService + ] +}) +export class SyntaxHighlightPopupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-data.service.ts new file mode 100644 index 000000000000..bae260200c77 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-data.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +enum TYPE { + SQL = 'SQL', + JSON = 'JSON' +} + +@Injectable() +export class SyntaxHighlightDataService { + private sqlRequestURL = 'sqlBind.pinpoint'; + private jsonRequestURL = 'jsonBind.pinpoint'; + constructor( + private http: HttpClient + ) { } + getData({type, originalContents, bindValue}: ISyntaxHighlightData): Observable { + let requestURL; + switch (type) { + case TYPE.SQL: + requestURL = this.sqlRequestURL; + break; + case TYPE.JSON: + requestURL = this.jsonRequestURL; + break; + } + return this.http.post( + requestURL, + `${type.toLowerCase()}=${encodeURIComponent(originalContents)}&bind=${encodeURIComponent(bindValue)}`, + { + headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') + } + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component.css b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component.css new file mode 100644 index 000000000000..3c6a97654ec4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component.css @@ -0,0 +1,18 @@ +:host { + display: block; + background-color: transparent; + width: 100%; + height: 100%; +} + +:host::before { + content: ''; + display: block; + height: 100%; + width: 100%; + background: #000; + opacity: 0.6; + position: absolute; + left: 0; + top: 0; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component.html b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component.html new file mode 100644 index 000000000000..78be554fce0e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component.html @@ -0,0 +1,6 @@ +
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component.ts new file mode 100644 index 000000000000..7d3748317a2a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup-container.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit, Input, Output, EventEmitter, AfterViewInit } from '@angular/core'; +import { Observable, iif, of } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { DynamicPopup } from 'app/shared/services'; +import { SyntaxHighlightDataService } from './syntax-highlight-data.service'; + +@Component({ + selector: 'pp-syntax-highlight-popup-container', + templateUrl: './syntax-highlight-popup-container.component.html', + styleUrls: ['./syntax-highlight-popup-container.component.css'], +}) +export class SyntaxHighlightPopupContainerComponent implements OnInit, AfterViewInit, DynamicPopup { + @Input() data: ISyntaxHighlightData; + @Output() outClose = new EventEmitter(); + @Output() outCreated = new EventEmitter(); + + data$: Observable; + + constructor( + private syntaxHighlightDataService: SyntaxHighlightDataService + ) {} + + ngOnInit() { + this.data$ = iif(() => !!this.data.bindValue, + this.syntaxHighlightDataService.getData(this.data).pipe( + map((bindedContents: string) => { + return { ...this.data, bindedContents }; + }) + ), + of(this.data) + ); + } + + ngAfterViewInit() { + this.outCreated.emit({ coordX: 0, coordY: 0 }); + } + + onClosePopup(): void { + this.outClose.emit(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup.component.css b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup.component.css new file mode 100644 index 000000000000..2238be4a310e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup.component.css @@ -0,0 +1,81 @@ +:host{ + display: block; + position: absolute; + width: 100%; + min-width: 500px; + max-width: 1000px; + background-color: #fff; + border: 1px solid #e5e8f0; + box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.75); + text-align: left; + top: 50%; + transform: translateX(-50%) translateY(-50%); + left: 50%; +} +.l-title-group { + padding: 17px 18px; + height: auto; + background-color: #fff; + border-bottom: 1px solid #e5e8f0; + position: relative; + font-size: 13px; + font-weight: 600; + color: #333; + display: flex; + align-items: center; + justify-content: space-between; +} +.l-title-group dt { + font-size: 20px; + font-weight: 600; + color: #4a8fd2; +} +.l-title-group button { + position:absolute; + right:27px; + top:50%; + transform:translateY(-50%); + color:#4a8fd2; + font-size: 30px; + width:20px; + height:20px; + background:url(../../../../assets/img/icon-close.png) no-repeat 0 0; +} +.l-contents-group { + background: #f6f8fb; + padding: 17px 18px; + overflow: auto; +} +.l-sql-list { + margin: 10px 0 0 0; +} +.l-sql-list:first-child { + margin: 0; +} +.l-sql-list dt { + font-size: 13px; + font-weight: 600; + color: #333; + margin: 0 0 12px; +} +.l-sql-list dd { + border: 1px solid #cfd7e1; + background: #fff; + padding: 18px 18px; + font-size: 13px; + color: #999; + line-height: 2em; + position: relative; +} +.l-sql-list button { + position: absolute; + right: 0; + bottom: 0; +} +pre { + height: 250px; +} +code { + height: 100%; + white-space: normal; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup.component.html b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup.component.html new file mode 100644 index 000000000000..00816fb9d5ed --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup.component.html @@ -0,0 +1,27 @@ +
+
{{data.type}}
+ +
+
+
+
Binded {{data.type}}
+
+
+ +
+
+
+
Original {{data.type}}
+
+
+ +
+
+
+
{{data.type}} Bind Value
+
+ {{data.bindValue}} + +
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup.component.ts b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup.component.ts new file mode 100644 index 000000000000..6230a7eb2cb0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/syntax-highlight-popup/syntax-highlight-popup.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, Input, Output, EventEmitter, HostBinding } from '@angular/core'; +import { ClipboardService } from 'ngx-clipboard'; + +@Component({ + selector: 'pp-syntax-highlight-popup', + templateUrl: './syntax-highlight-popup.component.html', + styleUrls: ['./syntax-highlight-popup.component.css'] +}) +export class SyntaxHighlightPopupComponent implements OnInit { + @Input() data: ISyntaxHighlightData; + @Output() outClosePopup = new EventEmitter(); + @HostBinding('class.font-opensans') fontFamily = true; + + constructor( + private clipboardService: ClipboardService + ) {} + ngOnInit() {} + onCopyOriginalContents() { + this.clipboardService.copyFromContent(this.data.originalContents); + } + onCopyBindedContents() { + this.clipboardService.copyFromContent(this.data.bindedContents); + } + onCopyBindValue() { + this.clipboardService.copyFromContent(this.data.bindValue); + } + onClose() { + this.outClosePopup.emit(); + } + hasBind(): boolean { + return !!this.data.bindValue; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/target-list/index.ts b/web/src/main/webapp/v2/src/app/core/components/target-list/index.ts new file mode 100644 index 000000000000..2553bb3825e1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/target-list/index.ts @@ -0,0 +1,23 @@ + +import { NgModule } from '@angular/core'; + +import { CommonModule } from '@angular/common'; +import { TargetListComponent } from './target-list.component'; +import { TargetListContainerComponent } from './target-list-container.component'; +import { FilterTransactionWizardPopupModule } from 'app/core/components/filter-transaction-wizard-popup'; + +@NgModule({ + declarations: [ + TargetListComponent, + TargetListContainerComponent + ], + imports: [ + CommonModule, + FilterTransactionWizardPopupModule + ], + exports: [ + TargetListContainerComponent + ], + providers: [] +}) +export class TargetListModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/target-list/target-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list-container.component.css new file mode 100644 index 000000000000..e91a2913301f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list-container.component.css @@ -0,0 +1,63 @@ +.l-target-container-wrapper { + margin: 0; + position: relative; + width: 459px; + height: 361px; + font-size: 14px; +} +.l-target-container { + display: grid; + grid-template-columns: auto; + grid-template-rows: 32px 299px 30px; +} +.l-target-container .l-search-header { + /* autoprefixer: off */ + color: #B3B3B4; + grid-column: 1 / 2; + grid-row: 1 / 2; +} +.l-target-container .l-search-header input { + width: 457px; + height: 30px; + padding: 0px 8px; + margin: 0px; + border: 1px solid #D7DDE4; +} +.l-target-container .l-search-header button { + position: absolute; + right: 10px; + top: 8px; + color: #B3B3B4; +} +.l-target-container .l-target-list { + /* autoprefixer: off */ + grid-column: 1 / 2; + grid-row: 2 / 3; + padding: 4px 0px; + overflow-x: hidden; + overflow-y: auto; +} + +.l-target-container .l-summary { + /* autoprefixer: off */ + grid-column: 1 / 2; + grid-row: 3 / 4; + background-color: #469ae4; +} +.l-target-container .l-summary > div { + display: flex; + color: #FFF; + cursor: pointer; + flex-wrap: nowrap; + align-items: center; + flex-direction: row; + justify-content: space-between; + height: 100%; + padding: 0px 28px 0px 8px; +} +button { + cursor: pointer; + background: none; + border: none; + font-size: 14px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/target-list/target-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list-container.component.html new file mode 100644 index 000000000000..6c56da0ada07 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list-container.component.html @@ -0,0 +1,23 @@ +
+
+
+ + +
+
+ + +
+
+
+ {{targetList.length}} application{{targetList.length > 1 ? 's' : ''}} + {{getRequestSum() | number}} +
+
+
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/target-list/target-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list-container.component.ts new file mode 100644 index 000000000000..5bad897cfaf3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list-container.component.ts @@ -0,0 +1,172 @@ +import { Component, OnInit, OnDestroy, AfterViewInit, AfterViewChecked, ElementRef, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators'; + +import { Actions } from 'app/shared/store'; +import { UrlPathId } from 'app/shared/models'; +import { Filter } from 'app/core/models/filter'; +import { + UrlRouteManagerService, + StoreHelperService, + NewUrlStateNotificationService, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { ServerMapData } from 'app/core/components/server-map/class/server-map-data.class'; +import { FilterTransactionWizardPopupContainerComponent } from 'app/core/components/filter-transaction-wizard-popup/filter-transaction-wizard-popup-container.component'; + +@Component({ + selector: 'pp-target-list-container', + templateUrl: './target-list-container.component.html', + styleUrls: ['./target-list-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TargetListContainerComponent implements OnInit, OnDestroy, AfterViewInit, AfterViewChecked { + minLength = 2; + isLink = false; + filterQuery = ''; + selectedTarget: ISelectedTarget; + serverMapData: ServerMapData; + notFilteredTargetList: any[]; + targetList: any[]; + unsubscribe: Subject = new Subject(); + userInputChange = new Subject(); + inputElement: HTMLInputElement; + constructor( + private changeDetector: ChangeDetectorRef, + private elementRef: ElementRef, + private urlRouteManagerService: UrlRouteManagerService, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private dynamicPopupService: DynamicPopupService, + private analyticsService: AnalyticsService + ) {} + ngOnInit() { + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + ngAfterViewInit(): void { + this.inputElement = this.elementRef.nativeElement.querySelector('input'); + this.setFocusToInput(); + } + ngAfterViewChecked(): void { + this.inputElement = this.elementRef.nativeElement.querySelector('input'); + } + private connectStore(): void { + this.storeHelperService.getServerMapTargetSelected(this.unsubscribe).pipe( + filter((target: ISelectedTarget) => { + return target && (target.isNode === true || target.isNode === false) ? true : false; + }) + ).subscribe((target: ISelectedTarget) => { + this.selectedTarget = target; + if (target.isMerged === true) { + this.isLink = target.isLink; + if ( this.inputElement ) { + this.inputElement.value = ''; + } + this.gatherTargets(); + } + this.changeDetector.detectChanges(); + }); + this.storeHelperService.getServerMapData(this.unsubscribe).subscribe((serverMapData: ServerMapData) => { + this.serverMapData = serverMapData; + }); + this.userInputChange.pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.unsubscribe) + ).subscribe((res: string) => { + const len = res.length; + if ( len === 0 || len >= this.minLength ) { + this.setFilterQuery(res); + } + }); + } + isGroup(): boolean { + return this.selectedTarget && this.selectedTarget.isMerged === true ? true : false; + } + gatherTargets(): void { + if ( this.selectedTarget.isMerged ) { + const targetList: any = []; + if ( this.selectedTarget.isNode ) { + this.selectedTarget.node.forEach(nodeKey => { + targetList.push([this.serverMapData.getNodeData(nodeKey), '']); + }); + } else if ( this.selectedTarget.isLink ) { + // Link 인 경우 필터 관련 버튼을 추가해야 함. + this.selectedTarget.link.forEach(linkKey => { + targetList.push([this.serverMapData.getNodeData(this.serverMapData.getLinkData(linkKey).to), linkKey]); + }); + } + this.notFilteredTargetList = this.targetList = targetList; + } + } + onSelectTarget(target: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_NODE_IN_GROUPED_VIEW); + this.storeHelperService.dispatch(new Actions.UpdateServerMapSelectedTargetByList(target[0])); + } + onOpenFilter(target: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_FILTER_TRANSACTION); + const link = this.serverMapData.getLinkData(target[1]); + this.urlRouteManagerService.openPage(this.urlRouteManagerService.makeFilterMapUrl({ + applicationName: link.filterApplicationName, + serviceType: link.filterApplicationServiceTypeName, + periodStr: this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithAddedWords(), + timeStr: this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getEndTime(), + filterStr: this.newUrlStateNotificationService.getPathValue(UrlPathId.FILTER), + hintStr: this.newUrlStateNotificationService.getPathValue(UrlPathId.HINT), + addedFilter: new Filter( + link.sourceInfo.applicationName, + link.sourceInfo.serviceType, + link.targetInfo.applicationName, + link.targetInfo.serviceType + )} + )); + } + getRequestSum(): number { + return this.targetList.reduce((accumulator: number, target: any) => { + return accumulator + target[0].totalCount; + }, 0); + } + onOpenFilterWizard(target: any): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_FILTER_TRANSACTION_WIZARD); + this.dynamicPopupService.openPopup({ + data: this.serverMapData.getLinkData(target[1]), + component: FilterTransactionWizardPopupContainerComponent + }); + } + onKeyUp($event: any): void { + if ( $event.keyCode === 27 ) { + this.inputElement.value = ''; + this.setFilterQuery(''); + } else { + this.userInputChange.next($event.target.value); + } + } + setFocusToInput(): void { + if ( this.inputElement ) { + this.inputElement.focus(); + } + } + setFilterQuery(query: string): void { + this.filterQuery = query; + this.targetList = this.filterList(); + this.changeDetector.detectChanges(); + } + filterList(): any[] { + if ( this.filterQuery === '' ) { + return this.notFilteredTargetList; + } + const filteredList: any = []; + this.notFilteredTargetList.forEach(aTarget => { + if ( aTarget[0].applicationName.indexOf(this.filterQuery) !== -1 ) { + filteredList.push(aTarget); + } + }); + return filteredList; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/target-list/target-list.component.css b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list.component.css new file mode 100644 index 000000000000..1ae0b6ff9cbc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list.component.css @@ -0,0 +1,42 @@ +.l-target-item { + display: flex; + padding: 4px 10px 4px 4px; + cursor: pointer; + flex-wrap: nowrap; + align-items: center; + flex-direction: row; + justify-content: space-between; + border-bottom: 1px solid #F3F3F3; +} +.l-target-item:hover { + background-color: #c1ecff; +} +.l-target-item.selected { + color: #1BABF4; + font-weight: 600; +} +.l-target-item:last-of-type { + border-bottom: none; +} +.l-application-name { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +button { + font-size: 11px; + padding: 1px 3px 0px 3px; +} + +button:first-of-type { + color: #4b99e3; + border: #CCC 1px solid; + margin-right: 1px; +} +button:last-of-type { + color: #e95459; + border: #CCC 1px solid; + margin-right: 8px; +} + diff --git a/web/src/main/webapp/v2/src/app/core/components/target-list/target-list.component.html b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list.component.html new file mode 100644 index 000000000000..18049e33a3ce --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list.component.html @@ -0,0 +1,8 @@ +
+ + + + {{target[0].applicationName}} + + {{target[0].totalCount | number}} +
diff --git a/web/src/main/webapp/v2/src/app/core/components/target-list/target-list.component.ts b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list.component.ts new file mode 100644 index 000000000000..5a5a81f445e6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/target-list/target-list.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-target-list', + templateUrl: './target-list.component.html', + styleUrls: ['./target-list.component.css'] +}) +export class TargetListComponent implements OnInit { + selectedAppName = ''; + @Input() isLink: boolean; + @Input() targetList: any[]; + @Output() outSelectTarget: EventEmitter = new EventEmitter(); + @Output() outOpenFilter: EventEmitter = new EventEmitter(); + @Output() outOpenFilterWizard: EventEmitter = new EventEmitter(); + constructor() { } + ngOnInit() { + } + onSelectTarget(target): void { + this.selectedAppName = target[0].applicationName; + this.outSelectTarget.emit(target); + } + onOpenFilter($event, target): void { + this.outOpenFilter.emit(target); + } + onOpenFilterWizard($event, target): void { + this.outOpenFilterWizard.emit(target); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/active-thread-dump-list-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/active-thread-dump-list-data.service.ts new file mode 100644 index 000000000000..acf625d4e885 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/active-thread-dump-list-data.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, retry, tap } from 'rxjs/operators'; + +export interface IActiveThreadDump { + threadId: string; + threadName: string; + threadState: string; + startTime: number; + execTime: number; + localTraceId: number; + sampled: boolean; + transactionId: string; + entryPoint: string; + detailMessage: string; +} + +export interface IActiveThreadDumpResponse { + code: number; + message: { + subType: string; + threadDumpData: IActiveThreadDump[]; + type: string; + version: string; + }; +} + +@Injectable() +export class ActiveThreadDumpListDataService { + requestURL = 'agent/activeThreadLightDump.pinpoint'; + constructor(private http: HttpClient) {} + getData(applicationName: string, agentId: string): Observable { + return this.http.get(this.requestURL, this.makeRequestOptionsArgs(applicationName, agentId)).pipe( + retry(3), + tap((data: any) => { + if (data['exception']) { + throw data['exception']['message']; + } + }), + catchError(this.handleError) + ); + } + private handleError(error: HttpErrorResponse | string) { + return throwError(error['message'] || error); + } + private makeRequestOptionsArgs(applicationName: string, agentId: string): object { + return { + params: new HttpParams().set('applicationName', applicationName).set('agentId', agentId) + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/index.ts b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/index.ts new file mode 100644 index 000000000000..8f2018b77fe3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/index.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { AgGridModule } from 'ag-grid-angular/main'; +import { ThreadDumpListContainerComponent } from './thread-dump-list-container.component'; +import { ThreadDumpListComponent } from './thread-dump-list.component'; +import { ActiveThreadDumpListDataService } from './active-thread-dump-list-data.service'; + +@NgModule({ + declarations: [ + ThreadDumpListComponent, + ThreadDumpListContainerComponent + ], + imports: [ + SharedModule, + AgGridModule.withComponents([]) + ], + exports: [ + ThreadDumpListContainerComponent + ], + providers: [ + ActiveThreadDumpListDataService + ] +}) +export class ThreadDumpListModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list-container.component.css b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list-container.component.css new file mode 100644 index 000000000000..cbb7ab917be5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list-container.component.css @@ -0,0 +1,17 @@ +.there-is-no-data { + position: absolute; + width: 100%; + height: 100%; + padding-top: 40%; + text-align: center; +} +.when-has-message > span { + color: #F00; +} +.when-has-message > div { + padding-top: 10px; +} +.when-has-message > ul { + list-style: none; + padding-top: 10px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list-container.component.html b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list-container.component.html new file mode 100644 index 000000000000..85f2675a4598 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list-container.component.html @@ -0,0 +1,21 @@ + +
+ There is no data +
+
+ {{errorMessage}} +
+
For the above reasons, this agent does not support thread dump.
+
    +
  • 1. check this agent version is 1.6.1+
  • +
  • 2. check cluster feature is enabled.
  • +
+
+
+ \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list-container.component.ts new file mode 100644 index 000000000000..72ebdaa06b50 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list-container.component.ts @@ -0,0 +1,93 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { takeUntil, filter, switchMap } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { StoreHelperService, NewUrlStateNotificationService} from 'app/shared/services'; +import { ThreadDumpLogInteractionService, IParam } from 'app/core/components/thread-dump-log/thread-dump-log-interaction.service'; +import { ActiveThreadDumpListDataService, IActiveThreadDump } from './active-thread-dump-list-data.service'; +import { IThreadDumpData } from './thread-dump-list.component'; + + +@Component({ + selector: 'pp-thread-dump-list-container', + templateUrl: './thread-dump-list-container.component.html', + styleUrls: ['./thread-dump-list-container.component.css'] +}) +export class ThreadDumpListContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private applicationName: string; + private agentId: string; + rowData: IThreadDumpData[] = []; + serverResponseError = true; + hasErrorResponse = false; + errorMessage: string; + showLoading = true; + loaded = false; + timezone$: Observable; + dateFormat$: Observable; + constructor( + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private activeThreadDumpListDataService: ActiveThreadDumpListDataService, + private threadDumpLogInteractionService: ThreadDumpLogInteractionService + ) {} + ngOnInit() { + this.connectStore(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.APPLICATION, UrlPathId.AGENT_ID); + }), + switchMap((urlService: NewUrlStateNotificationService) => { + this.applicationName = urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName(); + this.agentId = urlService.getPathValue(UrlPathId.AGENT_ID); + return this.activeThreadDumpListDataService.getData(this.applicationName, this.agentId); + }) + ).subscribe((data: any) => { + if (data.code === -1) { + this.serverResponseError = true; + this.hasErrorResponse = true; + this.errorMessage = data.message; + } else { + this.rowData = data.message.threadDumpData.map((threadDump: IActiveThreadDump, index: number) => { + return { + index: index + 1, + id: threadDump.threadId, + name: threadDump.threadName, + state: threadDump.threadState, + startTime: threadDump.startTime, + exec: threadDump.execTime, + sampled: threadDump.sampled, + path: threadDump.entryPoint, + transactionId: threadDump.transactionId, + localTraceId: threadDump.localTraceId + }; + }); + } + this.showLoading = false; + }, (errorMessage: string) => { + this.serverResponseError = false; + this.hasErrorResponse = true; + this.errorMessage = errorMessage; + this.showLoading = false; + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.dateFormat$ = this.storeHelperService.getDateFormat(this.unsubscribe, 2); + } + hasData(): boolean { + return this.rowData.length > 0; + } + hasError(): boolean { + return this.hasErrorResponse; + } + onSelectThread($param: IParam): void { + this.threadDumpLogInteractionService.sendParam($param); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list.component.css b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list.component.css new file mode 100644 index 000000000000..f943a2791973 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list.component.css @@ -0,0 +1,76 @@ +:host { + width: 100%; + height: 100%; +} +#thread-dump-table .ag-root { + border: none; + font-size: 12px; + font-family: 'Open Sans', sans-serif; +} +#thread-dump-table .ag-cell { + padding: 4px; + font-family: 'Open Sans', sans-serif; +} +#thread-dump-table .ag-column-moving .ag-cell { + transition: left 0.2s; +} +#thread-dump-table .ag-header-cell-moving .ag-header-cell-label { + opacity: 0; + filter: alpha(opacity=0); +} +#thread-dump-table .ag-header-cell-moving { + background-color: #bebebe; +} +#thread-dump-table .ag-header-cell-moving-clone { + border-right: 1px solid #808080; + border-left: 1px solid #808080; + background-color: rgba(220,220,220,0.8); +} +#thread-dump-table .ag-header { + background: #f6f8fb; + border-top: 1px solid #e6e8ec; + line-height: 2; +} +#thread-dump-table .ag-header-cell { + font-size: 12px; + font-weight: 600; + font-family: 'Open Sans', sans-serif; + padding-left: 2px; + padding-right: 2px; +} +#thread-dump-table .ag-header-cell-resize:after { + border-right: none; +} +#thread-dump-table .ag-header-cell-label { + padding: 4px; +} +#thread-dump-table .ag-group-expanded span { + margin-right: 4px; +} +#thread-dump-table .ag-row:hover { + cursor: pointer; + background-color: #F5F5F5; +} +#thread-dump-table .ag-row { + line-height: 2; + border-bottom: 1px solid #e6e8ec; +} +#thread-dump-table .ag-body { + background-color: #ffffff; +} +#thread-dump-table .ag-body-viewport { + background-color: #ffffff; +} +#thread-dump-table .ag-menu { + background-color: #ffffff; + border: 1px solid grey; +} +#thread-dump-table .fa { + font-size: 14px; +} +#thread-dump-table .ag-row-exception { + background-color: #fff1f1; +} +#thread-dump-table .ag-row-selected { + background-color: #e4f5e3; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list.component.html b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list.component.html new file mode 100644 index 000000000000..13fcc4a3fc83 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list.component.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list.component.ts b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list.component.ts new file mode 100644 index 000000000000..e12c21e2ae9a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-list/thread-dump-list.component.ts @@ -0,0 +1,147 @@ +import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; +import * as moment from 'moment-timezone'; +import { GridOptions } from 'ag-grid'; + +export interface IThreadDumpData { + index: number; + id: string; + name: string; + state: string; + startTime: number; + exec: number; + sampled: boolean; + path: string; + transactionId: string; + localTraceId: number; +} + +@Component({ + selector: 'pp-thread-dump-list', + templateUrl: './thread-dump-list.component.html', + styleUrls: ['./thread-dump-list.component.css'], + encapsulation: ViewEncapsulation.None +}) +export class ThreadDumpListComponent implements OnInit, OnDestroy { + @Input() rowData: IThreadDumpData[]; + @Input() timezone: string; + @Input() dateFormat: string; + @Output() outSelectThread: EventEmitter = new EventEmitter(); + gridOptions: GridOptions; + constructor() {} + ngOnInit() { + this.initGridOptions(); + } + private initGridOptions(): void { + this.gridOptions = { + rowHeight: 30, + columnDefs: this.makeColumnDefs(), + animateRows: true, + rowSelection: 'single', + headerHeight: 34, + enableSorting: false, + enableColResize: true, + onCellClicked: (params: any) => { + if ( params.colDef.field === 'localTraceId' ) { + const tag = params.event.target.tagName.toUpperCase(); + if (tag === 'I' || tag === 'BUTTON' ) { + this.outSelectThread.next({ + threadName: params.data.name, + localTraceId: params.data.localTraceId + }); + return; + } + } + } + }; + } + private makeColumnDefs(): any { + return [ + { + headerName: '#', + field: 'index', + width: 30, + cellStyle: () => { + return {'text-align': 'center'}; + }, + suppressSizeToFit: true + }, + { + headerName: 'id', + field: 'id', + width: 60, + cellStyle: () => { + return {'text-align': 'center'}; + }, + suppressSizeToFit: true + }, + { + headerName: 'name', + field: 'name', + width: 150, + tooltipField: 'name' + }, + { + headerName: 'state', + field: 'state', + width: 120, + suppressSizeToFit: true + }, + { + headerName: 'start time', + field: 'startTime', + width: 140, + valueFormatter: (params: any) => { + return moment(params.value).tz(this.timezone).format(this.dateFormat); + }, + suppressSizeToFit: true, + tooltipField: 'startTime' + }, + { + headerName: 'exec(ms)', + field: 'exec', + width: 120, + suppressSizeToFit: true + }, + { + headerName: 'sampled', + field: 'sampled', + width: 90, + suppressSizeToFit: true + }, + { + headerName: 'path', + field: 'path', + width: 200, + tooltipField: 'path' + }, + { + headerName: 'transaction id', + field: 'transactionId', + width: 220, + suppressSizeToFit: true, + tooltipField: 'transactionId' + }, + { + headerName: '', + field: 'localTraceId', + width: 40, + cellStyle: () => { + return {'text-align': 'center'}; + }, + cellRenderer: () => { + return ''; + }, + suppressSizeToFit: true + } + ]; + } + ngOnDestroy() { + } + onGridReady(params: GridOptions): void { + this.gridOptions.api.sizeColumnsToFit(); + } + onGridSizeChanged(params: GridOptions): void { + this.gridOptions.api.sizeColumnsToFit(); + } + +} diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/active-thread-dump-detail-info-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/active-thread-dump-detail-info-data.service.ts new file mode 100644 index 000000000000..6089e8cb8245 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/active-thread-dump-detail-info-data.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, retry, tap } from 'rxjs/operators'; + +export interface IActiveThreadDump { + threadId: string; + threadName: string; + threadState: string; + startTime: number; + execTime: number; + localTraceId: number; + sampled: boolean; + transactionId: string; + entryPoint: string; + detailMessage: string; +} +export interface IActiveThreadDumpResponse { + code: number; + message: { + subType: string; + threadDumpData: IActiveThreadDump[]; + type: string; + version: string; + }; +} + +@Injectable() +export class ActiveThreadDumpDetailInfoDataService { + requestURL = 'agent/activeThreadDump.pinpoint'; + constructor(private http: HttpClient) {} + getData(applicationName: string, agentId: string, threadName: string, localTraceId: number): Observable { + return this.http.get(this.requestURL, this.makeRequestOptionsArgs(applicationName, agentId, threadName, localTraceId)).pipe( + retry(3), + tap((data: any) => { + if (data.exception) { + throw data.exception.message; + } + }), + catchError(this.handleError) + ); + } + private handleError(error: HttpErrorResponse | string) { + return throwError(error['message'] || error); + } + private makeRequestOptionsArgs(applicationName: string, agentId: string, threadName: string, localTraceId: number): object { + return { + params: new HttpParams() + .set('applicationName', applicationName) + .set('agentId', agentId) + .set('threadName', threadName) + .set('localTraceId', '' + localTraceId) + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/index.ts b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/index.ts new file mode 100644 index 000000000000..6dda36aad50f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/index.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; + +import { ThreadDumpLogContainerComponent } from './thread-dump-log-container.component'; +import { ThreadDumpLogInteractionService } from './thread-dump-log-interaction.service'; +import { ActiveThreadDumpDetailInfoDataService } from './active-thread-dump-detail-info-data.service'; + +@NgModule({ + declarations: [ + ThreadDumpLogContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + ThreadDumpLogContainerComponent + ], + providers: [ + ThreadDumpLogInteractionService, + ActiveThreadDumpDetailInfoDataService + ] +}) +export class ThreadDumpLogModule {} diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-container.component.css b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-container.component.css new file mode 100644 index 000000000000..3fb81075194e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-container.component.css @@ -0,0 +1,9 @@ +textarea { + width: calc(100% - 40px); + height: calc(100% - 65px); + resize: none; + padding: 10px; + margin: 20px; + background-color: #FFF; + border: 1px solid #D0D7E1; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-container.component.html b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-container.component.html new file mode 100644 index 000000000000..3b9086fdc4ea --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-container.component.html @@ -0,0 +1,2 @@ + + diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-container.component.ts new file mode 100644 index 000000000000..ab53f065d3e1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-container.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService } from 'app/shared/services'; +import { ActiveThreadDumpDetailInfoDataService } from './active-thread-dump-detail-info-data.service'; +import { ThreadDumpLogInteractionService, IParam } from './thread-dump-log-interaction.service'; + +@Component({ + selector: 'pp-thread-dump-log-container', + templateUrl: './thread-dump-log-container.component.html', + styleUrls: ['./thread-dump-log-container.component.css'], +}) +export class ThreadDumpLogContainerComponent implements OnInit, OnDestroy { + @ViewChild('logDisplay') target: ElementRef; + private unsubscribe: Subject = new Subject(); + private applicationName: string; + private agentId: string; + showLoading = false; + constructor( + private newUrlStateNotificationService: NewUrlStateNotificationService, + private activeThreadDumpDetailInfoDataService: ActiveThreadDumpDetailInfoDataService, + private threadDumpLogInteractionService: ThreadDumpLogInteractionService + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.APPLICATION, UrlPathId.AGENT_ID); + } + )).subscribe((urlService: NewUrlStateNotificationService) => { + this.applicationName = urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName(); + this.agentId = urlService.getPathValue(UrlPathId.AGENT_ID); + }); + this.threadDumpLogInteractionService.onParam$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((param: IParam) => { + this.showLoading = true; + this.loadData(param.threadName, param.localTraceId); + }); + } + private loadData(threadName: string, localTraceId: number): void { + this.activeThreadDumpDetailInfoDataService.getData(this.applicationName, this.agentId, threadName, localTraceId).subscribe((data: any) => { + let msg = ''; + if (data.code === -1) { + msg = data.message; + } else { + if (data.message.threadDumpData.length > 0) { + msg = data.message.threadDumpData[0].detailMessage; + } else { + msg = 'There is no message( may be completed )'; + } + } + this.target.nativeElement.value = msg; + this.showLoading = false; + }, (errorMessage: string) => { + this.target.nativeElement.value = errorMessage; + this.showLoading = false; + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-interaction.service.ts new file mode 100644 index 000000000000..c7563354b6df --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/thread-dump-log/thread-dump-log-interaction.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; + +export interface IParam { + threadName: string; + localTraceId: number; +} + +@Injectable() +export class ThreadDumpLogInteractionService { + private outParam: Subject = new Subject(); + onParam$: Observable; + constructor() { + this.onParam$ = this.outParam.asObservable(); + } + sendParam(param: IParam): void { + this.outParam.next(param); + } +} + diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/index.ts b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/index.ts new file mode 100644 index 000000000000..6edf79987899 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/index.ts @@ -0,0 +1,22 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TimelineCommandGroupComponent } from './timeline-command-group.component'; +import { TimelineCommandGroupContainerComponent } from './timeline-command-group-container.component'; + +@NgModule({ + declarations: [ + TimelineCommandGroupComponent, + TimelineCommandGroupContainerComponent + ], + imports: [ + CommonModule + ], + exports: [ + TimelineCommandGroupContainerComponent + ], + providers: [ + + ] +}) +export class TimelineCommandGroupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group-container.component.css b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group-container.component.css new file mode 100644 index 000000000000..1620771952cf --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group-container.component.css @@ -0,0 +1,6 @@ +.l-command-group { + display: flex; + padding: 4px 12px; + justify-content: flex-end; + background-color: #F5F5F5; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group-container.component.html b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group-container.component.html new file mode 100644 index 000000000000..86a6d9bf04ee --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group-container.component.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group-container.component.ts new file mode 100644 index 000000000000..e912d4137a21 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group-container.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import * as moment from 'moment-timezone'; +import { Subject, Observable, combineLatest } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { StoreHelperService } from 'app/shared/services'; + +@Component({ + selector: 'pp-timeline-command-group-container', + templateUrl: './timeline-command-group-container.component.html', + styleUrls: ['./timeline-command-group-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TimelineCommandGroupContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + pointingTime: string; + pointingTime$: Observable; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService + ) {} + ngOnInit() { + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + combineLatest( + this.storeHelperService.getDateFormat(this.unsubscribe, 0), + this.storeHelperService.getTimezone(this.unsubscribe), + this.storeHelperService.getInspectorTimelineSelectedTime(this.unsubscribe) + ).pipe( + takeUntil(this.unsubscribe) + ).subscribe((data: [string, string, number]) => { + const dateFormat = data[0]; + const timezone = data[1]; + this.pointingTime = moment(data[2]).tz(timezone).format(dateFormat); + this.changeDetectorRef.detectChanges(); + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group.component.css b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group.component.css new file mode 100644 index 000000000000..41c09c3f6814 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group.component.css @@ -0,0 +1,42 @@ +:host { + display: flex; +} +.l-current-time { + height: 28px; + display: flex; +} +.l-current-time label { + color: #FFF; + cursor: pointer; + height: 100%; + display: inline-block; + padding: 7px 10px; + font-size: 13px; + font-weight: 600; + background-color: #4A8FD2; +} +.l-current-time input { + width: 180px; + height: 100%; + border: 1px solid #4A8FD2; + display: inline-block; + font-size: 13px; + text-align: center; + background-color: #FFF; +} +.l-command-group { + display: inline-block; +} +.l-command-group:first-child { + margin-right: 30px; +} +.l-command-group:nth-child(2) { + margin-right: 10px; +} +.l-command-group .current-time { + font-size: 14px; + text-decoration: underline; +} +.l-command-group button { + border-radius: 0px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group.component.html b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group.component.html new file mode 100644 index 000000000000..6621bc822c17 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group.component.html @@ -0,0 +1,12 @@ +
+
+ + +
+
+
+ +
+
+ +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group.component.ts b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group.component.ts new file mode 100644 index 000000000000..41d00f858f2a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline-command-group/timeline-command-group.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { TimelineInteractionService } from 'app/core/components/timeline/timeline-interaction.service'; + +@Component({ + selector: 'pp-timeline-command-group', + templateUrl: './timeline-command-group.component.html', + styleUrls: ['./timeline-command-group.component.css'], +}) +export class TimelineCommandGroupComponent implements OnInit { + @Input() pointingTime: string; + constructor(private timelineInteractionService: TimelineInteractionService) {} + ngOnInit() {} + onClickZoomIn(): void { + this.timelineInteractionService.setZoomIn(); + } + onClickZoomOut(): void { + this.timelineInteractionService.setZoomOut(); + } + onClickPrev(): void { + this.timelineInteractionService.setPrev(); + } + onClickNext(): void { + this.timelineInteractionService.setNext(); + } + onClickNow(): void { + this.timelineInteractionService.setNow(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/agent-inspector-timeline-container.component.css b/web/src/main/webapp/v2/src/app/core/components/timeline/agent-inspector-timeline-container.component.css new file mode 100644 index 000000000000..da48c3f40268 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/agent-inspector-timeline-container.component.css @@ -0,0 +1,4 @@ +:host { + display: block; + margin: 18px 12px 0px 12px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/agent-inspector-timeline-container.component.html b/web/src/main/webapp/v2/src/app/core/components/timeline/agent-inspector-timeline-container.component.html new file mode 100644 index 000000000000..923ae4fdedd0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/agent-inspector-timeline-container.component.html @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/agent-inspector-timeline-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/agent-inspector-timeline-container.component.ts new file mode 100644 index 000000000000..baac808c1cbd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/agent-inspector-timeline-container.component.ts @@ -0,0 +1,149 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { takeUntil, withLatestFrom } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { Actions } from 'app/shared/store'; +import { StoreHelperService, NewUrlStateNotificationService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; +import { Timeline, ITimelineEventSegment, TimelineUIEvent } from './class'; +import { TimelineComponent } from './timeline.component'; +import { TimelineInteractionService, ITimelineCommandParam, TimelineCommand } from './timeline-interaction.service'; +import { AgentTimelineDataService, IAgentTimeline, IRetrieveTime } from './agent-timeline-data.service'; + +@Component({ + selector: 'pp-agent-inspector-timeline-container', + templateUrl: './agent-inspector-timeline-container.component.html', + styleUrls: ['./agent-inspector-timeline-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AgentInspectorTimelineContainerComponent implements OnInit, OnDestroy { + @ViewChild(TimelineComponent) + private timelineComponent: TimelineComponent; + private unsubscribe: Subject = new Subject(); + private agentId = ''; + timelineStartTime: number; + timelineEndTime: number; + selectionStartTime: number; + selectionEndTime: number; + pointingTime: number; + timelineData: IAgentTimeline; + timezone$: Observable; + dateFormat$: Observable; + + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private agentTimelineDataService: AgentTimelineDataService, + private timelineInteractionService: TimelineInteractionService, + private analyticsService: AnalyticsService, + ) {} + + ngOnInit() { + this.connectStore(); + this.timelineInteractionService.onCommand$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((param: ITimelineCommandParam) => { + switch (param.command) { + case TimelineCommand.zoomIn: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.ZOOM_IN_TIMELINE); + this.timelineComponent.zoomIn(); + break; + case TimelineCommand.zoomOut: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.ZOOM_OUT_TIMELINE); + this.timelineComponent.zoomOut(); + break; + case TimelineCommand.prev: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.MOVE_TO_PREV_ON_TIMELINE); + this.timelineComponent.movePrev(); + break; + case TimelineCommand.next: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.MOVE_TO_NEXT_ON_TIMELINE); + this.timelineComponent.moveNext(); + break; + case TimelineCommand.now: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.MOVE_TO_NOW_ON_TIMELINE); + this.timelineComponent.moveNow(); + break; + } + this.updateTimelineData(); + }); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + withLatestFrom(this.storeHelperService.getInspectorTimelineData(this.unsubscribe)) + ).subscribe(([urlService, savedTimelineData]: [NewUrlStateNotificationService, ITimelineInfo]) => { + this.agentId = this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID); + const selectionStartTime = urlService.getStartTimeToNumber(); + const selectionEndTime = urlService.getEndTimeToNumber(); + const range = this.calcuRetrieveTime(selectionStartTime, selectionEndTime); + let timelineInfo: ITimelineInfo = { + range: [range.start, range.end], + selectedTime: selectionEndTime, + selectionRange: [selectionStartTime, selectionEndTime] + }; + if (urlService.isChanged(UrlPathId.APPLICATION) === false && urlService.isChanged(UrlPathId.PERIOD) === false) { + if (savedTimelineData.selectedTime !== 0) { + timelineInfo = savedTimelineData; + } + } + this.agentTimelineDataService.getData(this.agentId, { + start: timelineInfo.range[0], + end: timelineInfo.range[1] + }).subscribe((response: IAgentTimeline) => { + this.timelineStartTime = timelineInfo.range[0]; + this.timelineEndTime = timelineInfo.range[1]; + this.selectionStartTime = timelineInfo.selectionRange[0]; + this.selectionEndTime = timelineInfo.selectionRange[1]; + this.pointingTime = timelineInfo.selectedTime; + this.timelineData = response; + this.storeHelperService.dispatch(new Actions.UpdateTimelineData(timelineInfo)); + this.changeDetector.detectChanges(); + }); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.dateFormat$ = this.storeHelperService.getDateFormatArray(this.unsubscribe, 0, 5, 6); + } + calcuRetrieveTime(startTime: number, endTime: number ): IRetrieveTime { + const allowedMaxRagne = Timeline.MAX_TIME_RANGE; + const timeGap = endTime - startTime; + if ( timeGap > allowedMaxRagne ) { + return { + start: endTime - allowedMaxRagne, + end: endTime + }; + } else { + const calcuStart = timeGap * 3; + return { + start: endTime - (calcuStart > allowedMaxRagne ? allowedMaxRagne : calcuStart), + end: endTime + }; + } + } + updateTimelineData(): void { + const range = this.timelineComponent.getTimelineRange(); + this.agentTimelineDataService.getData(this.agentId, { + start: range[0], + end: range[1] + }).subscribe((response: IAgentTimeline) => { + this.timelineComponent.updateData(response); + }); + } + onSelectEventStatus($eventObj: ITimelineEventSegment): void { + this.timelineInteractionService.sendSelectedEventStatus($eventObj); + } + onChangeTimelineUIEvent(event: TimelineUIEvent): void { + if (event.changedSelectedTime) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CHANGE_POINTING_TIME_ON_TIMELINE); + } + if (event.changedSelectionRange) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CHANGE_SELECTION_RANGE_ON_TIMELINE); + } + this.storeHelperService.dispatch(new Actions.UpdateTimelineData(event.data)); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/agent-timeline-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/agent-timeline-data.service.ts new file mode 100644 index 000000000000..0a6492d4392d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/agent-timeline-data.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export interface IAgentTimeline { + agentEventTimeline: { + timelineSegments: any + }; + agentStatusTimeline: { + includeWarning: boolean; + timelineSegments: { + endTimestamp: number; + startTimestamp: number; + value: string; + }[] + }; +} +export interface IRetrieveTime { + start: number; + end: number; +} + +@Injectable() +export class AgentTimelineDataService { + requestURL = 'getAgentStatusTimeline.pinpoint'; + constructor(private http: HttpClient) { } + getData(agentId: string, retrieveTime: IRetrieveTime): Observable { + return this.http.get(this.requestURL, this.makeRequestOptionsArgs(agentId, retrieveTime)); + } + private makeRequestOptionsArgs(agentId: string, { start: from, end: to }: IRetrieveTime): { 'params': { [key: string]: any } } { + return { + params: { + agentId, + from, + to, + exclude: 10199 + // DESC: + // [exclude] 요청에 대한 응답에서 제외하고 싶은 eventCode를 넣어줌. + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/application-inspector-timeline-container.component.css b/web/src/main/webapp/v2/src/app/core/components/timeline/application-inspector-timeline-container.component.css new file mode 100644 index 000000000000..da48c3f40268 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/application-inspector-timeline-container.component.css @@ -0,0 +1,4 @@ +:host { + display: block; + margin: 18px 12px 0px 12px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/application-inspector-timeline-container.component.html b/web/src/main/webapp/v2/src/app/core/components/timeline/application-inspector-timeline-container.component.html new file mode 100644 index 000000000000..923ae4fdedd0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/application-inspector-timeline-container.component.html @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/application-inspector-timeline-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/application-inspector-timeline-container.component.ts new file mode 100644 index 000000000000..e639536a7dc4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/application-inspector-timeline-container.component.ts @@ -0,0 +1,152 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { takeUntil, withLatestFrom } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { Actions } from 'app/shared/store'; +import { StoreHelperService, NewUrlStateNotificationService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; +import { Timeline, ITimelineEventSegment, TimelineUIEvent } from './class'; +import { TimelineComponent } from './timeline.component'; +import { TimelineInteractionService, ITimelineCommandParam, TimelineCommand } from './timeline-interaction.service'; +import { IAgentTimeline, IRetrieveTime } from './agent-timeline-data.service'; + +@Component({ + selector: 'pp-application-inspector-timeline-container', + templateUrl: './application-inspector-timeline-container.component.html', + styleUrls: ['./application-inspector-timeline-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ApplicationInspectorTimelineContainerComponent implements OnInit, OnDestroy { + @ViewChild(TimelineComponent) + private timelineComponent: TimelineComponent; + private unsubscribe: Subject = new Subject(); + timelineStartTime: number; + timelineEndTime: number; + selectionStartTime: number; + selectionEndTime: number; + pointingTime: number; + timelineData: IAgentTimeline; + timezone$: Observable; + dateFormat$: Observable; + + constructor( + private changeDetector: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private timelineInteractionService: TimelineInteractionService, + private analyticsService: AnalyticsService, + ) {} + + ngOnInit() { + this.connectStore(); + this.timelineInteractionService.onCommand$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((param: ITimelineCommandParam) => { + switch (param.command) { + case TimelineCommand.zoomIn: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.ZOOM_IN_TIMELINE); + this.timelineComponent.zoomIn(); + break; + case TimelineCommand.zoomOut: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.ZOOM_OUT_TIMELINE); + this.timelineComponent.zoomOut(); + break; + case TimelineCommand.prev: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.MOVE_TO_PREV_ON_TIMELINE); + this.timelineComponent.movePrev(); + break; + case TimelineCommand.next: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.MOVE_TO_NEXT_ON_TIMELINE); + this.timelineComponent.moveNext(); + break; + case TimelineCommand.now: + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.MOVE_TO_NOW_ON_TIMELINE); + this.timelineComponent.moveNow(); + break; + } + }); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + withLatestFrom(this.storeHelperService.getInspectorTimelineData(this.unsubscribe)), + ).subscribe(([urlService, savedTimelineData]: [NewUrlStateNotificationService, ITimelineInfo]) => { + /* + if ( application.changed or period.changed ) { + url 값 사용 + } else { + store에 저장된 값 있나? + - 있다면 사용 + - 없다면 URL 값 사용 + } + */ + const selectionStartTime = urlService.getStartTimeToNumber(); + const selectionEndTime = urlService.getEndTimeToNumber(); + const range = this.calcuRetrieveTime(selectionStartTime, selectionEndTime); + let timelineInfo: ITimelineInfo = { + range: [range.start, range.end], + selectedTime: selectionEndTime, + selectionRange: [selectionStartTime, selectionEndTime] + }; + if (urlService.isChanged(UrlPathId.APPLICATION) === false && urlService.isChanged(UrlPathId.PERIOD) === false) { + if (savedTimelineData.selectedTime !== 0) { + timelineInfo = savedTimelineData; + } + } + this.timelineStartTime = timelineInfo.range[0]; + this.timelineEndTime = timelineInfo.range[1]; + this.selectionStartTime = timelineInfo.selectionRange[0]; + this.selectionEndTime = timelineInfo.selectionRange[1]; + this.pointingTime = timelineInfo.selectedTime; + this.timelineData = { + 'agentStatusTimeline': { + 'timelineSegments': [ + { + 'startTimestamp': timelineInfo.range[0], + 'endTimestamp': timelineInfo.range[1], + 'value': 'EMPTY' + } + ], + 'includeWarning': false + }, + 'agentEventTimeline': { + 'timelineSegments': [] + } + }; + this.storeHelperService.dispatch(new Actions.UpdateTimelineData(timelineInfo)); + this.changeDetector.detectChanges(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + this.dateFormat$ = this.storeHelperService.getDateFormatArray(this.unsubscribe, 0, 5, 6); + } + calcuRetrieveTime(startTime: number, endTime: number ): IRetrieveTime { + const allowedMaxRagne = Timeline.MAX_TIME_RANGE; + const timeGap = endTime - startTime; + if ( timeGap > allowedMaxRagne ) { + return { + start: endTime - allowedMaxRagne, + end: endTime + }; + } else { + const calcuStart = timeGap * 3; + return { + start: endTime - (calcuStart > allowedMaxRagne ? allowedMaxRagne : calcuStart), + end: endTime + }; + } + } + onSelectEventStatus($eventObj: ITimelineEventSegment): void {} + onChangeTimelineUIEvent(event: TimelineUIEvent): void { + if (event.changedSelectedTime) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CHANGE_POINTING_TIME_ON_TIMELINE); + } + if (event.changedSelectionRange) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CHANGE_SELECTION_RANGE_ON_TIMELINE); + } + this.storeHelperService.dispatch(new Actions.UpdateTimelineData(event.data)); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/index.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/index.ts new file mode 100644 index 000000000000..abd5e75590c0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/index.ts @@ -0,0 +1,13 @@ +export * from './timeline-background.class'; +export * from './timeline-data.class'; +export * from './timeline-events.class'; +export * from './timeline-handler.class'; +export * from './timeline-loading-indicator.class'; +export * from './timeline-position-manager.class'; +export * from './timeline-selection-manager.class'; +export * from './timeline-selection-point.class'; +export * from './timeline-signboard.class'; +export * from './timeline-state-line.class'; +export * from './timeline-x-axis.class'; +export * from './timeline-ui-event'; +export * from './timeline.class'; diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-background.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-background.class.ts new file mode 100644 index 000000000000..e2faaa846b96 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-background.class.ts @@ -0,0 +1,17 @@ +declare var Snap: any; +declare var mina: any; + +export class TimelineBackground { + private backgroundRect: any; + constructor(private snap: any, private group: any, private options: { [key: string]: number }) { + this.addElements(); + } + addElements(): void { + this.backgroundRect = this.snap.rect(this.options.left, this.options.top, this.options.width, this.options.height); + this.group.add(this.backgroundRect); + } + reset(width: number): void { + this.backgroundRect.animate({ 'width': width }, this.options.duration, mina.easein); + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-data.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-data.class.ts new file mode 100644 index 000000000000..6c444b9cc21d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-data.class.ts @@ -0,0 +1,88 @@ +import { BehaviorSubject } from 'rxjs'; + +export interface ITimelineData { + agentEventTimeline: IAgentEventTimeline; + agentStatusTimeline: IAgentStatusTimeline; +} +export interface IAgentEventTimeline { + timelineSegments: ITimelineEventSegment[]; +} +export interface ITimelineEventSegment { + startTimestamp: number; + endTimestamp: number; + value: { + totalCount: number; + typeCounts: ITimelineEventSegmentTypeCount[]; + }; +} +export interface ITimelineEventSegmentTypeCount { + code: number; + desc: string; + count: number; +} +export interface IAgentStatusTimeline { + includeWarning: boolean; + timelineSegments: ITimelineStatusSegment[]; +} +export interface ITimelineStatusSegment { + endTimestamp: number; + startTimestamp: number; + value: string; +} + +export class TimelineData { + aStatusRawData: ITimelineStatusSegment[]; + oStatusRawHash: { [key: string]: ITimelineStatusSegment }; + aEventRawData: ITimelineEventSegment[]; + + onChangeStatusData$: BehaviorSubject = new BehaviorSubject(null); + onChangeEventData$: BehaviorSubject = new BehaviorSubject(null); + constructor(aRawData: ITimelineData) { + this.initData(aRawData); + this.onChangeStatusData$.next(this.aStatusRawData); + this.onChangeEventData$.next(this.aEventRawData); + } + initData(aRawData: ITimelineData): void { + this.initStatusData(aRawData.agentStatusTimeline); + this.initEventData(aRawData.agentEventTimeline); + } + initStatusData(oStatusRawData: IAgentStatusTimeline): void { + this.aStatusRawData = oStatusRawData.timelineSegments || []; + this.oStatusRawHash = {}; + this.aStatusRawData.forEach((segment: ITimelineStatusSegment) => { + this.oStatusRawHash[this.makeID(segment)] = segment; + }); + } + initEventData(oEventRawData: IAgentEventTimeline): void { + this.aEventRawData = oEventRawData.timelineSegments || []; + } + makeID(segment: ITimelineStatusSegment): string { + return segment.endTimestamp + ''; + } + eventCount(): number { + return this.aEventRawData.length; + } + statusCount(): number { + return this.aStatusRawData.length; + } + getDataByIndex(index: number): ITimelineStatusSegment { + return this.aStatusRawData[index]; + } + getDataByKey(key: string): ITimelineStatusSegment { + return this.oStatusRawHash[key]; + } + getEventDataByIndex(index: number): ITimelineEventSegment { + return this.aEventRawData[index]; + } + emptyData(): void { + this.aStatusRawData = []; + this.oStatusRawHash = {}; + this.aEventRawData = []; + } + addData(oNewData: ITimelineData): void { + this.initData(oNewData); + this.onChangeStatusData$.next(this.aStatusRawData); + this.onChangeEventData$.next(this.aEventRawData); + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-events.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-events.class.ts new file mode 100644 index 000000000000..11b5aa2a564b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-events.class.ts @@ -0,0 +1,127 @@ +declare var Snap: any; +import { Subject } from 'rxjs'; +import { ITimelineEventSegment } from './timeline-data.class'; +import { TimelinePositionManager } from './timeline-position-manager.class'; + +export class TimelineEvents { + private aEventGroupElement: any[] = []; + private aEventStatusData: ITimelineEventSegment[]; + clickEvent$: Subject = new Subject(); + constructor(private snap: any, private group: any, private options: { [key: string]: any }, private oPositionManager: TimelinePositionManager ) { + this.initOptions(); + this.addEvents(); + } + initOptions() { + const options = { + 'y': 4, + 'barLength': 4, + 'gapBarNCircle': 2, + 'circleRadius': 8, + 'filter': this.snap.filter( Snap.filter.shadow(1, 1, 1, '#000', 0.3)) + }; + Object.keys(options).forEach((key) => { + this.options[key] = options[key]; + }); + } + addEvents() { + this.group.click((event: any, x: number, y: number) => { + const targetElement = this.getElement(event, 'g'); + const dataIndex = Math.floor(targetElement.getAttribute('data-id')); + // const dataTime = targetElement.getAttribute('data-time'); + this.clickEvent$.next(this.aEventStatusData[dataIndex]); + }); + } + getElement(event: any, elementName: string): any | null { + let target = event.target; + while (target) { + if ( target.tagName.toLowerCase() === elementName ) { + break; + } + target = target.parentElement; + } + return target; + } + updateData(data: ITimelineEventSegment[]): void { + this.aEventStatusData = data; + this.emptyData(); + this.reset(); + } + emptyData(): void { + this.aEventGroupElement.forEach((g: any) => { + g.attr('display', 'none'); + }); + } + reset(): void { + const oldLen = this.aEventGroupElement.length; + const newLen = this.aEventStatusData.length; + let index; + + if ( oldLen === newLen ) { + for ( index = 0 ; index < newLen ; index++ ) { + this.reposition(this.aEventGroupElement[index], index); + } + } else if ( oldLen > newLen ) { + for ( index = newLen ; index < oldLen ; index++ ) { + this.aEventGroupElement[index].attr('display', 'none'); + } + for ( index = 0 ; index < newLen ; index++ ) { + this.reposition(this.aEventGroupElement[index], index); + } + } else { // oldLen < newLen + for ( index = 0 ; index < oldLen ; index++ ) { + this.reposition(this.aEventGroupElement[index], index); + } + for ( index = oldLen ; index < newLen ; index++ ) { + this.addEventElement(this.aEventStatusData[index], index); + } + } + } + reposition(elEventGroup: any, index: number): void { + const oEvent = this.aEventStatusData[index]; + const time = oEvent.startTimestamp + (oEvent.endTimestamp - oEvent.startTimestamp) / 2; + const x = this.oPositionManager.getPositionByTime(time); + const oTextInfo = this.getEventTextInfo(oEvent.value.totalCount); + elEventGroup[2].attr({ + x: oTextInfo.x, + y: oTextInfo.y, + text: oTextInfo.text + }); + elEventGroup.attr('display', 'block'); + elEventGroup.animate({ + 'transform': `translate(${x}, 0)` + }, this.options.duration); + } + addEventElement(oEvent: ITimelineEventSegment, index: number): void { + this.group.add(this.makeElement(oEvent, index)); + } + makeElement(oEvent: ITimelineEventSegment, index: number): any { + const time = oEvent.startTimestamp + (oEvent.endTimestamp - oEvent.startTimestamp) / 2; + const oTextInfo = this.getEventTextInfo(oEvent.value.totalCount); + const elEventGroup = this.group.g().attr({ + 'data-id': index, + 'data-time': time, + 'transform': `translate(${this.oPositionManager.getPositionByTime(time)}, 0)` + }); + elEventGroup.add( + this.snap.line(0, this.options.y, 0, this.options.y + this.options.barLength), + this.snap.circle(0, this.options.y + this.options.circleRadius + this.options.gapBarNCircle + this.options.barLength, this.options.circleRadius).attr({ + 'class': 'event', + 'filter': this.options.filter, + 'data-time': time + }), + this.snap.text(oTextInfo.x, oTextInfo.y, oTextInfo.text).attr({ + 'class': 'event' + }) + ); + this.aEventGroupElement.push(elEventGroup); + return elEventGroup; + } + getEventTextInfo(totalCount: number): { x: number, y: number, text: string } { + return { + x: totalCount < 10 ? -(this.options.circleRadius / 3 ) : -(this.options.circleRadius / 4 ) * 3, + y: this.options.y + this.options.circleRadius + (this.options.circleRadius / 2) + this.options.gapBarNCircle + this.options.barLength, + text: totalCount >= 100 ? '...' : totalCount + '' + }; + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-handler.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-handler.class.ts new file mode 100644 index 000000000000..baba9ea5031f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-handler.class.ts @@ -0,0 +1,87 @@ +declare var Snap: any; +declare var mina: any; +import { Subject } from 'rxjs'; + +export class TimelineHandler { + static HANDLER_IMAGE_WIDTH = 30; + static HANDLER_IMAGE_HEIGHT = 18; + + previousX: number; + handlerGrip: any; + handlerGroup: any; + onDragStart$: Subject = new Subject(); + onDragging$: Subject = new Subject(); + onDragEnd$: Subject<{ dragged: boolean, x: number }> = new Subject(); + + constructor(private snap: any, private group: any, private options: { [key: string]: any }) { + this.previousX = options.x; + this.addElements(); + this.setX(options.x); + this.addEvent(); + } + addElements(): void { + this.handlerGrip = this.snap.circle(0, 3, 5).attr({ + 'fill': '#777af9', + 'cursor': 'pointer', + 'stroke': '#4E50C8', + 'stroke-width': '3px' + }); + this.handlerGroup = this.group.g(); + this.handlerGroup.add( + this.snap.line( 0, 0, 0, this.options.height ), + this.snap.circle(0, 3, 7).attr({ + 'fill': '#000', + 'filter': Snap.filter.shadow(0, 0, 2, '#000', .5) + }), + this.handlerGrip + ); + } + addEvent(): void { + let lastX = -1; + this.handlerGrip.click((event: any) => { + event.stopPropagation(); + }); + this.handlerGrip.mousedown((event: any) => { + event.stopPropagation(); + }); + this.handlerGrip.drag((dx: number, dy: number, x: number, y: number, event: any) => { + const newX = x - this.options.margin; + if ( this.isInRestrictionZone(newX) === false ) { + return; + } + this.handlerGroup.attr({ + 'transform': `translate(${newX}, 0)` + }); + lastX = newX; + this.onDragging$.next(newX); + }, (x: number, y: number, event: any) => { + event.stopPropagation(); + this.onDragStart$.next(x - this.options.margin); + }, (event: any) => { + event.stopPropagation(); + if ( this.previousX !== lastX && lastX !== -1 ) { + this.onDragEnd$.next({ dragged: true, x: lastX }); + this.previousX = lastX; + } else { + this.onDragEnd$.next({ dragged: false, x: -1 }); + } + }); + } + setX(x: number): void { + this.handlerGroup.animate({ + 'transform': `translate(${x}, 0)` + }, this.options.duration, mina.easeout); + } + isInRestrictionZone(x: number): boolean { + return (x <= this.options.zone[0] || x >= this.options.zone[1]) ? false : true; + } + setZone(start: number, end: number): void { + this.options.zone = [start, end]; + } + setPositionAndZone(x: number, aZone: number[]): void { + this.setX(x); + this.onDragging$.next(x); + this.setZone(aZone[0], aZone[1]); + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-loading-indicator.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-loading-indicator.class.ts new file mode 100644 index 000000000000..101a18fa3a23 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-loading-indicator.class.ts @@ -0,0 +1,52 @@ +declare var Snap: any; +declare var mina: any; + +export class TimelineLoadingIndicator { + private clockWiseRect: any; + private antiClockWiseRect: any; + private bRun = false; + + constructor(private snap: any, private group: any, private options: { [key: string]: number }) { + this.addElements(); + this.show(); + } + addElements(): void { + const centerPosition = this.getCenterPosition(this.options.width, this.options.height, this.options.size); + const backgroundRect = this.snap.rect(0, 0, this.options.width, this.options.height); + + this.clockWiseRect = this.snap.rect(centerPosition.x, centerPosition.y, this.options.size, this.options.size).attr({ + 'stroke': 'rgba(197, 197, 197, .9)' + }); + this.antiClockWiseRect = this.snap.rect(centerPosition.x, centerPosition.y, this.options.size, this.options.size).attr({ + 'stroke': 'rgba(239, 246, 105)' + }); + this.group.add(backgroundRect, this.clockWiseRect, this.antiClockWiseRect); + } + getCenterPosition(width: number, height: number, size: number): {x: number, y: number} { + const halfSize = size / 2; + return { + x: width / 2 - halfSize, + y: height / 2 - halfSize + }; + } + show() { + this.group.attr('display', 'block'); + this.bRun = true; + this.animate( this.clockWiseRect, 0, 360, mina.easeout ); + this.animate( this.antiClockWiseRect, 45, -315, mina.easein ); + } + animate(ele, from, to, fnEase) { + Snap.animate(from, to, (val) => { + ele.attr('transform', 'rotate(' + val + 'deg)'); + }, this.options.duration, fnEase, () => { + if ( this.bRun === true ) { + this.animate( ele, to, from, fnEase ); + } + }); + } + hide() { + this.group.attr('display', 'none'); + this.bRun = false; + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-position-manager.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-position-manager.class.ts new file mode 100644 index 000000000000..34f1db70fb6a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-position-manager.class.ts @@ -0,0 +1,254 @@ +import * as moment from 'moment-timezone'; + +export class TimelinePositionManager { + static X_AXIS_TICKS = 5; + + width: number; + startTime: number; + endTime: number; + timePerPoint: number; + xAxisTicks: number; + pointingTime: number; + pointingPosition: number; + minTimeRange: number; + maxSelectionTimeRange; + aSelectionTimeRange: number[] = []; + aSelectionPosition: number[] = []; + + constructor(private options: any) { + this.width = options.width; + this.minTimeRange = options.minTimeRange; + this.maxSelectionTimeRange = options.maxSelectionTimeRange; + + this.initInnerVar(); + this.setTimelineRange(options.timelineRange[0], options.timelineRange[1]); + this.initSelectionTimeRange(options.selectionTimeRange || []); + this.resetSelectionByTime(); + this.xAxisTicks = options.xAxisTicks || TimelinePositionManager.X_AXIS_TICKS; + this.setPointingTime( options.pointingTime ); + } + initInnerVar(): void { + this.aSelectionTimeRange = []; + this.aSelectionPosition = []; + } + setTimelineRange(start: number, end: number): void { + this.startTime = start; + this.endTime = end; + this.calcuTimePerPoint(); + } + initSelectionTimeRange(aTime: number[]): void { + if ( aTime.length !== 2 ) { + aTime = [ this.startTime, this.endTime ]; + } + if ( aTime[1] - aTime[0] > this.maxSelectionTimeRange ) { + aTime[0] = aTime[1] - this.maxSelectionTimeRange; + } + this.setSelectionTimeRange(aTime[0], aTime[1]); + } + resetSelectionByTime(): void { + this.setSelectionPosition(this.getPositionByTime(this.aSelectionTimeRange[0]), this.getPositionByTime(this.aSelectionTimeRange[1])); + } + + calcuTimePerPoint(): void { + this.timePerPoint = ( this.endTime - this.startTime ) / this.width; + } + setSelectionTimeRange(start: number, end: number): void { + this.aSelectionTimeRange[0] = start === null ? this.aSelectionTimeRange[0] : start; + this.aSelectionTimeRange[1] = end === null ? this.aSelectionTimeRange[1] : end; + } + setSelectionPosition(start: number, end: number): void { + this.aSelectionPosition[0] = start === null ? this.aSelectionPosition[0] : start; + this.aSelectionPosition[1] = end === null ? this.aSelectionPosition[1] : end; + } + getPositionByTime(time: number): number { + return Math.floor((time - this.startTime) / this.timePerPoint); + } + setPointingTime(time: number ): void { + this.pointingTime = time; + this.pointingPosition = this.getPositionByTime(time); + } + isInMaxSelectionTimeRange(start: number, end: number): boolean { + return (end - start) <= this.maxSelectionTimeRange; + } + getNewSelectionTimeRangeFromStart(start: number): number[] { + return [start, start + this.maxSelectionTimeRange]; + } + getNewSelectionTimeRangeFromEnd(end: number): number[] { + return [end - this.maxSelectionTimeRange, end]; + } + isBeforeSliderStartTime(time: number): boolean { + return time < this.startTime; + } + isAfterSliderEndTime(time: number): boolean { + return time > this.endTime; + } + getTimelineEndTime(): number { + return this.endTime; + } + setWidth(width: number): void { + this.width = width; + this.calcuTimePerPoint(); + this.reset(); + } + getTimelineEndPosition(): number { + return this.width; + } + getTimelineStartTimeStr(): string { + return this.formatDate(new Date(this.startTime)); + } + getTimelineEndTimeStr(): string { + return this.formatDate( new Date(this.endTime) ); + } + getFullTimeStr(x: number): string { + return this.formatDate( new Date(this.getTimeFromPosition(x)) ); + } + formatDate(d: Date): string { + return moment(d).tz(this.options.timezone).format(this.options.dateFormat[0]); + } + isInSelectionZone(): boolean { + return (this.pointingTime >= this.aSelectionTimeRange[0] && this.pointingTime <= this.aSelectionTimeRange[1] ) ? true : false; + } + isInTimelineRange(time: number): boolean { + return (time >= this.startTime && time <= this.endTime ) ? true : false; + } + getTimelineRange(): number[] { + return [this.startTime, this.endTime]; + } + getSelectionTimeRange(): number[] { + return [this.aSelectionTimeRange[0], this.aSelectionTimeRange[1]]; + } + getSelectionPosition(): number[] { + return [this.aSelectionPosition[0], this.aSelectionPosition[1]]; + } + getPointingPosition(): number { + return this.pointingPosition; + } + getPointingTime(): number { + return this.pointingTime; + } + getPrevTime(): number { + const gap = this.aSelectionTimeRange[1] - this.aSelectionTimeRange[0]; + return this.aSelectionTimeRange[0] - Math.floor(gap / 2) - 1; + } + getNextTime(): number { + const gap = this.aSelectionTimeRange[1] - this.aSelectionTimeRange[0]; + const nextTime = this.aSelectionTimeRange[1] + Math.floor(gap / 2) + 1; + if ( nextTime > Date.now() ) { + return Date.now(); + } else { + return nextTime; + } + } + getTimeFromPosition(x: number): number { + return this.startTime + Math.floor(this.timePerPoint * x); + } + calcuSelectionZone(): void { + const currentSelectionSize = this.aSelectionTimeRange[1] - this.aSelectionTimeRange[0]; + const currentSelectionHalfSize = Math.round( currentSelectionSize / 2 ); + let selectionStart = this.pointingTime - currentSelectionHalfSize; + let selectionEnd = this.pointingTime + currentSelectionHalfSize; + if ( selectionStart < this.startTime ) { + selectionEnd = selectionStart + currentSelectionSize; + selectionStart = this.startTime; + } else if ( selectionEnd > this.endTime ) { + selectionStart = this.endTime - currentSelectionSize; + selectionEnd = this.endTime; + } + this.setSelectionTimeRange(selectionStart, selectionEnd); + this.setSelectionPosition(this.getPositionByTime(selectionStart), this.getPositionByTime(selectionEnd)); + } + getXAxisPositionData(): any { + const max = TimelinePositionManager.X_AXIS_TICKS + 1; + const space = Math.floor(this.width / max); + const a = []; + for ( let i = 0 ; i < max ; i++ ) { + if ( i === 0 ) { + continue; + } + const x = i * space; + a.push( { + x: x, + time: this.getTimeStr(x) + }); + } + return a; + } + getTimeStr(x: number): string { + const timeX = Math.floor( x * this.timePerPoint ) + this.startTime; + return moment(new Date(timeX)).tz(this.options.timezone).format(this.options.dateFormat[1]) + ' ' + moment(new Date(timeX)).tz(this.options.timezone).format(this.options.dateFormat[2]); + } + setSelectionStartTime(time: number): void { + this.setSelectionTimeRange(time, null); + this.setSelectionPosition(this.getPositionByTime(time), null); + } + setSelectionEndTime(time: number): void { + this.setSelectionTimeRange( null, time ); + this.setSelectionPosition( null, this.getPositionByTime( time ) ); + } + setSelectionStartPosition(x: number): void { + this.setSelectionTimeRange(this.getTimeFromPosition(x), null); + this.setSelectionPosition(x, null); + } + setSelectionEndPosition(x: number): void { + this.setSelectionTimeRange(null, this.getTimeFromPosition(x)); + this.setSelectionPosition(null, x); + } + zoomIn(): void { + // 선택 영역 중심으로 확대 + if ( this.startTime === this.aSelectionTimeRange[0] && this.endTime === this.aSelectionTimeRange[1] ) { + return; + } + const quarterTimeline = Math.floor((this.endTime - this.startTime) / 4); + let tempStartTime = this.pointingTime - quarterTimeline; + let tempEndTime = this.pointingTime + quarterTimeline; + + if ( tempEndTime - tempStartTime < this.minTimeRange ) { + const minHalf = Math.floor(this.minTimeRange / 2); + tempStartTime = this.pointingTime - minHalf; + tempEndTime = this.pointingTime + minHalf; + } + let gap; + if ( this.aSelectionTimeRange[0] < tempStartTime ) { + gap = tempStartTime - this.aSelectionTimeRange[0]; + const tempSelectionEndTime = (this.aSelectionTimeRange[1] + gap > tempEndTime) ? tempEndTime : this.aSelectionTimeRange[1] + gap; + this.setSelectionTimeRange(tempStartTime, tempSelectionEndTime); + } + if ( this.aSelectionTimeRange[1] > tempEndTime ) { + gap = this.aSelectionTimeRange[1] - tempEndTime; + const tempSelectionStartTime = (this.aSelectionTimeRange[0] - gap < tempStartTime) ? tempStartTime : this.aSelectionTimeRange[0] - gap; + this.setSelectionTimeRange(tempSelectionStartTime, tempEndTime); + } + this.setTimelineRange(tempStartTime, tempEndTime); + this.reset(); + } + zoomOut(): void { + const one = this.endTime - this.startTime; + const tempCenterTime = this.aSelectionTimeRange[0] + Math.floor((this.aSelectionTimeRange[1] - this.aSelectionTimeRange[0] ) / 2); + this.setTimelineRange(tempCenterTime - one, tempCenterTime + one); + this.reset(); + } + resetBySelectTime(time: number, bIsNow: boolean): void { + const halfSliderTimeRange = Math.floor((this.endTime - this.startTime) / 2); + const halfSelectionTimeRange = Math.floor((this.aSelectionTimeRange[1] - this.aSelectionTimeRange[0] ) / 2); + if ( bIsNow === true ) { + this.setTimelineRange(time - halfSliderTimeRange * 2, time); + this.setSelectionTimeRange(time - halfSelectionTimeRange * 2, time); + } else { + this.setTimelineRange(time - halfSliderTimeRange, time + halfSliderTimeRange); + this.setSelectionTimeRange(time - halfSelectionTimeRange, time + halfSelectionTimeRange); + } + this.resetSelectionByTime(); + this.setPointingTime(time); + } + reset(): void { + this.setPointingTime(this.pointingTime); + this.resetSelectionByTime(); + } + setTimezone(timezone: string): void { + this.options.timezone = timezone; + } + setDateFormat(dateFormat: string[]): void { + this.options.dateFormat = dateFormat; + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-selection-manager.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-selection-manager.class.ts new file mode 100644 index 000000000000..de12c0f0ac63 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-selection-manager.class.ts @@ -0,0 +1,179 @@ +import { Subject, Subscription } from 'rxjs'; +import { TimelinePositionManager } from './timeline-position-manager.class'; +import { TimelineSelectionZone } from './timeline-selection-zone.class'; +import { TimelineSelectionPoint } from './timeline-selection-point.class'; +import { TimelineHandler } from './timeline-handler.class'; +import { TimelineSignboard } from './timeline-signboard.class'; +import { TimelineUIEvent } from './timeline-ui-event'; + +export class TimelineSelectionManager { + + unsubscribe: Subscription; + onChangeTimelineUIEvent$: Subject = new Subject(); + onReset$: Subject = new Subject(); + constructor( + private options: { [key: string]: any }, + private oSelectionZone: TimelineSelectionZone, + private oSelectionPoint: TimelineSelectionPoint, + private oLeftHandler: TimelineHandler, + private oRightHandler: TimelineHandler, + private oLeftTimeSignboard: TimelineSignboard, + private oRightTimeSignboard: TimelineSignboard, + private oPositionManager: TimelinePositionManager + ) { + this.initClass(); + } + initClass(): void { + this.unsubscribe = this.oLeftHandler.onDragStart$.subscribe((x: number) => { + this.oLeftTimeSignboard.onDragStart(x); + }); + this.unsubscribe.add(this.oLeftHandler.onDragging$.subscribe((x: number) => { + this.oSelectionZone.onDragXStart(x); + this.oLeftTimeSignboard.onDrag(x); + })); + this.unsubscribe.add(this.oLeftHandler.onDragEnd$.subscribe((res: { dragged: boolean, x: number }) => { + this.oLeftTimeSignboard.onDragEnd(); + if (res.dragged) { + this.movedLeftHandler(res.x); + } + })); + + this.unsubscribe.add(this.oRightHandler.onDragStart$.subscribe((x: number) => { + this.oRightTimeSignboard.onDragStart(x); + })); + this.unsubscribe.add(this.oRightHandler.onDragging$.subscribe((x: number) => { + this.oSelectionZone.onDragXEnd(x); + this.oRightTimeSignboard.onDrag(x); + })); + this.unsubscribe.add(this.oRightHandler.onDragEnd$.subscribe((res: { dragged: boolean, x: number }) => { + this.oRightTimeSignboard.onDragEnd(); + if (res.dragged) { + this.movedRightHandler(res.x); + } + })); + } + movedLeftHandler(x: number): void { + let selectedTime = this.oPositionManager.getPointingTime(); + const event = new TimelineUIEvent(); + const aCurrentSelectionTimeRange = this.oPositionManager.getSelectionTimeRange(); + const newLeftTime = this.oPositionManager.getTimeFromPosition(x); + if ( this.oPositionManager.isInMaxSelectionTimeRange(newLeftTime, aCurrentSelectionTimeRange[1])) { + this.oRightHandler.setZone(x, this.oPositionManager.getTimelineEndPosition()); + this.oPositionManager.setSelectionStartPosition(x); + + if ( this.oPositionManager.isInSelectionZone() === false ) { + this.oPositionManager.setPointingTime(newLeftTime); + this.oSelectionPoint.setPointing(x); + selectedTime = newLeftTime; + event.setOnChangedSelectedTime(); + } + } else { + const aNewSelectionTimeSeries = this.oPositionManager.getNewSelectionTimeRangeFromStart(newLeftTime); + const newRightX = this.oPositionManager.getPositionByTime(aNewSelectionTimeSeries[1]); + this.oRightHandler.setZone(x, this.oPositionManager.getTimelineEndPosition()); + this.oPositionManager.setSelectionStartPosition(x); + this.oRightHandler.setX(newRightX); + this.oRightTimeSignboard.onDrag(newRightX); + this.oLeftHandler.setZone(0, newRightX); + this.oPositionManager.setSelectionEndPosition(newRightX); + + if ( this.oPositionManager.isInSelectionZone() === false ) { + this.oPositionManager.setPointingTime(aNewSelectionTimeSeries[1]); + this.oSelectionPoint.setPointing(newRightX); + selectedTime = aNewSelectionTimeSeries[1]; + event.setOnChangedSelectedTime(); + } + } + this.oSelectionZone.redraw(); + event.setOnChangedSelectionRange(); + event.setData( + selectedTime, + this.oPositionManager.getSelectionTimeRange(), + this.oPositionManager.getTimelineRange() + ); + this.onChangeTimelineUIEvent$.next(event); + } + movedRightHandler(x: number): void { + let selectedTime = this.oPositionManager.getPointingTime(); + const event = new TimelineUIEvent(); + const aCurrentSelectionTimeRange = this.oPositionManager.getSelectionTimeRange(); + const newRightTime = this.oPositionManager.getTimeFromPosition(x); + if ( this.oPositionManager.isInMaxSelectionTimeRange(aCurrentSelectionTimeRange[0], newRightTime) ) { + this.oLeftHandler.setZone(0, x); + this.oPositionManager.setSelectionEndPosition(x); + + if ( this.oPositionManager.isInSelectionZone() === false ) { + this.oPositionManager.setPointingTime(newRightTime); + this.oSelectionPoint.setPointing(x); + selectedTime = newRightTime; + event.setOnChangedSelectedTime(); + } + } else { + const aNewSelectionTimeSeries = this.oPositionManager.getNewSelectionTimeRangeFromEnd(newRightTime); + const newLeftX = this.oPositionManager.getPositionByTime(aNewSelectionTimeSeries[0]); + this.oLeftHandler.setZone(0, x); + this.oPositionManager.setSelectionEndPosition(x); + this.oLeftHandler.setX(newLeftX); + this.oLeftTimeSignboard.onDrag(newLeftX); + this.oRightHandler.setZone(newLeftX, this.oPositionManager.getTimelineEndPosition()); + this.oPositionManager.setSelectionStartPosition(newLeftX); + + if ( this.oPositionManager.isInSelectionZone() === false ) { + this.oPositionManager.setPointingTime(aNewSelectionTimeSeries[0]); + this.oSelectionPoint.setPointing(newLeftX); + selectedTime = aNewSelectionTimeSeries[0]; + event.setOnChangedSelectedTime(); + } + } + this.oSelectionZone.redraw(); + event.setOnChangedSelectionRange(); + event.setData( + selectedTime, + this.oPositionManager.getSelectionTimeRange(), + this.oPositionManager.getTimelineRange() + ); + this.onChangeTimelineUIEvent$.next(event); + } + moveSelectionAndHandler(): void { + const aNewSelectionZone = this.oPositionManager.getSelectionPosition(); + this.oLeftHandler.setPositionAndZone(aNewSelectionZone[0], [0, aNewSelectionZone[1]]); + this.oRightHandler.setPositionAndZone(aNewSelectionZone[1], [aNewSelectionZone[0], this.oPositionManager.getTimelineEndPosition()]); + this.oSelectionZone.redraw(); + } + onSetPointingByPosition(x: number): void { + this.onSetPointingByTime(this.oPositionManager.getTimeFromPosition(x)); + } + onSetPointingByTime(time: number, bIsNow?: boolean): void { + const event = new TimelineUIEvent(); + event.setOnChangedSelectedTime(); + if ( this.oPositionManager.isInTimelineRange(time) ) { + this.oPositionManager.setPointingTime(time); + if ( this.oPositionManager.isInSelectionZone() === false ) { + this.oPositionManager.calcuSelectionZone(); + this.moveSelectionAndHandler(); + event.setOnChangedSelectionRange(); + } + this.oSelectionPoint.setPointing(this.oPositionManager.getPointingPosition()); + } else { + this.oPositionManager.resetBySelectTime(time, bIsNow); + this.onReset$.next(); + event.setOnChangedSelectionRange(); + } + event.setData( + this.oPositionManager.getPointingTime(), + this.oPositionManager.getSelectionTimeRange(), + this.oPositionManager.getTimelineRange() + ); + this.onChangeTimelineUIEvent$.next(event); + } + reset(): void { + const aNewSelectionZone = this.oPositionManager.getSelectionPosition(); + this.oLeftHandler.setPositionAndZone(aNewSelectionZone[0], [0, aNewSelectionZone[1]]); + this.oRightHandler.setPositionAndZone(aNewSelectionZone[1], [aNewSelectionZone[0], this.oPositionManager.getTimelineEndPosition()]); + this.oSelectionZone.redraw(); + this.oSelectionPoint.setPointing(this.oPositionManager.getPointingPosition()); + } + destroy(): void { + this.unsubscribe.unsubscribe(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-selection-point.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-selection-point.class.ts new file mode 100644 index 000000000000..3659b6d33ded --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-selection-point.class.ts @@ -0,0 +1,24 @@ +declare var Snap: any; +declare var mina: any; + +export class TimelineSelectionPoint { + constructor(private snap: any, private group: any, private options: { [key: string]: number } ) { + this.addElements(); + this.setPointing( options.x ); + } + addElements(): void { + const halfRadius = this.options.radius / 2; + this.group.add( + this.snap.line(0, 0, 0, this.options.height), + this.snap.circle(0, this.options.height / 2, this.options.radius).attr({ + 'filter': this.snap.filter( Snap.filter.shadow(0, 0, 4, '#FF0', 1)) + }) + ); + } + setPointing(x: number): void { + this.group.animate({ + 'transform': `translate(${x}, ${this.options.y})` + }, this.options.duration, mina.easein); + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-selection-zone.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-selection-zone.class.ts new file mode 100644 index 000000000000..bcf88b289771 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-selection-zone.class.ts @@ -0,0 +1,36 @@ +declare var Snap: any; +import { TimelinePositionManager } from './timeline-position-manager.class'; + +export class TimelineSelectionZone { + private elZone: any; + constructor(private snap: any, private group: any, private options: { [key: string]: number }, private oPositionManager: TimelinePositionManager ) { + this.addElements(); + } + addElements() { + const aSelectionZone = this.oPositionManager.getSelectionPosition(); + this.elZone = this.snap.rect(aSelectionZone[0], this.options.top, aSelectionZone[1] - aSelectionZone[0], this.options.height); + this.group.add(this.elZone); + } + redraw() { + const aSelectionZone = this.oPositionManager.getSelectionPosition(); + this.elZone.animate({ + 'x': aSelectionZone[0], + 'width': aSelectionZone[1] - aSelectionZone[0] + }, this.options.duration); + } + onDragXStart(x: number): void { + const width = this.oPositionManager.getSelectionPosition()[1] - x; + this.elZone.attr({ + 'x': x, + 'width': width + }); + } + onDragXEnd(x: number): void { + const selectionStartPosition = this.oPositionManager.getSelectionPosition()[0]; + this.elZone.attr({ + 'x': selectionStartPosition, + 'width': x - selectionStartPosition + }); + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-signboard.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-signboard.class.ts new file mode 100644 index 000000000000..e7f912a52421 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-signboard.class.ts @@ -0,0 +1,45 @@ +declare var Snap: any; +import { TimelinePositionManager } from './timeline-position-manager.class'; + +export class TimelineSignboard { + private timeText: any; + private textMaxWidth = 10; + private xPadding = 10; + constructor(private snap: any, private group: any, private options: { [key: string]: any }, private oPositionManager: TimelinePositionManager ) { + this.addElements(); + } + addElements(): void { + const isIn = this.isIn(this.options.x); + const x = this.options.x + (isIn ? this.xPadding : -this.xPadding); + this.timeText = this.group.text(x, 26, this.oPositionManager.getFullTimeStr(this.options.x) ).attr({ + 'text-anchor': this.getAnchorPosition(isIn) + }); + this.group.add( this.timeText ); + } + setX(x: number): void { + const isIn = this.isIn(x); + this.timeText.attr({ + 'x': x + ( isIn ? this.xPadding : -this.xPadding ), + 'text': this.oPositionManager.getFullTimeStr(x), + 'text-anchor': this.getAnchorPosition(isIn) + }); + } + getAnchorPosition(innerPosition: boolean): string { + return innerPosition ? 'start' : 'end'; + } + isIn(x: number): boolean { + if ( this.options.direction === 'left' ) { + return x < this.textMaxWidth; + } else { + return x + this.textMaxWidth < this.oPositionManager.getTimelineEndPosition(); + } + } + onDragStart(x: number): void { + this.setX(x); + } + onDragEnd(): void {} + onDrag(x: number): void { + this.setX(x); + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-state-line.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-state-line.class.ts new file mode 100644 index 000000000000..ce92a2a2b784 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-state-line.class.ts @@ -0,0 +1,119 @@ +declare var Snap: any; +declare var mina: any; +import { TimelinePositionManager } from './timeline-position-manager.class'; +import { ITimelineStatusSegment } from './timeline-data.class'; + +export class TimelineStateLine { + static ID_SPLITER = '+'; + static ID_PREFIX_BASE = 'base-'; + static ID_POSTFIX = '+state-line'; + + statusData: ITimelineStatusSegment[]; + aBaseLine: any[] = []; + aLineElement: any[] = []; + + constructor(private snap: any, private group: any, private options: { [key: string]: any }, private oPositionManager: TimelinePositionManager) { + this.initBaseLine(); + } + initBaseLine(): void { + this.aBaseLine.push( + this.makeRect(0, this.options.width, 0, this.options.height, this.options.statusColor['BASE'], TimelineStateLine.ID_PREFIX_BASE + Date.now()) + ); + this.aBaseLine.forEach((line: any) => { + this.group.add(line); + }); + } + addStatus(oStatus: ITimelineStatusSegment, index: number): void { + if ( this.isOutsideOfTimeline(oStatus.startTimestamp, oStatus.endTimestamp) ) { + return; + } + this.addLine( + this.oPositionManager.getPositionByTime(oStatus.startTimestamp), + this.getX2(oStatus.endTimestamp), + this.options.statusColor[oStatus.value], + this.makeID(oStatus) + ); + } + isOutsideOfTimeline(start: number, end: number): boolean { + return this.oPositionManager.isInTimelineRange(start) === false && this.oPositionManager.isInTimelineRange(end) === false; + } + makeID(oStatus: ITimelineStatusSegment): string { + return oStatus.endTimestamp + TimelineStateLine.ID_POSTFIX; + } + addLine(x: number, x2: number, backgroundColor: string, id: string): void { + const line = this.makeRect(x, x2, 0, this.options.height, backgroundColor, id); + this.aLineElement.push(line); + this.group.add(line); + } + makeRect(x: number, x2: number, y: number, y2: number, color: string, id: string): any { + return this.snap.rect(x, y, x2, y2).attr({ + 'fill': color, + 'data-id': id + }); + } + getX2(end: number): number { + return this.oPositionManager.getPositionByTime(end === -1 ? this.oPositionManager.getTimelineEndTime() : end); + } + updateData(statusData: ITimelineStatusSegment[]): void { + this.statusData = statusData; + this.updateRender(); + } + updateRender(): void { + let index; + const curLen = this.aLineElement.length; + const newLen = this.statusData.length; + if ( curLen === newLen ) { + for ( index = 0 ; index < curLen ; index++ ) { + this.show(this.aLineElement[index]); + } + } else if ( curLen > newLen ) { + for ( index = newLen ; index < curLen ; index++ ) { + this.hide(this.aLineElement[index]); + } + } else { // curLen < newLen + for ( index = 0 ; index < curLen ; index++ ) { + this.show(this.aLineElement[index]); + } + for ( index = curLen ; index < newLen ; index++ ) { + this.addStatus(this.statusData[index], index); + } + } + this.reset(); + } + emptyData(): void { + this.aLineElement.forEach((line: any) => { + this.hide(line); + }); + } + reset() { + for ( let i = 0 ; i < this.aLineElement.length ; i++ ) { + const line = this.aLineElement[i]; + const oStatus = this.statusData[i]; + if ( oStatus ) { + const x = this.oPositionManager.getPositionByTime(oStatus.startTimestamp); + this.show(line); + line.animate({ + 'x': x, + 'width': this.getX2(oStatus.endTimestamp) - x + }, this.options.duration, mina.easeOut, () => { + line.attr('fill', this.options.statusColor[oStatus.value]); + }); + } else { + this.hide(line); + } + } + const endPosition = this.oPositionManager.getTimelineEndPosition(); + this.aBaseLine.forEach((elBase: any) => { + elBase.animate({ + 'width': endPosition + }, this.options.duration); + }); + } + show(el: any) { + el.attr('display', 'block'); + } + hide(el: any) { + el.attr('display', 'none'); + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-ui-event.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-ui-event.ts new file mode 100644 index 000000000000..755662264891 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-ui-event.ts @@ -0,0 +1,46 @@ +export class TimelineUIEvent { + changedSelectedTime = false; + changedSelectionRange = false; + changedRange = false; + data = { + selectedTime: 0, + range: [0, 0], + selectionRange: [0, 0] + }; + setData(time: number, selectionRange: number[], range: number[]): TimelineUIEvent { + this.data.selectedTime = time; + this.data.selectionRange = selectionRange; + this.data.range = range; + return this; + } + setSelectedTime(time: number): TimelineUIEvent { + this.data.selectedTime = time; + return this; + } + setSelectionRange(range: number[]): TimelineUIEvent { + this.data.selectionRange = range; + return this; + } + setRange(range: number[]): TimelineUIEvent { + this.data.range = range; + return this; + } + setOnChangedSelectedTime(): void { + this.changedSelectedTime = true; + } + setOffChangedSelectedTime(): void { + this.changedSelectedTime = false; + } + setOnChangedSelectionRange(): void { + this.changedSelectionRange = true; + } + setOffChangedSelectionRange(): void { + this.changedSelectionRange = false; + } + setOnRange(): void { + this.changedRange = true; + } + setOffRange(): void { + this.changedRange = false; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-x-axis.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-x-axis.class.ts new file mode 100644 index 000000000000..589a8d2bda01 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline-x-axis.class.ts @@ -0,0 +1,40 @@ +declare var Snap: any; +import { TimelinePositionManager } from './timeline-position-manager.class'; + +export class TimelineXAxis { + aText: any[]; + aAxisGroup: any[] = []; + + constructor(private snap: any, private group: any, private options: { [key: string]: any }, private oPositionManager: TimelinePositionManager) { + this.aText = []; + this.init(); + } + init() { + const aXBarPosition = this.oPositionManager.getXAxisPositionData(); + const centerX = this.options.width / 2; + + aXBarPosition.forEach((xBar: any, index: number) => { + const g = this.group.g(); + const text = this.snap.text(0, this.options.textY, aXBarPosition[index].time); + this.aText.push(text); + g.attr('transform', 'translate(' + centerX + ', 0)'); + g.add(text, this.snap.line(0, this.options.startY, 0, this.options.endY)); + this.group.add(g); + this.aAxisGroup.push(g); + this.resetXPosition(g, aXBarPosition[index].x, centerX); + }); + } + resetXPosition(g: any, x: number, startX: number): void { + Snap.animate(startX, x, (val: number) => { + g.attr('transform', `translate(${val}, 0)`); + }, this.options.duration); + } + reset() { + const aYBarPosition = this.oPositionManager.getXAxisPositionData(); + for ( let i = 0 ; i < aYBarPosition.length ; i++ ) { + this.aAxisGroup[i].attr('transform', `translate(${aYBarPosition[i].x}, 0)`); + this.aText[i].attr('text', aYBarPosition[i].time); + } + } + destroy(): void {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline.class.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline.class.ts new file mode 100644 index 000000000000..62cf161a75cb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/class/timeline.class.ts @@ -0,0 +1,386 @@ +import { Subject } from 'rxjs'; +import { TimelineData, ITimelineData, ITimelineStatusSegment, ITimelineEventSegment } from './timeline-data.class'; +import { TimelinePositionManager } from './timeline-position-manager.class'; +import { TimelineLoadingIndicator } from './timeline-loading-indicator.class'; +import { TimelineBackground } from './timeline-background.class'; +import { TimelineStateLine } from './timeline-state-line.class'; +import { TimelineSelectionZone } from './timeline-selection-zone.class'; +import { TimelineSelectionPoint } from './timeline-selection-point.class'; +import { TimelineHandler } from './timeline-handler.class'; +import { TimelineSignboard } from './timeline-signboard.class'; +import { TimelineSelectionManager } from './timeline-selection-manager.class'; +import { TimelineXAxis } from './timeline-x-axis.class'; +import { TimelineEvents } from './timeline-events.class'; +import { TimelineUIEvent } from './timeline-ui-event'; + +declare var Snap: any; + +// TODO: Reset 확인 +// TODO: Resize 확인 +// TODO: 데이터 업데이트시 동작 확인 +// TODO: timeline-selection-manager.onSetPointingByTime의 timeline.reset() 확인 +export class Timeline { + static MAX_TIME_RANGE = 604800000; // 7day + static GROUP_TYPE: { [key: string]: string } = { + TOP_BASE: 'TOP-BASE', + CONTENT_BASE: 'CONTENT-BASE', + BOTTOM_BASE: 'BOTTOM-BASE' + }; + static DRAWING_ORDER: { [key: string]: number } = { + 'background': 0, + 'state-line': 3, + 'selection-zone': 5, + 'x-axis': 10, + 'events': 10, + 'time-signboard': 15, + 'selection-point': 15, + 'left-handler': 25, + 'right-handler': 25, + 'guide': 30, + 'loading': 100 + }; + static DEFAULT_STATUS_COLOR: { [key: string]: string } = { + 'BASE': 'rgba(187, 187, 187, 0.3)', + 'UNKNOWN': 'rgba(220, 214, 214, 0.8)', + 'RUNNING': 'rgba(0, 158, 0, 0.4)', + 'SHUTDOWN': 'rgba(209, 82, 96, 0.7)', + 'UNSTABLE_RUNNING': 'rgba(255, 102, 0, 0.4)', + 'EMPTY': 'rgba(165, 219, 245, 0.7)' + }; + private snap: any; + + private oLoading: TimelineLoadingIndicator; + private oBackground: TimelineBackground; + private oTimelineData: TimelineData; + private oPositionManager: TimelinePositionManager; + private oStateLine: TimelineStateLine; + private oSelectionManager: TimelineSelectionManager; + private oXAxis: TimelineXAxis; + private oEvents: TimelineEvents; + + private svgGroups: { [key: string]: any} = {}; + private options: { [key: string]: any } = { + 'top': 0, + 'left': 0, + 'duration': 50, + 'xAxisTicks': 5, + 'minTimeRange': 6000, // 6sec + 'eventZoneHeight': 30, // 하단 이벤트 영역의 height + 'headerZoneHeight': 20, // 상단 시간 표시영역의 height + 'stateLineThickness': 4, // 상태선의 두께 + 'maxSelectionTimeRange': Timeline.MAX_TIME_RANGE, // 7day + 'headerTextTopPadding': 10, // 상단 상태선과 시간 text의 간격 + 'pointerRadius': 6 + }; + onChangeTimelineUIEvent$: Subject = new Subject(); + onSelectEventStatus$: Subject = new Subject(); + + constructor(private element: any, options: { [key: string]: any }) { + this.snap = Snap(element); + this.snap.attr({'height': options.height }); + this.initOptions(options); + this.initDataClass(options.timelineData); + this.checkOffset(); + this.initControlClass(); + this.addEventListener(); + } + initOptions(options: { [key: string]: any }) { + Object.keys(options).forEach((key: string) => { + this.options[key] = options[key]; + }); + this.setDefaultStatusColor(); + this.checkOffset(); + } + setDefaultStatusColor() { + if ( this.options['statusColor'] ) { + Object.keys(Timeline.DEFAULT_STATUS_COLOR).forEach((key: string) => { + if ( !this.options['statusColor'][key] ) { + this.options['statusColor'][key] = Timeline.DEFAULT_STATUS_COLOR[key]; + } + }); + } else { + const statusColor = {}; + Object.keys(Timeline.DEFAULT_STATUS_COLOR).forEach((key: string) => { + statusColor[key] = Timeline.DEFAULT_STATUS_COLOR[key]; + }); + this.options['statusColor'] = statusColor; + } + } + initDataClass(timelineData: ITimelineData) { + this.oTimelineData = new TimelineData(timelineData || { + agentEventTimeline: { + timelineSegments: [] + }, + agentStatusTimeline: { + includeWarning: false, + timelineSegments: [] + } + }); + } + checkOffset() { + const offset = this.element.getBoundingClientRect(); + this.options.top = offset.top; + this.options.left = offset.left; + } + initControlClass() { + this.oPositionManager = new TimelinePositionManager( { + 'width': this.options.width, + 'xAxisTicks': this.options.xAxisTicks, + 'pointingTime': this.options.pointingTime, + 'minTimeRange': this.options.minTimeRange, + 'timelineRange': this.options.timelineRange, + 'selectionTimeRange': this.options.selectionTimeRange, + 'maxSelectionTimeRange': this.options.maxSelectionTimeRange, + 'timezone': this.options.timezone, + 'dateFormat': this.options.dateFormat + }); + const contentZoneHeight = this.options.height - this.options.headerZoneHeight - this.options.eventZoneHeight; + + this.oLoading = new TimelineLoadingIndicator(this.snap, this.getGroup('loading', Timeline.GROUP_TYPE.TOP_BASE, Timeline.DRAWING_ORDER['loading']), { + 'size': 30, + 'width': this.options.width, + 'height': this.options.height, + 'duration': 2000 + }); + this.oBackground = new TimelineBackground(this.snap, this.getGroup('background', Timeline.GROUP_TYPE.CONTENT_BASE, Timeline.DRAWING_ORDER['background']), { + 'top': 0, + 'left': 0, + 'size': 30, + 'width': this.options.width, + 'height': contentZoneHeight, + 'duration': this.options.duration + }); + this.oStateLine = new TimelineStateLine(this.snap, this.getGroup('state-line', Timeline.GROUP_TYPE.CONTENT_BASE, Timeline.DRAWING_ORDER['state-line']), { + 'width': this.options.width, + 'height': contentZoneHeight, + 'duration': this.options.duration, + 'thickness': this.options.stateLineThickness, + 'statusColor': this.options.statusColor + }, this.oPositionManager); + + const aSelectionZone = this.oPositionManager.getSelectionPosition(); + const oSelectionZone = new TimelineSelectionZone(this.snap, this.getGroup('selection-zone', Timeline.GROUP_TYPE.CONTENT_BASE, Timeline.DRAWING_ORDER['selection-zone']), { + 'top': 0, + 'left': aSelectionZone[0], + 'width': aSelectionZone[1] - aSelectionZone[0], + 'height': contentZoneHeight, + 'duration': this.options.duration + }, this.oPositionManager); + const oSelectionPoint = new TimelineSelectionPoint(this.snap, this.getGroup('selection-point', Timeline.GROUP_TYPE.CONTENT_BASE, Timeline.DRAWING_ORDER['selection-point']), { + 'y': this.options.headerZoneHeight, + 'x': this.oPositionManager.getPointingPosition(), + 'radius': this.options.pointerRadius, + 'height': contentZoneHeight, + 'duration': this.options.duration + }); + const oLeftHandler = new TimelineHandler(this.snap, this.getGroup('left-handler', Timeline.GROUP_TYPE.CONTENT_BASE, Timeline.DRAWING_ORDER['left-handler']), { + 'x': aSelectionZone[0], + 'zone': [0, aSelectionZone[1]], + 'height': contentZoneHeight, + 'margin': this.options.left, + 'duration': this.options.duration + }); + const oRightHandler = new TimelineHandler(this.snap, this.getGroup('right-handler', Timeline.GROUP_TYPE.CONTENT_BASE, Timeline.DRAWING_ORDER['right-handler']), { + 'x': aSelectionZone[1], + 'zone': [ aSelectionZone[0], this.oPositionManager.getTimelineEndPosition() ], + 'height': contentZoneHeight, + 'margin': this.options.left, + 'duration': this.options.duration + }); + const oLeftTimeSignboard = new TimelineSignboard(this.snap, this.getGroup('time-left-signboard', Timeline.GROUP_TYPE.CONTENT_BASE, Timeline.DRAWING_ORDER['time-signboard']), { + 'x': aSelectionZone[0], + 'direction': 'left' + }, this.oPositionManager); + const oRightTimeSignboard = new TimelineSignboard(this.snap, this.getGroup('time-right-signboard', Timeline.GROUP_TYPE.CONTENT_BASE, Timeline.DRAWING_ORDER['time-signboard']), { + 'x': aSelectionZone[1], + 'direction': 'right' + }, this.oPositionManager); + this.oSelectionManager = new TimelineSelectionManager({ + 'headerZoneHeight': this.options.headerZoneHeight, + 'contentZoneHeight': contentZoneHeight + }, oSelectionZone, oSelectionPoint, oLeftHandler, oRightHandler, oLeftTimeSignboard, oRightTimeSignboard, this.oPositionManager); + + this.oXAxis = new TimelineXAxis(this.snap, this.getGroup('x-axis', Timeline.GROUP_TYPE.TOP_BASE, Timeline.DRAWING_ORDER['x-axis']), { + 'endY': this.options.height - this.options.eventZoneHeight, + 'width': this.options.width, + 'textY': this.options.headerTextTopPadding, + 'startY': this.options.headerZoneHeight, + 'duration': this.options.duration + }, this.oPositionManager); + this.oEvents = new TimelineEvents(this.snap, this.getGroup('events', Timeline.GROUP_TYPE.BOTTOM_BASE, Timeline.DRAWING_ORDER['events']), { + 'duration': this.options.duration + }, this.oPositionManager ); + + this.oLoading.hide(); + } + getGroup(name: string, type: string, zIndex: number) { + if ( this.svgGroups[name] ) { + return this.svgGroups[name]; + } + const g: any = this.svgGroups[name] = this.snap.g().attr({ + 'class': name, + 'data-order': zIndex + }); + this.setTransform(g, type); + this.sortGroup(g, zIndex); + return g; + } + setTransform(newGroup: any, type: string) { + switch ( type ) { + case Timeline.GROUP_TYPE.TOP_BASE: + newGroup.attr({ 'transform': `translate(0, 0)` }); + break; + case Timeline.GROUP_TYPE.CONTENT_BASE: + newGroup.attr({ 'transform': `translate(0, ${this.options.headerZoneHeight})` }); + break; + case Timeline.GROUP_TYPE.BOTTOM_BASE: + newGroup.attr({ 'transform': `translate(0, ${this.options.height - this.options.eventZoneHeight})` }); + break; + } + } + sortGroup(newGroup: any, zIndex: number) { + const aGroups = this.snap.selectAll('g'); + let afterGroup = null; + for ( let i = aGroups.length - 1; i >= 0 ; i-- ) { + if ( aGroups[i] === newGroup ) { + continue; + } + if ( zIndex < Math.floor(aGroups[i].attr('data-order')) ) { + afterGroup = aGroups[i]; + } + } + if ( afterGroup !== null ) { + afterGroup.before(newGroup); + } + } + addEventListener() { + let mousedownX = -1; + window.addEventListener('resize', () => { + this.checkOffset(); + this.resize(); + }); + this.snap.mousedown((event: any, x: number, y: number) => { + mousedownX = x; + }); + this.oEvents.clickEvent$.subscribe((eventData: ITimelineEventSegment) => { + this.onSelectEventStatus$.next(eventData); + }); + this.oTimelineData.onChangeStatusData$.subscribe((statusData: ITimelineStatusSegment[]) => { + this.oStateLine.updateData(statusData); + }); + this.oTimelineData.onChangeEventData$.subscribe((eventData: ITimelineEventSegment[]) => { + this.oEvents.updateData(eventData); + }); + this.oSelectionManager.onChangeTimelineUIEvent$.subscribe((event: TimelineUIEvent) => { + this.onChangeTimelineUIEvent$.next(event); + }); + this.oSelectionManager.onReset$.subscribe(() => { + this.reset(); + }); + + const eventStartPosition = this.options.headerZoneHeight; + const eventEndPosition = this.options.height - this.options.eventZoneHeight; + this.snap.click((event: any, x: number, y: number) => { + if ( mousedownX !== x ) { + return; + } + if ( event.offsetY > eventStartPosition && event.offsetY < eventEndPosition ) { + this.oSelectionManager.onSetPointingByPosition(event.offsetX); + } + }); + } + addData(oNewData: any) { + this.oLoading.show(); + this.oTimelineData.addData( oNewData ); + this.oLoading.hide(); + } + reset() { + this.oXAxis.reset(); + this.oSelectionManager.reset(); + this.oStateLine.reset(); + this.oEvents.reset(); + } + resize() { + this.oPositionManager.setWidth(this.element.getBoundingClientRect().width); + this.oBackground.reset(this.oPositionManager.getTimelineEndPosition()); + this.reset(); + } + zoomIn() { + // 1/2배씩 + this.oPositionManager.zoomIn(); + this.reset(); + this.fireChangedRangeEvent(); + } + zoomOut() { + // 2배씩 + this.oPositionManager.zoomOut(); + this.reset(); + this.fireChangedRangeEvent(); + } + private fireChangedRangeEvent(): void { + const event = new TimelineUIEvent(); + event.setOnRange(); + event.setData( + this.oPositionManager.getPointingTime(), + this.oPositionManager.getSelectionTimeRange(), + this.oPositionManager.getTimelineRange() + ); + this.onChangeTimelineUIEvent$.next(event); + } + resetTimeRangeAndSelectionZone(aSelectionFromTo: number[], aFromTo: number[], selectedTime: number) { + this.oPositionManager.setTimelineRange(aFromTo[0], aFromTo[1]); + this.oPositionManager.setSelectionStartTime(aSelectionFromTo[0]); + this.oPositionManager.setSelectionEndTime(aSelectionFromTo[1]); + this.oPositionManager.setPointingTime(selectedTime || aSelectionFromTo[1]); + this.emptyData(); + this.reset(); + } + movePrev() { + this.oSelectionManager.onSetPointingByTime(this.oPositionManager.getPrevTime()); + } + moveNext() { + this.oSelectionManager.onSetPointingByTime(this.oPositionManager.getNextTime()); + } + moveHead() { + this.oSelectionManager.onSetPointingByTime(Date.now(), true); + } + getTimelineRange() { + return this.oPositionManager.getTimelineRange(); + } + getSelectionTimeRange() { + return this.oPositionManager.getSelectionTimeRange(); + } + emptyData() { + this.oLoading.show(); + this.oTimelineData.emptyData(); + this.oEvents.emptyData(); + this.oStateLine.emptyData(); + this.oLoading.hide(); + } + setPointingTime(time: number): void { + this.oSelectionManager.onSetPointingByTime(time); + } + getPointingTime() { + return this.oPositionManager.getPointingTime(); + } + setTimezone(timezone: string): void { + this.options.timezone = timezone; + this.oPositionManager.setTimezone(timezone); + this.reset(); + } + setDateFormat(dateFormat: string[]): void { + this.options.dateFormat = dateFormat; + this.oPositionManager.setDateFormat(dateFormat); + this.reset(); + } + destroy() { + this.oLoading.destroy(); + this.oBackground.destroy(); + this.oTimelineData.destroy(); + this.oPositionManager.destroy(); + this.oStateLine.destroy(); + this.oSelectionManager.destroy(); + this.oXAxis.destroy(); + this.oEvents.destroy(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/index.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/index.ts new file mode 100644 index 000000000000..4e5d830d3c3a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/index.ts @@ -0,0 +1,28 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TimelineComponent } from './timeline.component'; +import { TimelineInteractionService } from './timeline-interaction.service'; +import { AgentInspectorTimelineContainerComponent } from './agent-inspector-timeline-container.component'; +import { ApplicationInspectorTimelineContainerComponent } from './application-inspector-timeline-container.component'; +import { AgentTimelineDataService } from './agent-timeline-data.service'; + +@NgModule({ + declarations: [ + TimelineComponent, + AgentInspectorTimelineContainerComponent, + ApplicationInspectorTimelineContainerComponent + ], + imports: [ + CommonModule + ], + exports: [ + AgentInspectorTimelineContainerComponent, + ApplicationInspectorTimelineContainerComponent + ], + providers: [ + TimelineInteractionService, + AgentTimelineDataService + ] +}) +export class TimelineModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/timeline-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/timeline-interaction.service.ts new file mode 100644 index 000000000000..e577b5af2285 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/timeline-interaction.service.ts @@ -0,0 +1,64 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { ITimelineEventSegment } from './class/timeline-data.class'; +export enum TimelineCommand { + zoomIn = 'ZOOM_IN', + zoomOut = 'ZOOM_OUT', + prev = 'PREV', + next = 'NEXT', + now = 'NOW' +} + +export interface ITimelineCommandParam { + command: TimelineCommand; + payload?: any; +} + +@Injectable() +export class TimelineInteractionService { + public onSelectPointingTime$: Observable; + public onSelectEventStatus$: Observable; + public onCommand$: Observable; + + private changePointingTimeSource = new Subject(); + private selectEventStatusSource = new Subject(); + private commandSource = new Subject(); + + constructor() { + this.onSelectPointingTime$ = this.changePointingTimeSource.asObservable(); + this.onSelectEventStatus$ = this.selectEventStatusSource.asObservable(); + this.onCommand$ = this.commandSource.asObservable(); + } + sendSelectedPointingTime(pointingTime: number): void { + this.changePointingTimeSource.next(pointingTime); + } + sendSelectedEventStatus(eventSegment: ITimelineEventSegment): void { + this.selectEventStatusSource.next(eventSegment); + } + setZoomIn(): void { + this.commandSource.next({ + command: TimelineCommand.zoomIn + }); + } + setZoomOut(): void { + this.commandSource.next({ + command: TimelineCommand.zoomOut + }); + } + setPrev(): void { + this.commandSource.next({ + command: TimelineCommand.prev + }); + } + setNext(): void { + this.commandSource.next({ + command: TimelineCommand.next + }); + } + setNow(): void { + this.commandSource.next({ + command: TimelineCommand.now + }); + } +} + diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/timeline.component.css b/web/src/main/webapp/v2/src/app/core/components/timeline/timeline.component.css new file mode 100644 index 000000000000..edb330555024 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/timeline.component.css @@ -0,0 +1,91 @@ +:host { + display: block; + user-select: none; +} +svg { + width: 100%; +} +svg text { + user-select: none; +} +.hide { + display: none; +} +.background rect { + fill: #EEE; +} + +.selection-zone rect { + fill: rgba(255, 255, 255, 0.5); +} +.x-axis text { + font-size: 12px; + text-anchor: middle; +} +.x-axis line { + stroke: #FFF; + stroke-width: 0.5; + stroke-linecap: square; +} +.left-handler rect, .right-handler rect { + fill: #FFF; +} +.left-handler line, .right-handler line { + stroke: #000; + stroke-width: 2; +} +.guide line { + stroke: #F00; + stroke-width: .5; + stroke-linecap: square; + stroke-dasharray: 2, 2; +} +.guide circle { + stroke: #F00; + stroke-width: 2; +} +.selection-point line { + stroke: #FF0; + stroke-width: 1; +} +.selection-point circle { + fill: #FF0; + stroke: #000; + stroke-width: 2; +} +.events line { + stroke: #000; + stroke-width: 1; +} +.events circle { + fill: #BDB76B; +} +.events text { + fill: #FFF; + font-size: 11px; +} +image { + cursor: pointer; +} +.event { + cursor: pointer; +} +.loading { + fill: rgba(170, 170, 170, 0.1); +} +.loading rect { + stroke-width: 2px; +} +.loading rect:nth-child(2) { + stroke: rgba(197, 197, 197, .9); +} +.loading rect:nth-child(3) { + stroke: rgba(239, 246, 105, .9); +} +.time-left-signboard text, .time-right-signboard text { + fill: #000; + font-size: 13px; +} +.time-series-signboard text { + fill: #BBB; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/timeline.component.html b/web/src/main/webapp/v2/src/app/core/components/timeline/timeline.component.html new file mode 100644 index 000000000000..e30fc442523d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/timeline.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timeline/timeline.component.ts b/web/src/main/webapp/v2/src/app/core/components/timeline/timeline.component.ts new file mode 100644 index 000000000000..36594afbca7b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timeline/timeline.component.ts @@ -0,0 +1,103 @@ +import { Component, OnInit, ViewEncapsulation, ChangeDetectionStrategy, OnChanges, OnDestroy, SimpleChanges, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core'; +import { Timeline, ITimelineData, ITimelineEventSegment, TimelineUIEvent } from './class'; + +@Component({ + selector: 'pp-timeline', + templateUrl: './timeline.component.html', + styleUrls: ['./timeline.component.css'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TimelineComponent implements OnInit, OnChanges, OnDestroy { + @ViewChild('timeline') el: ElementRef; + + @Input() data: any; + @Input() height: number; + @Input() pointingTime: number; + @Input() timelineStartTime: number; + @Input() timelineEndTime: number; + @Input() selectionStartTime: number; + @Input() selectionEndTime: number; + @Input() timezone: string; + @Input() dateFormat: string[]; + + @Output() outChangeTimelineUIEvent: EventEmitter = new EventEmitter(); + @Output() outSelectEventStatus: EventEmitter = new EventEmitter(); + + timeline: Timeline; + constructor(private hostElRef: ElementRef) {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if (changes['data'] && changes['data'].currentValue) { + this.initTimeline(); + } + if (changes['timezone'] && this.timeline) { + this.timeline.setTimezone(this.timezone); + } + if (changes['dateFormat'] && this.timeline) { + this.timeline.setDateFormat(this.dateFormat); + } + } + // selectionTime : 전체 조회 기간 중 선택 구간 + // start - from : timeline의 전체 조회 구간 + // pointingTime : 포인팅 지점 + initTimeline() { + if ( this.timeline ) { + this.timeline.resetTimeRangeAndSelectionZone( + [this.selectionStartTime, this.selectionEndTime], + [this.timelineStartTime, this.timelineEndTime], + this.pointingTime + ); + this.timeline.addData(this.data); + } else { + this.timeline = new Timeline(this.el.nativeElement, { + 'width': this.hostElRef.nativeElement.offsetWidth, + 'height': this.height, + 'timelineRange': [this.timelineStartTime, this.timelineEndTime], + 'selectionTimeRange': [this.selectionStartTime, this.selectionEndTime], + 'pointingTime': this.pointingTime, + 'timelineData': this.data, + 'timezone': this.timezone, + 'dateFormat': this.dateFormat, + 'statusColor': { + 'BASE': 'rgba(187, 187, 187, .3)', + 'UNKNOWN': 'rgba(220, 214, 214, .8)', + 'RUNNING': 'rgba(0, 158, 0, .4 )', + 'SHUTDOWN': 'rgba(209, 82, 96, .7)', + 'UNSTABLE_RUNNING': 'rgba(255, 102, 0, .4)', + 'EMPTY': 'rgba(165, 219, 245, .7)' + } + }); + this.timeline.onSelectEventStatus$.subscribe((eventSegment: ITimelineEventSegment) => { + this.outSelectEventStatus.emit(eventSegment); + }); + this.timeline.onChangeTimelineUIEvent$.subscribe((event: TimelineUIEvent) => { + this.outChangeTimelineUIEvent.emit(event); + }); + } + } + zoomIn(): void { + this.timeline.zoomIn(); + } + zoomOut(): void { + this.timeline.zoomOut(); + } + movePrev(): void { + this.timeline.movePrev(); + } + moveNext(): void { + this.timeline.moveNext(); + } + moveNow(): void { + this.timeline.moveHead(); + } + getTimelineRange(): number[] { + return this.timeline.getTimelineRange(); + } + updateData(data: any): void { + this.timeline.addData(data); + } + ngOnDestroy() { + // this.timeline.destroy(); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timezone/index.ts b/web/src/main/webapp/v2/src/app/core/components/timezone/index.ts new file mode 100644 index 000000000000..2a66aa18175b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timezone/index.ts @@ -0,0 +1,21 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { TimezoneSelectContainerComponent } from './timezone-select-container.component'; +import { TimezoneSelectComponent } from './timezone-select.component'; + +@NgModule({ + declarations: [ + TimezoneSelectContainerComponent, + TimezoneSelectComponent + ], + imports: [ + SharedModule + ], + exports: [ + TimezoneSelectContainerComponent, + ], + providers: [ + ] +}) +export class TimezoneModule {} diff --git a/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select-container.component.css b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select-container.component.css new file mode 100644 index 000000000000..7f26ddcb5503 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select-container.component.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select-container.component.html b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select-container.component.html new file mode 100644 index 000000000000..15cfaa0bf78d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select-container.component.html @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select-container.component.ts new file mode 100644 index 000000000000..fdbe26021570 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select-container.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import * as moment from 'moment-timezone'; +import { Observable, Subject } from 'rxjs'; + +import { Actions } from 'app/shared/store'; +import { StoreHelperService, WebAppSettingDataService, AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +@Component({ + selector: 'pp-timezone-select-container', + templateUrl: './timezone-select-container.component.html', + styleUrls: ['./timezone-select-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TimezoneSelectContainerComponent implements OnInit { + private unsubscribe: Subject = new Subject(); + timezoneList: string[]; + currentTimezone$: Observable; + + constructor( + private storeHelperService: StoreHelperService, + private webAppSettingDataService: WebAppSettingDataService, + private analyticsService: AnalyticsService, + ) {} + + ngOnInit() { + this.initTimezoneList(); + this.initCurrentTimezone(); + } + + private initTimezoneList(): void { + this.timezoneList = moment.tz.names(); + } + + private initCurrentTimezone(): void { + this.currentTimezone$ = this.storeHelperService.getTimezone(this.unsubscribe); + } + + onChangeTimezone(timezone: string): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SET_TIMEZONE_IN_CONFIGURATION, timezone); + this.webAppSettingDataService.setTimezone(timezone); + this.storeHelperService.dispatch(new Actions.ChangeTimezone(timezone)); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select.component.css b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select.component.css new file mode 100644 index 000000000000..b2b8789c1cdc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select.component.css @@ -0,0 +1,26 @@ +:host { + display: block; + position: relative; + width: 100%; +} + +.fa-angle-down { + position: absolute; + top: 8px; + right: 8px; + font-size: 15px; +} + +.l-app-select { + width: 100%; + cursor: pointer; + padding: 6px 12px; + background-color: #fff; + border: 1px solid #d7dde4 !important; + border-radius: 0px; + font-size: 13px; + color: #666; + appearance: none; + -webkit-appearance: none; + outline: 0; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select.component.html b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select.component.html new file mode 100644 index 000000000000..47390e84e189 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select.component.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select.component.ts b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select.component.ts new file mode 100644 index 000000000000..91a1ea8782be --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/timezone/timezone-select.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-timezone-select', + templateUrl: './timezone-select.component.html', + styleUrls: ['./timezone-select.component.css'], +}) +export class TimezoneSelectComponent implements OnInit { + @Input() timezoneList: string[]; + @Input() currentTimezone: string; + @Output() outChangeTimezone = new EventEmitter(); + + constructor() {} + ngOnInit() { + } + + onChangeTimezone(value: string): void { + this.outChangeTimezone.emit(value); + } + + compareFn(o1: string, o2: string): boolean { + return o1 && o2 ? o1 === o2 : false; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/index.ts new file mode 100644 index 000000000000..864afadbe8f1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/index.ts @@ -0,0 +1,35 @@ + +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { TransactionShortInfoModule } from 'app/core/components/transaction-short-info'; +import { TransactionDetailMenuModule } from 'app/core/components/transaction-detail-menu'; +import { TransactionSearchModule } from 'app/core/components/transaction-search'; +import { CallTreeModule } from 'app/core/components/call-tree'; +import { ServerMapModule } from 'app/core/components/server-map'; +import { TransactionTimelineModule } from 'app/core/components/transaction-timeline'; +import { SyntaxHighlightPopupModule } from 'app/core/components/syntax-highlight-popup'; +import { TransactionDetailContentsContainerComponent } from './transaction-detail-contents-container.component'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + TransactionDetailContentsContainerComponent + ], + imports: [ + SharedModule, + TransactionShortInfoModule, + TransactionDetailMenuModule, + TransactionSearchModule, + CallTreeModule, + ServerMapModule, + TransactionTimelineModule, + SyntaxHighlightPopupModule, + HelpViewerPopupModule + ], + exports: [ + TransactionDetailContentsContainerComponent + ], + providers: [] +}) +export class TransactionDetailContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/transaction-detail-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/transaction-detail-contents-container.component.css new file mode 100644 index 000000000000..edfceb22aea7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/transaction-detail-contents-container.component.css @@ -0,0 +1,24 @@ +.l-content-section { + height: 100%; + position: relative; +} +.l-tab-menu-options { + display: flex; + flex-flow: row wrap; +} +.l-component-wrapper { + width: 100%; + height: calc(100% - 84px); + position: relative; +} +.l-middle-tool-box { + height: 54px; + align-items: center; + justify-content: space-between; + padding: 0 25px; + position:relative; +} +.l-middle-tool-box, .l-middle-tool-box-tip { + color:#a8acb5; + font-size: 18px +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/transaction-detail-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/transaction-detail-contents-container.component.html new file mode 100644 index 000000000000..857c3f81ea97 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/transaction-detail-contents-container.component.html @@ -0,0 +1,15 @@ +
+ +
+ + +
+ +
+
+
+ + + +
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/transaction-detail-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/transaction-detail-contents-container.component.ts new file mode 100644 index 000000000000..a6629f31d8a8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-contents/transaction-detail-contents-container.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; + +import { TransactionViewTypeService, VIEW_TYPE, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-transaction-detail-contents-container', + templateUrl: './transaction-detail-contents-container.component.html', + styleUrls: ['./transaction-detail-contents-container.component.css'] +}) +export class TransactionDetailContentsContainerComponent implements OnInit { + private currentViewType: string; + constructor( + private transactionViewTypeService: TransactionViewTypeService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + + ngOnInit() { + this.transactionViewTypeService.onChangeViewType$.subscribe((viewType: string) => { + this.currentViewType = viewType; + }); + } + isHiddenSearchComponent(): boolean { + return this.currentViewType !== VIEW_TYPE.CALL_TREE && this.currentViewType !== VIEW_TYPE.TIMELINE; + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.CALL_TREE); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.CALL_TREE, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/index.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/index.ts new file mode 100644 index 000000000000..bdd1c1b49361 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/index.ts @@ -0,0 +1,26 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { TransactionDetailMenuComponent } from './transaction-detail-menu.component'; +import { TransactionDetailMenuContainerComponent } from './transaction-detail-menu-container.component'; +import { TransactionDetailMenuForDetailContainerComponent } from './transaction-detail-menu-for-detail-container.component'; +import { MessagePopupModule } from 'app/core/components/message-popup'; + +@NgModule({ + declarations: [ + TransactionDetailMenuComponent, + TransactionDetailMenuContainerComponent, + TransactionDetailMenuForDetailContainerComponent + ], + imports: [ + CommonModule, + MessagePopupModule + ], + exports: [ + TransactionDetailMenuContainerComponent, + TransactionDetailMenuForDetailContainerComponent + ], + providers: [] +}) +export class TransactionDetailMenuModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-container.component.html new file mode 100644 index 000000000000..952f08f59823 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-container.component.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-container.component.ts new file mode 100644 index 000000000000..29a0e3a33bc8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-container.component.ts @@ -0,0 +1,110 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +import { + StoreHelperService, + NewUrlStateNotificationService, + UrlRouteManagerService, + TransactionViewTypeService, + IViewType, TransactionDetailDataService, + ITransactionDetailPartInfo, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { MessagePopupContainerComponent } from 'app/core/components/message-popup/message-popup-container.component'; + +@Component({ + selector: 'pp-transaction-detail-menu-container', + templateUrl: './transaction-detail-menu-container.component.html', + styleUrls: ['./transaction-detail-menu-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransactionDetailMenuContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private transactionInfo: ITransactionMetaData; + viewTypeList: IViewType[]; + viewType: string; + partInfo: ITransactionDetailPartInfo; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private transactionViewTypeService: TransactionViewTypeService, + private transactionDetailDataService: TransactionDetailDataService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.viewTypeList = this.transactionViewTypeService.getViewTypeList(); + this.transactionViewTypeService.onChangeViewType$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((viewType: string) => { + this.viewType = viewType; + this.changeDetectorRef.detectChanges(); + }); + this.transactionDetailDataService.partInfo$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((partInfo: ITransactionDetailPartInfo) => { + this.partInfo = partInfo; + this.changeDetectorRef.detectChanges(); + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTransactionData(this.unsubscribe).pipe( + filter((data: ITransactionMetaData) => { + if (data && data.application && data.agentId && data.traceId) { + return true; + } + return false; + }) + ).subscribe((transactionInfo: ITransactionMetaData) => { + this.transactionInfo = transactionInfo; + this.changeDetectorRef.detectChanges(); + }); + } + onSelectViewType(viewType: string): void { + this.analyticsService.trackEvent((TRACKED_EVENT_LIST as any)[`CLICK_${viewType}`]); + this.urlRouteManagerService.moveOnPage({ + url: [ + UrlPath.TRANSACTION_LIST, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.TRANSACTION_INFO), + viewType + ] + }); + } + onOpenDetailView(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_TRANSACTION_VIEW); + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_VIEW, + this.transactionInfo.agentId, + this.transactionInfo.traceId, + this.transactionInfo.collectorAcceptTime + '', + this.transactionInfo.spanId, + ]); + } + onOpenExtraView(param: any): void { + if (param.open) { + this.urlRouteManagerService.openPage(param.url); + } else { + this.dynamicPopupService.openPopup({ + data: { + title: 'Notice', + contents: this.partInfo.disableButtonMessage + }, + component: MessagePopupContainerComponent + }); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-for-detail-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-for-detail-container.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-for-detail-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-for-detail-container.component.html new file mode 100644 index 000000000000..952f08f59823 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-for-detail-container.component.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-for-detail-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-for-detail-container.component.ts new file mode 100644 index 000000000000..93fa6b5ad4b2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu-for-detail-container.component.ts @@ -0,0 +1,79 @@ +import { Component, OnInit } from '@angular/core'; + +import { + NewUrlStateNotificationService, + UrlRouteManagerService, + TransactionViewTypeService, + IViewType, + TransactionDetailDataService, + ITransactionDetailPartInfo, + AnalyticsService, + TRACKED_EVENT_LIST, + DynamicPopupService +} from 'app/shared/services'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { MessagePopupContainerComponent } from 'app/core/components/message-popup/message-popup-container.component'; + +@Component({ + selector: 'pp-transaction-detail-menu-for-detail-container', + templateUrl: './transaction-detail-menu-for-detail-container.component.html', + styleUrls: ['./transaction-detail-menu-for-detail-container.component.css'] +}) +export class TransactionDetailMenuForDetailContainerComponent implements OnInit { + viewTypeList: IViewType[]; + viewType: string; + partInfo: ITransactionDetailPartInfo; + constructor( + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private transactionViewTypeService: TransactionViewTypeService, + private transactionDetailDataService: TransactionDetailDataService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.viewTypeList = this.transactionViewTypeService.getViewTypeList(); + this.transactionViewTypeService.onChangeViewType$.subscribe((viewType: string) => { + this.viewType = viewType; + }); + this.transactionDetailDataService.partInfo$.subscribe((partInfo: ITransactionDetailPartInfo) => { + this.partInfo = partInfo; + }); + } + onSelectViewType(viewType: string): void { + this.analyticsService.trackEvent((TRACKED_EVENT_LIST as any)[`CLICK_${viewType}`]); + this.urlRouteManagerService.moveOnPage({ + url: [ + UrlPath.TRANSACTION_DETAIL, + this.newUrlStateNotificationService.getPathValue(UrlPathId.TRACE_ID), + this.newUrlStateNotificationService.getPathValue(UrlPathId.FOCUS_TIMESTAMP), + this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), + this.newUrlStateNotificationService.getPathValue(UrlPathId.SPAN_ID), + viewType + ] + }); + } + onOpenDetailView(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_TRANSACTION_VIEW); + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_VIEW, + this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), + this.newUrlStateNotificationService.getPathValue(UrlPathId.TRACE_ID), + this.newUrlStateNotificationService.getPathValue(UrlPathId.FOCUS_TIMESTAMP), + this.newUrlStateNotificationService.getPathValue(UrlPathId.SPAN_ID) + ]); + } + onOpenExtraView(param: any): void { + if (param.open) { + this.urlRouteManagerService.openPage(param.url); + } else { + this.dynamicPopupService.openPopup({ + data: { + title: 'Notice', + contents: this.partInfo.disableButtonMessage + }, + component: MessagePopupContainerComponent + }); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu.component.css new file mode 100644 index 000000000000..e2eb9160ec67 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu.component.css @@ -0,0 +1,38 @@ +.l-wrapper { + display: flex; + flex-flow: row wrap; +} +.l-btn-group { + border:1px solid #e5e8ea; + height:32px; +} +.l-btn-group button { + background:#fff; + float:left; + padding:0 15px; + height:100%; +} +.l-btn-group button.active, .l-btn-group button:hover { + color:#fff; + font-weight:600; + background:#4a8fd2; +} +.l-transaction-state { + color: #FFF; + height: 32px; + padding: 8px; + font-size: 12px; + margin-left: 10px; +} +.l-transaction-complete { + background-color: #5CB85C; +} +.l-transaction-progress { + background-color: rgb(91, 192, 222); +} +.l-transaction-error { + background-color: #D9534F; +} +.l-log-info.disable { + color: #BBBABA; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu.component.html new file mode 100644 index 000000000000..3bad8e5d42bf --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu.component.html @@ -0,0 +1,10 @@ +
+
+ + + +
+ {{partInfo?.completeState}} +
diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu.component.ts new file mode 100644 index 000000000000..23e00c742ae5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-detail-menu/transaction-detail-menu.component.ts @@ -0,0 +1,59 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-transaction-detail-menu', + templateUrl: './transaction-detail-menu.component.html', + styleUrls: ['./transaction-detail-menu.component.css'] +}) + +export class TransactionDetailMenuComponent implements OnInit { + @Input() viewTypeList: object[]; + @Input() viewType: string; + @Input() partInfo: any; + @Output() outSelectViewType: EventEmitter = new EventEmitter(); + @Output() outOpenDetailView: EventEmitter = new EventEmitter(); + @Output() outOpenExtraView: EventEmitter = new EventEmitter(); + constructor() {} + ngOnInit() {} + isCurrentView(viewType: string): boolean { + return this.viewType === viewType; + } + onSelectView(viewType: string): void { + if ( this.viewType === viewType ) { + return; + } + this.outSelectViewType.next(viewType); + } + openDetailView(): void { + this.outOpenDetailView.next(); + } + hasLogView(): boolean { + return this.partInfo && this.partInfo.logLinkEnable; + } + openLogView(): void { + if (this.partInfo.loggingTransactionInfo === true ) { + this.outOpenExtraView.next({ + open: true, + url: this.partInfo.logPageUrl + }); + } else { + this.outOpenExtraView.next({ + open: false, + message: this.partInfo.disableButtonMessage + }); + } + } + hasInfo(): boolean { + return this.partInfo && !this.partInfo.loggingTransactionInfo; + } + getStateClass(): string { + if ( this.partInfo ) { + return 'l-transaction-' + this.partInfo.completeState.toLowerCase(); + } else { + return ''; + } + } + hasState(): boolean { + return !!this.partInfo; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/index.ts new file mode 100644 index 000000000000..c5afafe8738b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/index.ts @@ -0,0 +1,37 @@ + +import { NgModule } from '@angular/core'; + +import { SharedModule } from 'app/shared'; +import { TransactionShortInfoModule } from 'app/core/components/transaction-short-info'; +import { TransactionDetailMenuModule } from 'app/core/components/transaction-detail-menu'; +import { TransactionSearchModule } from 'app/core/components/transaction-search'; +import { CallTreeModule } from 'app/core/components/call-tree'; +import { ServerMapModule } from 'app/core/components/server-map'; +import { TransactionTimelineModule } from 'app/core/components/transaction-timeline'; +import { TransactionTableGridModule } from 'app/core/components/transaction-table-grid'; +import { SyntaxHighlightPopupModule } from 'app/core/components/syntax-highlight-popup'; +import { TransactionListBottomContentsContainerComponent } from './transaction-list-bottom-contents-container.component'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + TransactionListBottomContentsContainerComponent + ], + imports: [ + SharedModule, + TransactionTableGridModule, + TransactionShortInfoModule, + TransactionDetailMenuModule, + TransactionSearchModule, + ServerMapModule, + TransactionTimelineModule, + CallTreeModule, + SyntaxHighlightPopupModule, + HelpViewerPopupModule + ], + exports: [ + TransactionListBottomContentsContainerComponent + ], + providers: [] +}) +export class TransactionListBottomContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component.css new file mode 100644 index 000000000000..849ae5c5f46e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component.css @@ -0,0 +1,26 @@ +.l-content-section { + height: 100%; + position: relative; +} +.l-middle-tool-box { + background-color: #edf2f8; +} +.l-component-wrapper { + width: 100%; + height: calc(100% - 84px); + position: relative; +} +.l-middle-tool-box { + display: flex; + flex-flow: row wrap; + height: 54px; + background-color: #edf2f8; + align-items: center; + justify-content: space-between; + padding: 0 25px; + position:relative; +} +.l-middle-tool-box, .l-middle-tool-box-tip { + color:#a8acb5; + font-size: 18px +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component.html new file mode 100644 index 000000000000..c439859621b5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component.html @@ -0,0 +1,21 @@ +
+ +
+ + +
+ +   + +
+
+
+ + + +
+ + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component.ts new file mode 100644 index 000000000000..58815fd3deed --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component.ts @@ -0,0 +1,103 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +import { + StoreHelperService, + UrlRouteManagerService, + TransactionViewTypeService, VIEW_TYPE, + TransactionDetailDataService, + AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath } from 'app/shared/models'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + + +@Component({ + selector: 'pp-transaction-list-bottom-contents-container', + templateUrl: './transaction-list-bottom-contents-container.component.html', + styleUrls: ['./transaction-list-bottom-contents-container.component.css'] +}) +export class TransactionListBottomContentsContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + currentViewType: string; + transactionInfo: ITransactionMetaData; + useDisable = false; + showLoading = false; + constructor( + private storeHelperService: StoreHelperService, + private urlRouteManagerService: UrlRouteManagerService, + private transactionDetailDataService: TransactionDetailDataService, + private transactionViewTypeService: TransactionViewTypeService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.transactionViewTypeService.onChangeViewType$.subscribe((viewType: string) => { + this.currentViewType = viewType; + }); + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTransactionData(this.unsubscribe).pipe( + filter((data: ITransactionMetaData) => { + if ( data && data.agentId && data.spanId && data.traceId && data.collectorAcceptTime ) { + return true; + } + return false; + }) + ).subscribe((transactionInfo: ITransactionMetaData) => { + if (this.transactionInfo) { + this.setDisplayGuide(true); + } + this.transactionInfo = transactionInfo; + this.transactionDetailDataService.getData( + transactionInfo.agentId, + transactionInfo.spanId, + transactionInfo.traceId, + transactionInfo.collectorAcceptTime + ).subscribe((transactionDetailInfo: ITransactionDetailData) => { + this.storeHelperService.dispatch(new Actions.UpdateTransactionDetailData(transactionDetailInfo)); + this.setDisplayGuide(false); + }); + }); + } + private setDisplayGuide(state: boolean): void { + this.showLoading = state; + this.useDisable = state; + } + isSameType(type: string): boolean { + return this.currentViewType === type; + } + isCallTreeView(): boolean { + return this.currentViewType === VIEW_TYPE.CALL_TREE || this.currentViewType === VIEW_TYPE.TIMELINE; + } + onOpenTransactionDetailPage(): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_TRANSACTION_DETAIL); + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_DETAIL, + this.transactionInfo.traceId, + this.transactionInfo.collectorAcceptTime + '', + this.transactionInfo.agentId, + this.transactionInfo.spanId + ]); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.CALL_TREE); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.CALL_TREE, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-search/index.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-search/index.ts new file mode 100644 index 000000000000..ad549f27e85f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-search/index.ts @@ -0,0 +1,23 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { TransactionSearchComponent } from './transaction-search.component'; +import { TransactionSearchContainerComponent } from './transaction-search-container.component'; +import { TransactionSearchInteractionService } from './transaction-search-interaction.service'; + +@NgModule({ + declarations: [ + TransactionSearchComponent, + TransactionSearchContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + TransactionSearchContainerComponent + ], + providers: [ + TransactionSearchInteractionService + ] +}) +export class TransactionSearchModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-container.component.css new file mode 100644 index 000000000000..c3329127e37a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-container.component.css @@ -0,0 +1,3 @@ +:host { + display: flex; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-container.component.html new file mode 100644 index 000000000000..3b114adc6978 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-container.component.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-container.component.ts new file mode 100644 index 000000000000..34ce20fafd97 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-container.component.ts @@ -0,0 +1,73 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { combineLatest, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + TranslateReplaceService, + TransactionViewTypeService, VIEW_TYPE, + AnalyticsService, TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { TransactionSearchInteractionService, ISearchParam } from './transaction-search-interaction.service'; + +@Component({ + selector: 'pp-transaction-search-container', + templateUrl: './transaction-search-container.component.html', + styleUrls: ['./transaction-search-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransactionSearchContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private i18nText: { [key: string]: string } = { + HAS_RESULTS: '', + EMPTY_RESULT: '' + }; + currentViewType: string; + useArgument: boolean; + resultMessage: string; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private transactionSearchInteractionService: TransactionSearchInteractionService, + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + private transactionViewTypeService: TransactionViewTypeService, + private analyticsService: AnalyticsService, + ) {} + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + ngOnInit() { + this.getI18NText(); + this.transactionSearchInteractionService.onSearchResult$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((result: any) => { + const resultMessage = result.result === 0 ? this.i18nText.EMPTY_RESULT : this.i18nText.HAS_RESULTS; + this.resultMessage = this.translateReplaceService.replace(resultMessage, result.result); + this.changeDetectorRef.detectChanges(); + }); + this.transactionViewTypeService.onChangeViewType$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((viewType: string) => { + this.currentViewType = viewType; + this.useArgument = viewType === VIEW_TYPE.TIMELINE ? false : true; + this.changeDetectorRef.detectChanges(); + }); + } + private getI18NText(): void { + combineLatest( + this.translateService.get('TRANSACTION.HAS_RESULTS'), + this.translateService.get('TRANSACTION.EMPTY_RESULT') + ).subscribe((i18n: string[]) => { + this.i18nText.HAS_RESULTS = i18n[0]; + this.i18nText.EMPTY_RESULT = i18n[1]; + }); + } + onSearch(params: ISearchParam): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SEARCH_TRANSACTION, `Search Type: ${params.type}`); + this.transactionSearchInteractionService.setSearchParmas({ + type: params.type, + query: params.query === 'self' ? +params.query : params.query + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-interaction.service.ts new file mode 100644 index 000000000000..5ea90f9494ce --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search-interaction.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; + +export interface ISearchParam { + type: string; + query: string | number; +} + +@Injectable() +export class TransactionSearchInteractionService { + private search = new Subject(); + private searchResult = new Subject(); + private moveRow = new Subject(); + + onSearch$: Observable; + onSearchResult$: Observable; + onMoveRow$: Observable; + + constructor() { + this.onSearch$ = this.search.asObservable(); + this.onSearchResult$ = this.searchResult.asObservable(); + this.onMoveRow$ = this.moveRow.asObservable(); + } + setSearchParmas(params: ISearchParam): void { + this.search.next(params); + } + setSearchResult(result: any): void { + this.searchResult.next(result); + } + setRow(id: string): void { + this.moveRow.next(id); + } +} + diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search.component.css new file mode 100644 index 000000000000..17de9d4033e7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search.component.css @@ -0,0 +1,60 @@ +:host { + display: flex; +} +.l-search-group-wrap { + display: flex; + flex-flow: row wrap; + padding: 0 10px; + font-size: 13px; + justify-content: flex-end; + position:relative; +} +.l-search-group-wrap .fas { + color:#a8acb5; + font-size:18px; +} +.l-search-group-wrap .l-search-group2 { + height:32px; +} +.l-search-group-wrap .l-search-group2 input { + float: left; + display: inline-block; + height: 100%; + border: 1px solid #4a8fd2; + padding:0 10px; + background:#fff; +} +.l-search-group-wrap button { + margin-left: 10px; +} +.l-search-group-wrap .l-error-message { + width: 300px; + color:#e95459; + margin-left: 10px; +} +select { + appearance: none; + -webkit-appearance: none; + outline: 0; + border-radius: 0px; + color: #FFF; + float: left; + height: 100%; + display: inline-block; + padding: 7px 10px; + font-size: 13px; + font-weight: 600; + background-color: #4A8FD2; + width: 100px; +} +.l-search-group-wrap .l-search-group2 .fa-angle-down { + position: absolute; + color: #FFF; + top: 8px; + left: 94px; + font-size: 15px; +} +.l-error-message { + margin-left: 10px; + margin-top: 8px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search.component.html new file mode 100644 index 000000000000..e8dfa4f79a7e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search.component.html @@ -0,0 +1,13 @@ +
+
+ + + +
+ + {{resultMessage}} +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search.component.ts new file mode 100644 index 000000000000..47e65fc8ea93 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-search/transaction-search.component.ts @@ -0,0 +1,37 @@ +import { Component, Renderer2, OnInit, OnChanges, SimpleChanges, Output, Input, EventEmitter, ViewChild, ElementRef } from '@angular/core'; + +@Component({ + selector: 'pp-transaction-search', + templateUrl: './transaction-search.component.html', + styleUrls: ['./transaction-search.component.css'] +}) +export class TransactionSearchComponent implements OnInit, OnChanges { + @ViewChild('searchType') searchType: ElementRef; + @Output() outSearch: EventEmitter<{type: string, query: string}> = new EventEmitter(); + @Input() viewType: string; + @Input() useArgument: boolean; + @Input() resultMessage = ''; + inputValue: string; + constructor(private renderer: Renderer2) { } + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if (changes['viewType'] && changes['viewType'].currentValue) { + this.onClear(); + // this.renderer.setAttribute(this.searchType.nativeElement.options[0], 'selected', 'selected'); + this.searchType.nativeElement.options[0].selected = true; + } + } + onSearch(type: string): void { + const query = this.inputValue.trim(); + if (query !== '') { + this.outSearch.emit({ + type: type, + query: query + }); + } + } + onClear() { + this.inputValue = ''; + this.resultMessage = ''; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/index.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/index.ts new file mode 100644 index 000000000000..c61c3250261c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/index.ts @@ -0,0 +1,21 @@ + +import { NgModule } from '@angular/core'; +import { MatTooltipModule } from '@angular/material'; +import { SharedModule } from 'app/shared'; +import { TransactionShortInfoContainerComponent } from './transaction-short-info-container.component'; + +@NgModule({ + declarations: [ + TransactionShortInfoContainerComponent + ], + imports: [ + MatTooltipModule, + SharedModule + ], + exports: [ + TransactionShortInfoContainerComponent, + ], + providers: [ + ] +}) +export class TransactionShortInfoModule {} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/transaction-short-info-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/transaction-short-info-container.component.css new file mode 100644 index 000000000000..66d2c542b316 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/transaction-short-info-container.component.css @@ -0,0 +1,22 @@ +.l-route { + display: flex; + flex-flow: row wrap; + background:#506078; + height: 30px; + align-items: center; + justify-content: space-around; +} +.l-route > div { + width: 25%; + color:#fff; + display: block; + position:relative; + font-size: 12px; + overflow: hidden; + white-space: nowrap; + padding-left: 6px; + text-overflow: ellipsis; +} +.l-route > div:last-child { + padding-right: 6px; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/transaction-short-info-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/transaction-short-info-container.component.html new file mode 100644 index 000000000000..c094da947429 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/transaction-short-info-container.component.html @@ -0,0 +1,6 @@ +
+
ApplicationName : {{applicationName}}
+
AgentId : {{agentId}}
+
TransactionId : {{transactionId}}
+
Path : {{path}}
+
diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/transaction-short-info-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/transaction-short-info-container.component.ts new file mode 100644 index 000000000000..4ba05fa28947 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-short-info/transaction-short-info-container.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { StoreHelperService } from 'app/shared/services'; + +@Component({ + selector: 'pp-transaction-short-info-container', + templateUrl: './transaction-short-info-container.component.html', + styleUrls: ['./transaction-short-info-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransactionShortInfoContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + path: string; + agentId: string; + transactionId: string; + applicationName: string; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService) {} + ngOnInit() { + this.connectStore(); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTransactionDetailData(this.unsubscribe).pipe( + filter((transactionDetailInfo: ITransactionDetailData) => { + return transactionDetailInfo ? true : false; + }) + ).subscribe((transactionDetailInfo: ITransactionDetailData) => { + this.path = transactionDetailInfo.applicationName; + this.agentId = transactionDetailInfo.agentId; + this.transactionId = transactionDetailInfo.transactionId; + this.applicationName = transactionDetailInfo.applicationId; + this.changeDetectorRef.detectChanges(); + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/index.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/index.ts new file mode 100644 index 000000000000..04602b816255 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/index.ts @@ -0,0 +1,28 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AgGridModule } from 'ag-grid-angular/main'; + +import { TransactionTableGridComponent } from './transaction-table-grid.component'; +import { TransactionTableGridContainerComponent } from './transaction-table-grid-container.component'; +import { TransactionMetaDataService } from './transaction-meta-data.service'; +import { MessagePopupModule } from 'app/core/components/message-popup'; + +@NgModule({ + declarations: [ + TransactionTableGridComponent, + TransactionTableGridContainerComponent + ], + imports: [ + CommonModule, + AgGridModule.withComponents([]), + MessagePopupModule + ], + exports: [ + TransactionTableGridContainerComponent + ], + providers: [ + TransactionMetaDataService + ] +}) +export class TransactionTableGridModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-meta-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-meta-data.service.ts new file mode 100644 index 000000000000..b1f86b814196 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-meta-data.service.ts @@ -0,0 +1,191 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Subject, Observable } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { + UrlRouteManagerService, + NewUrlStateNotificationService, + WindowRefService, + DynamicPopupService +} from 'app/shared/services'; +import { MessagePopupContainerComponent } from 'app/core/components/message-popup/message-popup-container.component'; + +@Injectable() +export class TransactionMetaDataService { + private unsubscribe: Subject = new Subject(); + private requestURL = 'transactionmetadata.pinpoint'; + private retrieveErrorMessage: string; + private lastFetchedIndex = 0; + private maxLoadLength = 100; + private requestSourceData: any[]; + private requestCount = 0; + private countStatus = [0, 0]; + private outTransactionDataLoad: Subject = new Subject(); + private outTransactionDataRange: Subject = new Subject(); + private outTransactionDataCount: Subject = new Subject(); + + onTransactionDataLoad$: Observable; + onTransactionDataRange$: Observable; + onTransactionDataCount$: Observable; + + constructor( + private http: HttpClient, + private windowRefService: WindowRefService, + private translateService: TranslateService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private dynamicPopupService: DynamicPopupService + ) { + this.onTransactionDataLoad$ = this.outTransactionDataLoad.asObservable(); + this.onTransactionDataRange$ = this.outTransactionDataRange.asObservable(); + this.onTransactionDataCount$ = this.outTransactionDataCount.asObservable(); + + this.translateService.get('TRANSACTION_LIST.TRANSACTION_RETRIEVE_ERROR').subscribe((text: string) => { + this.retrieveErrorMessage = text; + }); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService && urlService.hasValue(UrlPathId.APPLICATION, UrlPathId.PERIOD, UrlPathId.END_TIME); + }) + ).subscribe(() => { + this.requestSourceData = this.getInfoFromOpener(); + this.countStatus[1] = this.requestSourceData.length; + this.unsubscribe.next(); + this.unsubscribe.complete(); + }); + } + loadData(): void { + if (this.requestSourceData.length === 0) { + this.dynamicPopupService.openPopup({ + data: { + title: 'Notice', + contents: this.retrieveErrorMessage, + }, + component: MessagePopupContainerComponent, + onCloseCallback: () => { + this.urlRouteManagerService.moveOnPage({ + url: [ + UrlPath.MAIN, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ] + }); + } + }); + } else { + this.http.post<{ metadata: ITransactionMetaData[] }>(this.requestURL, this.makeRequestOptionsArgs(), { + headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') + }).subscribe((responseData: { metadata: ITransactionMetaData[] }) => { + const responseLength = responseData.metadata.length; + if (this.requestCount !== responseLength) { + if (this.requestCount > responseLength) { + this.countStatus[1] -= (this.requestCount - responseLength); + } else { + this.countStatus[1] += (responseLength - this.requestCount); + } + } + if (responseLength === 0) { + this.outFullRange(); + } else { + this.countStatus[0] += responseLength; + if (this.countStatus[0] === this.countStatus[1]) { + this.outFullRange(); + } else { + this.outTransactionDataRange.next([ + responseData.metadata[responseLength - 1].collectorAcceptTime, + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getDate().valueOf() + ]); + } + this.outTransactionDataLoad.next(responseData.metadata); + } + this.outTransactionDataCount.next(this.countStatus); + }); + } + } + moreLoad(): void { + this.loadData(); + } + private outFullRange(): void { + this.outTransactionDataRange.next([ + this.newUrlStateNotificationService.getStartTimeToNumber(), + this.newUrlStateNotificationService.getEndTimeToNumber() + ]); + } + private makeRequestOptionsArgs(): string { + const requestStr = []; + const len = this.requestSourceData.length; + const maxLen = Math.min(len, this.maxLoadLength + this.lastFetchedIndex); + for ( let index = 0, dataIndex = this.lastFetchedIndex ; dataIndex < maxLen ; index++, dataIndex++ ) { + // transactionId, x, y + requestStr.push(`I${index}=${this.requestSourceData[dataIndex][0]}`); + requestStr.push(`T${index}=${this.requestSourceData[dataIndex][1]}`); + requestStr.push(`R${index}=${this.requestSourceData[dataIndex][2]}`); + } + this.requestCount = requestStr.length / 3; + this.lastFetchedIndex = maxLen; + return requestStr.join('&'); + // load from parent window; + // 브라우저 윈도우 이름으로 아래의 정보를 넘김 + // applicationName + // oDragXY.fromX + // oDragXY.toX + // oDragXY.fromY + // oDragXY.toY + // agent + // inclue? + // Success/Failed 정보는 현재 window.opener.htoScatter['applicationName'] 에 저장해 둔 Scatter 객체가 가지고 있음. + // window.htoScatter[''] + // + // transactionList 데이터의 총합은 이전 window에서 가져옴. + // indicator의 range는 + // 가져온 데이터 중 가장 예전의 collectorAcceptTime ~ endTime + // 가져온 데이터의 갯수가 0 이거나 total 보다 커지면 끝 + // 요청 갯수와 응답 갯수가 다른 경우 total 값을 계속 update 해줘야 함. + // 10개 transaction 데이터를 요청했는데 11개 혹은 12개가 오는 케이스가 있음. + // --한번에 요청하는 갯수는 일단 최대 100개 + // const postBody = 'I0=FrontWAS2^1512710981192^11123&T0=1512718752125&R0=2&I1=FrontWAS3^1512711055472^10928&T1=1512718752105&R1=2&I2=FrontWAS2^1512710981192^11122&T2=1512718751113&R2=2&I3=FrontWAS3^1512711055472^10927&T3=1512718751092&R3=2&I4=FrontWAS1^1512711025001^3447&T4=1512718750828&R4=792&I5=FrontWAS4^1512711041898^3430&T5=1512718750448&R5=337&I6=FrontWAS4^1512711041898^3430&T6=1512718750440&R6=8&I7=FrontWAS2^1512710981192^11121&T7=1512718750103&R7=2&I8=FrontWAS3^1512711055472^10926&T8=1512718750081&R8=2&I9=FrontWAS2^1512710981192^11120&T9=1512718749819&R9=9&I10=FrontWAS2^1512710981192^11119&T10=1512718749621&R10=432&I11=FrontWAS2^1512710981192^11119&T11=1512718749617&R11=8&I12=FrontWAS3^1512711055472^10924&T12=1512718749564&R12=933&I13=FrontWAS4^1512711041898^3429&T13=1512718749558&R13=13&I14=FrontWAS2^1512710981192^11118&T14=1512718749091&R14=3&I15=FrontWAS3^1512711055472^10925&T15=1512718749070&R15=2&I16=FrontWAS2^1512710981192^11117&T16=1512718748080&R16=2&I17=FrontWAS3^1512711055472^10923&T17=1512718748059&R17=2&I18=FrontWAS2^1512710981192^11116&T18=1512718747069&R18=2&I19=FrontWAS3^1512711055472^10922&T19=1512718747047&R19=2'; + // return postBody; + } + private getInfoFromOpener(): any[] { + if (this.windowRefService.nativeWindow.opener) { + const paramsInfo = this.windowRefService.nativeWindow.name.split('|'); + if (paramsInfo.length === 7) { + const params = { + application: paramsInfo[0], + fromX: paramsInfo[1], + toX: paramsInfo[2], + fromY: paramsInfo[3], + toY: paramsInfo[4], + agent: paramsInfo[5], + types: paramsInfo[6].split(',') + }; + try { + const scatterChartInstance = this.windowRefService.nativeWindow.opener.scatterChartInstance[params.application]; + if (scatterChartInstance) { + return scatterChartInstance.getDataByRange(params.fromX, params.toX, params.fromY, params.toY, params.agent, params.types); + } + } catch (error) { + return []; + } + } + } + return this.checkUrlInfo(); + } + private checkUrlInfo(): any[] { + if (this.newUrlStateNotificationService.hasValue(UrlPathId.TRANSACTION_INFO)) { + const transactionInfo = this.newUrlStateNotificationService.getPathValue(UrlPathId.TRANSACTION_INFO).split('-'); + const length = transactionInfo.length; + return [[ + transactionInfo.slice(0, length - 2).join('-'), + transactionInfo[length - 2], + transactionInfo[length - 1] + ]]; + } + return []; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid-container.component.css new file mode 100644 index 000000000000..392058fac2cc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid-container.component.css @@ -0,0 +1,8 @@ +:host { + display: flex; + width: 100%; + height: 100%; +} +pp-transaction-table-grid { + width: 100%; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid-container.component.html new file mode 100644 index 000000000000..9ee6a177f6b0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid-container.component.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid-container.component.ts new file mode 100644 index 000000000000..f92ce7eaa258 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid-container.component.ts @@ -0,0 +1,156 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { + UrlRouteManagerService, + NewUrlStateNotificationService, + GutterEventService, + StoreHelperService, + AnalyticsService, TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { Actions } from 'app/shared/store'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { IGridData } from './transaction-table-grid.component'; +import { TransactionMetaDataService } from './transaction-meta-data.service'; + + +@Component({ + selector: 'pp-transaction-table-grid-container', + templateUrl: './transaction-table-grid-container.component.html', + styleUrls: ['./transaction-table-grid-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransactionTableGridContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + areaResized: any; + selectedTraceId: string; + transactionData: ITransactionMetaData[] = []; + transactionDataForAgGrid: IGridData[]; + transactionAddedDataForAgGrid: IGridData[]; + transactionIndex = 1; + timezone: string; + dateFormat: string; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private urlRouteManagerService: UrlRouteManagerService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private transactionMetaDataService: TransactionMetaDataService, + private gutterEventService: GutterEventService, + private analyticsService: AnalyticsService + ) {} + ngOnInit() { + this.connectStore(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.TRANSACTION_INFO)) { + this.selectedTraceId = urlService.getPathValue(UrlPathId.TRANSACTION_INFO).replace(/(.*)-\d*-\d*$/, '$1'); + this.changeDetectorRef.detectChanges(); + this.dispatchTransaction(); + } + if (this.transactionData.length === 0) { + this.transactionMetaDataService.loadData(); + } + }); + this.connectMetaDataService(); + this.gutterEventService.onGutterResized$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((params: any) => { + this.areaResized = params; + this.changeDetectorRef.detectChanges(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => { + this.timezone = timezone; + this.changeDetectorRef.detectChanges(); + }); + this.storeHelperService.getDateFormat(this.unsubscribe, 2).subscribe((dateFormat: string) => { + this.dateFormat = dateFormat; + this.changeDetectorRef.detectChanges(); + }); + } + private connectMetaDataService(): void { + this.transactionMetaDataService.onTransactionDataLoad$.pipe( + takeUntil(this.unsubscribe), + filter((responseData: any) => { + if ( responseData.length > 0 ) { + return responseData; + } + }) + ).subscribe((responseData: ITransactionMetaData[]) => { + this.transactionData = this.transactionData.concat(responseData || []); + if ( this.transactionDataForAgGrid ) { + this.transactionAddedDataForAgGrid = this.makeGridData(responseData); + this.changeDetectorRef.detectChanges(); + } else { + this.transactionDataForAgGrid = this.makeGridData(responseData); + this.changeDetectorRef.detectChanges(); + this.dispatchTransaction(); + } + }); + } + private makeGridData(transactionData: ITransactionMetaData[]): IGridData[] { + return transactionData.map((data: ITransactionMetaData) => { + return this.makeRow(data); + }); + } + private makeRow(gridData: ITransactionMetaData): IGridData { + return { + id: this.transactionIndex++, + startTime: gridData.startTime, + path: gridData.application, + responseTime: gridData.elapsed, + exception: gridData.exception, + agentId: gridData.agentId, + clientIp: gridData.remoteAddr, + traceId: gridData.traceId, + spanId: gridData.spanId, + collectorAcceptTime: gridData.collectorAcceptTime + } as IGridData; + } + private findTransaction(traceId: string): ITransactionMetaData { + for ( let i = 0 ; i < this.transactionData.length ; i++ ) { + if (this.transactionData[i].traceId === traceId) { + return this.transactionData[i]; + } + } + return null; + } + private dispatchTransaction(): void { + if ( this.selectedTraceId ) { + const transaction = this.findTransaction(this.selectedTraceId); + if ( transaction ) { + this.storeHelperService.dispatch(new Actions.UpdateTransactionData(transaction)); + } + } + } + onSelectTransaction(transactionShortInfo: { traceId: string, collectorAcceptTime: number, elapsed: number }): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_TRANSACTION); + this.urlRouteManagerService.moveOnPage({ + url: [ + UrlPath.TRANSACTION_LIST, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(), + `${transactionShortInfo.traceId}-${transactionShortInfo.collectorAcceptTime}-${transactionShortInfo.elapsed}` + ] + }); + } + onOpenTransactionView(transactionShortInfo: { agentId: string, traceId: string, collectorAcceptTime: number, spanId: string }): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.OPEN_TRANSACTION_VIEW); + this.urlRouteManagerService.openPage([ + UrlPath.TRANSACTION_VIEW, + transactionShortInfo.agentId, + transactionShortInfo.traceId, + transactionShortInfo.collectorAcceptTime + '', + transactionShortInfo.spanId, + ]); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid.component.css new file mode 100644 index 000000000000..98c57593c8c3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid.component.css @@ -0,0 +1,78 @@ +:host { + width: 100%; + height: 100%; +} +#transaction-table .ag-root { + border: none; + font-size: 12px; + font-family: 'Open Sans', sans-serif; +} +#transaction-table .ag-cell { + padding: 4px; + border-right: 1px solid #e6e8ec; + font-family: 'Open Sans', sans-serif; +} +#transaction-table .ag-column-moving .ag-cell { + transition: left 0.2s; +} +#transaction-table .ag-header-cell-moving .ag-header-cell-label { + opacity: 0; + filter: alpha(opacity=0); +} +#transaction-table .ag-header-cell-moving { + background-color: #bebebe; +} +#transaction-table .ag-header-cell-moving-clone { + border-right: 1px solid #808080; + border-left: 1px solid #808080; + background-color: rgba(220,220,220,0.8); +} +#transaction-table .ag-header { + background: #f6f8fb; + border-bottom: 1px solid #e6e8ec; + line-height: 2; +} +#transaction-table .ag-header-cell { + font-size: 12px; + font-weight: 600; + font-family: 'Open Sans', sans-serif; + border-right: 1px solid #e6e8ec; + padding-left: 2px; + padding-right: 2px; +} +#transaction-table .ag-header-cell-resize:after { + border-right: none; +} +#transaction-table .ag-header-cell-label { + padding: 4px; +} +#transaction-table .ag-group-expanded span { + margin-right: 4px; +} +#transaction-table .ag-row:hover { + background-color: #F5F5F5; +} +#transaction-table .ag-row { + cursor: pointer; + line-height: 2; + border-bottom: 1px solid #e6e8ec; +} +#transaction-table .ag-body { + background-color: #ffffff; +} +#transaction-table .ag-body-viewport { + background-color: #ffffff; +} +#transaction-table .ag-menu { + background-color: #ffffff; + border: 1px solid grey; +} +#transaction-table .fa { + font-size: 14px; +} +#transaction-table .ag-row-exception { + background-color: #fff1f1; +} +#transaction-table .ag-row-selected { + background-color: #e4f5e3; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid.component.html new file mode 100644 index 000000000000..d31fd385a61b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid.component.html @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid.component.ts new file mode 100644 index 000000000000..864a46b1cb1a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-table-grid/transaction-table-grid.component.ts @@ -0,0 +1,190 @@ +import { Component, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; +import * as moment from 'moment-timezone'; +import { GridOptions } from 'ag-grid'; + +export interface IGridData { + id: number; + startTime: number; + path: string; + responseTime: number; + exception: number; + agentId: string; + clientIp: string; + traceId: string; + spanId: string; + collectorAcceptTime: number; +} + +@Component({ + selector: 'pp-transaction-table-grid', + templateUrl: './transaction-table-grid.component.html', + styleUrls: ['./transaction-table-grid.component.css'], + encapsulation: ViewEncapsulation.None +}) +export class TransactionTableGridComponent implements OnInit, OnChanges { + gridOptions: GridOptions; + @Input() rowData: IGridData[]; + @Input() addData: IGridData[]; + @Input() resized: any; + @Input() currentTraceId: string; + @Input() timezone: string; + @Input() dateFormat: string; + @Output() outSelectTransaction: EventEmitter = new EventEmitter(); + @Output() outSelectTransactionView: EventEmitter = new EventEmitter(); + + constructor() {} + ngOnInit() { + this.initGridOptions(); + } + ngOnChanges(changes: SimpleChanges) { + if (changes['addData'] && changes['addData']['currentValue']) { + this.gridOptions.api.updateRowData({ + add: this.addData + }); + } + if (changes['resized'] && !changes['resized']['firstChange'] && changes['resized']['currentValue']) { + this.gridOptions.api.doLayout(); + } + if (changes['timezone'] && changes['timezone'].firstChange === false) { + this.gridOptions.api.refreshCells({ + columns: ['startTime'], + force: true + }); + } + if (changes['dateFormat'] && changes['dateFormat'].firstChange === false) { + this.gridOptions.api.refreshCells({ + columns: ['startTime'], + force: true + }); + } + } + private initGridOptions() { + this.gridOptions = { + rowHeight: 30, + columnDefs: this.makeColumnDefs(), + animateRows: true, + rowSelection: 'single', + headerHeight: 34, + enableSorting: true, + enableColResize: true, + getRowClass: (params: any) => { + return params.data.exception === 1 ? 'ag-row-exception' : ''; + }, + onCellClicked: (params: any) => { + if ( params.colDef.field === 'path' ) { + const tag = params.event.target.tagName.toUpperCase(); + if (tag === 'I' || tag === 'BUTTON' ) { + this.outSelectTransactionView.next({ + agentId: params.data.agentId, + traceId: params.data.traceId, + collectorAcceptTime: params.data.collectorAcceptTime, + spanId: params.data.spanId + }); + return; + } + } + if ( this.currentTraceId === params.data.traceId ) { + return; + } + this.currentTraceId = params.data.traceId; + this.outSelectTransaction.next({ + traceId: params.data.traceId, + collectorAcceptTime: params.data.collectorAcceptTime, + elapsed: params.data.responseTime + }); + } + }; + } + onGridReady(params: GridOptions): void { + this.gridOptions.api.forEachNode((node) => { + if (this.currentTraceId === node.data.traceId) { + node.setSelected(true); + } + }); + } + onGridSizeChanged(params: GridOptions): void { + this.gridOptions.api.sizeColumnsToFit(); + } + private makeColumnDefs(): any { + return [ + { + headerName: '#', + field: 'id', + width: 40, + cellStyle: () => { + return {'text-align': 'center'}; + }, + suppressSizeToFit: true + }, + { + headerName: 'StartTime', + field: 'startTime', + width: 170, + valueFormatter: (params: any) => { + return params.value === 0 ? '' : moment(params.value).tz(this.timezone).format(this.dateFormat); + }, + suppressSizeToFit: true + }, + { + headerName: 'Path', + field: 'path', + width: 370, + cellRenderer: (params: any) => { + return '  ' + params.value; + }, + tooltipField: 'path' + }, + { + headerName: 'Res(ms)', + field: 'responseTime', + width: 75, + cellStyle: this.alignRightCellStyle, + sort: 'desc', + valueFormatter: (params: any) => { + return params.value === '' ? '' : new Intl.NumberFormat().format(params.value); + }, + suppressSizeToFit: true + }, + { + headerName: 'Exception', + field: 'exception', + width: 85, + cellStyle: () => { + return {'text-align': 'center'}; + }, + cellRenderer: (params: any) => { + if ( params.value === 1 ) { + return ''; + } else { + return ''; + } + }, + suppressSizeToFit: true + }, + { + headerName: 'Agent', + field: 'agentId', + width: 170, + tooltipField: 'agentId' + }, + { + headerName: 'Client IP', + field: 'clientIp', + width: 120 + }, + { + headerName: 'Transaction', + field: 'traceId', + width: 270, + suppressSizeToFit: true, + tooltipField: 'traceId' + } + ]; + } + argumentCellStyle(): any { + return {'text-align': 'left'}; + } + alignRightCellStyle(): any { + return {'text-align': 'right'}; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/index.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/index.ts new file mode 100644 index 000000000000..063ac3df4d09 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/index.ts @@ -0,0 +1,20 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { TransactionTimelineComponent } from './transaction-timeline.component'; +import { TransactionTimelineContainerComponent } from './transaction-timeline-container.component'; + +@NgModule({ + declarations: [ + TransactionTimelineComponent, + TransactionTimelineContainerComponent + ], + imports: [ + SharedModule + ], + exports: [ + TransactionTimelineContainerComponent + ], + providers: [] +}) +export class TransactionTimelineModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline-container.component.css new file mode 100644 index 000000000000..a8962602d61a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline-container.component.css @@ -0,0 +1,6 @@ +:host { + position: relative; + width: 100%; + height: 100%; + background-color: #FFF; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline-container.component.html new file mode 100644 index 000000000000..d12702a43363 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline-container.component.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline-container.component.ts new file mode 100644 index 000000000000..118097111b9c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline-container.component.ts @@ -0,0 +1,125 @@ +import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { + StoreHelperService, + NewUrlStateNotificationService, + UrlRouteManagerService, + TransactionViewTypeService, VIEW_TYPE, + AnalyticsService, TRACKED_EVENT_LIST +} from 'app/shared/services'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { TransactionSearchInteractionService, ISearchParam } from 'app/core/components/transaction-search/transaction-search-interaction.service'; +import { TransactionTimelineComponent } from './transaction-timeline.component'; + +@Component({ + selector: 'pp-transaction-timeline-container', + templateUrl: './transaction-timeline-container.component.html', + styleUrls: ['./transaction-timeline-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class TransactionTimelineContainerComponent implements OnInit, OnDestroy { + @ViewChild(TransactionTimelineComponent) private transactionTimelineComponent: TransactionTimelineComponent; + private unsubscribe: Subject = new Subject(); + hiddenComponent = true; + keyIndex: any; + startTime: number; + endTime: number; + filteredData: any; + barRatio: number; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private urlRouteManagerService: UrlRouteManagerService, + private transactionViewTypeService: TransactionViewTypeService, + private transactionSearchInteractionService: TransactionSearchInteractionService, + private analyticsService: AnalyticsService + ) {} + ngOnInit() { + this.connectStore(); + this.transactionViewTypeService.onChangeViewType$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((viewType: string) => { + if ( viewType === VIEW_TYPE.TIMELINE ) { + this.hiddenComponent = false; + } else { + this.hiddenComponent = true; + } + this.changeDetectorRef.detectChanges(); + }); + this.transactionSearchInteractionService.onSearch$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((params: ISearchParam) => { + if (this.hiddenComponent === true) { + return; + } + this.transactionSearchInteractionService.setSearchResult({ + type: params.type, + query: params.query, + result: this.transactionTimelineComponent.searchRow(params) + }); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private connectStore(): void { + this.storeHelperService.getTransactionDetailData(this.unsubscribe).pipe( + filter((transactionDetailInfo: any) => { + return transactionDetailInfo && transactionDetailInfo.transactionId ? true : false; + }) + ).subscribe((transactionDetailInfo: ITransactionDetailData) => { + this.startTime = transactionDetailInfo.callStackStart; + this.endTime = transactionDetailInfo.callStackEnd; + this.keyIndex = transactionDetailInfo.callStackIndex; + this.barRatio = this.getBarRatio(transactionDetailInfo); + this.filteredData = this.filterCallStack(transactionDetailInfo); + this.changeDetectorRef.detectChanges(); + + }); + } + private getBarRatio(tInfo: ITransactionDetailData): number { + return 1000 / (tInfo.callStack[0][tInfo.callStackIndex.end] - tInfo.callStack[0][tInfo.callStackIndex.begin]); + } + private filterCallStack(rowData: ITransactionDetailData): any { + const newCallStacks: any = []; + rowData.callStack.forEach((call: any) => { + if (call[this.keyIndex.isMethod] && !call[this.keyIndex.excludeFromTimeline] && call[this.keyIndex.service] !== '') { + newCallStacks.push(call); + } + }); + return newCallStacks; + } + onSelectTransaction(id: string): void { + if (this.newUrlStateNotificationService.getStartPath() === UrlPath.TRANSACTION_DETAIL) { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_TRANSACTION_IN_TIMELINE); + this.urlRouteManagerService.moveOnPage({ + url: [ + UrlPath.TRANSACTION_DETAIL, + this.newUrlStateNotificationService.getPathValue(UrlPathId.TRACE_ID), + this.newUrlStateNotificationService.getPathValue(UrlPathId.FOCUS_TIMESTAMP), + this.newUrlStateNotificationService.getPathValue(UrlPathId.AGENT_ID), + this.newUrlStateNotificationService.getPathValue(UrlPathId.SPAN_ID), + VIEW_TYPE.CALL_TREE, + id + ] + }); + } else { + this.urlRouteManagerService.moveOnPage({ + url: [ + UrlPath.TRANSACTION_LIST, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME), + this.newUrlStateNotificationService.getPathValue(UrlPathId.TRANSACTION_INFO), + VIEW_TYPE.CALL_TREE, + id + ] + }); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline.component.css new file mode 100644 index 000000000000..c160df1dc113 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline.component.css @@ -0,0 +1,50 @@ +.l-wrapper { + height: 100%; + overflow-y: auto; + background-color: #FFF; +} +.l-timeline-bar { + width: 100%; + height: 20px; + font-size: 11px; + font-family: Verdana; + border-style: dashed !important; + border-bottom: 1px solid #D3D3D3; + text-shadow: 1px 1px 0 #eee, 1px -1px 0 #eee, -1px 1px 0 #eee, -1px -1px 0 #eee, 1px 0px 0 #eee, 0px 1px 0 #eee, -1px 0px 0 #eee, 0px -1px 0 #eee; +} +.l-timeline-bar > div { + color: #000; + cursor: pointer; + margin-top: 1px; + padding-top: 2px; + margin-bottom: 1px; + padding-bottom: 2px; +} +.l-timeline-bar-frame { + width: 100%; + overflow-x: visible; + padding-left: 4px; +} +.l-timeline-bar-frame:hover { + box-shadow: 1px 1px 1px -1px rgba(0, 0, 0, 0.75); + font-weight: bold; +} +.l-timeline-bar-frame > span { + top: 0px; + position: relative; + white-space: nowrap; +} +.l-timeline-bar-frame span.before { + left: -70px; + display: none; + position: absolute; + text-align: right; + white-space: nowrap; +} +.l-timeline-bar-frame span.after { + display: none; + white-space: nowrap; +} +.l-timeline-bar-selected { + background-color: orange !important; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline.component.html new file mode 100644 index 000000000000..dab49abe5292 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline.component.html @@ -0,0 +1,14 @@ +
+
+
+
+ + {{getStartTime(call)}}ms + [{{call[keyIndex.applicationName]}}] / {{call[keyIndex.apiType]}} ({{call[keyIndex.end] - call[keyIndex.begin]}}ms) + / {{call[keyIndex.apiType]}} ({{call[keyIndex.end] - call[keyIndex.begin]}}ms) + ( {{getStartTime(call)}}ms ) + +
+
+
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline.component.ts new file mode 100644 index 000000000000..96dd4e838779 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-timeline/transaction-timeline.component.ts @@ -0,0 +1,102 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'pp-transaction-timeline', + templateUrl: './transaction-timeline.component.html', + styleUrls: ['./transaction-timeline.component.css'] +}) + +export class TransactionTimelineComponent implements OnInit { + @Input() data: any[]; + @Input() keyIndex: { [key: string]: number}; + @Input() startTime: number; + @Input() endTime: number; + @Input() barRatio: number; + @Output() outSelectTransaction: EventEmitter = new EventEmitter(); + + selectedRow: number[] = []; + colorSet: { [key: string]: string } = {}; + constructor() {} + ngOnInit() {} + private calcColor(str: string): string { + if (!(str in this.colorSet)) { + const color = []; + let hash = 0; + for ( let i = 0 ; i < str.length ; i++ ) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + for ( let i = 0 ; i < 3 ; i++ ) { + color.push(('00' + ((hash >> i * 8) & 0xFF).toString(16)).slice(-2)); + } + this.colorSet[str] = color.map((v: string) => { + return parseInt(v, 16); + }).join(','); + } + return this.colorSet[str]; + } + private getWidth(call: any): number { + return ((call[this.keyIndex.end] - call[this.keyIndex.begin]) * this.barRatio) + 0.9; + } + getLineStyle(call: any): object { + return { + 'background-color': 'rgba(' + this.calcColor(call[this.keyIndex.applicationName]) + ', 0.1)', + }; + } + getStyles(call: any): object { + return { + 'width': this.getWidth(call) + 'px', + 'background-color': 'rgb(' + this.calcColor(call[this.keyIndex.applicationName]) + ')', + 'margin-left': this.getMarginLeft(call) + 'px' + }; + } + getTop(index: number): number { + return index * 21; + } + isSelectedRow(index: number): boolean { + return this.selectedRow.indexOf(index) !== -1; + } + getStartTime(call: any): number { + return call[this.keyIndex.begin] - this.startTime; + } + + getMarginLeft(call: any): number { + return ((call[this.keyIndex.begin] - this.startTime) * this.barRatio) + 0.9; + } + onSelectCall(index: number, call: any): void { + this.outSelectTransaction.emit(call[this.keyIndex.id]); + } + searchRow({type, query}: {type: string, query: string | number}): number { + let resultCount = 0; + this.selectedRow = []; + const fnCompare = { + 'self': (data: any, value: string): boolean => { + return data[this.keyIndex.executionMilliseconds] >= value; + }, + 'argument': (data: any, value: number): boolean => { + return data[this.keyIndex.arguments].indexOf(value) !== -1; + } + }; + this.data.forEach((call: any, index: number) => { + if (fnCompare[type](call, query)) { + resultCount++; + this.selectedRow.push(index); + } + }); + if (resultCount > 0) { + // move row + } + return resultCount; + } + showApplicationName(call: any, index: number): boolean { + if (index === 0) { + return true; + } else { + if (this.data[index - 1][this.keyIndex.applicationName] === call[this.keyIndex.applicationName]) { + return false; + } else { + return true; + } + } + } + onScrollDown() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/index.ts new file mode 100644 index 000000000000..58df3c301061 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/index.ts @@ -0,0 +1,32 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { TransactionShortInfoModule } from 'app/core/components/transaction-short-info'; +import { TransactionDetailMenuModule } from 'app/core/components/transaction-detail-menu'; +import { TransactionSearchModule } from 'app/core/components/transaction-search'; +import { CallTreeModule } from 'app/core/components/call-tree'; +import { ServerMapModule } from 'app/core/components/server-map'; +import { TransactionTimelineModule } from 'app/core/components/transaction-timeline'; +import { SyntaxHighlightPopupModule } from 'app/core/components/syntax-highlight-popup'; +import { TransactionViewBottomContentsContainerComponent } from './transaction-view-bottom-contents-container.component'; + +@NgModule({ + declarations: [ + TransactionViewBottomContentsContainerComponent + ], + imports: [ + SharedModule, + TransactionShortInfoModule, + TransactionDetailMenuModule, + TransactionSearchModule, + ServerMapModule, + TransactionTimelineModule, + CallTreeModule, + SyntaxHighlightPopupModule + ], + exports: [ + TransactionViewBottomContentsContainerComponent + ], + providers: [] +}) +export class TransactionViewBottomContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component.css new file mode 100644 index 000000000000..57778b22a899 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component.css @@ -0,0 +1,5 @@ +.l-wrapper { + width: 100%; + height: calc(100% - 30px); + position: relative; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component.html new file mode 100644 index 000000000000..716743ea91ec --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component.html @@ -0,0 +1,5 @@ +
+ +
diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component.ts new file mode 100644 index 000000000000..1bb50aa04e27 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component.ts @@ -0,0 +1,11 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'pp-transaction-view-bottom-contents-container', + templateUrl: './transaction-view-bottom-contents-container.component.html', + styleUrls: ['./transaction-view-bottom-contents-container.component.css'] +}) +export class TransactionViewBottomContentsContainerComponent implements OnInit { + constructor() {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/index.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/index.ts new file mode 100644 index 000000000000..4118d5a531b8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/index.ts @@ -0,0 +1,22 @@ + +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { InspectorChartModule } from 'app/core/components/inspector-chart'; +import { ServerMapModule } from 'app/core/components/server-map'; +import { TransactionViewTopContentsContainerComponent } from './transaction-view-top-contents-container.component'; + +@NgModule({ + declarations: [ + TransactionViewTopContentsContainerComponent + ], + imports: [ + SharedModule, + InspectorChartModule, + ServerMapModule + ], + exports: [ + TransactionViewTopContentsContainerComponent + ], + providers: [] +}) +export class TransactionViewTopContentsModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component.css b/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component.css new file mode 100644 index 000000000000..a43561ac9715 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component.css @@ -0,0 +1,55 @@ +:host { + display: block; + width: 100%; + height: 100%; +} +.l-transaction-view-top { + height: 100%; + display: flex; +} +.l-left-section, .l-right-section { + width: 50%; + height: 100%; + padding: 5px 25px +} +.l-left-section { + background-color: #fff; +} +.l-tab-menu-list { + border-bottom: 1px solid #e6e8ec; +} +.l-tab-menu-list:after { + content: ""; + display: block; + width: 100%; + height: 0; + visibility: hidden; + clear: both; +} +.l-tab-menu-list-item { + float: left; +} +.l-tab-menu-list-item a { + cursor: pointer; + display:inline-block; + font-size: 13px; + color: #666; + padding: 14px 15px; + border: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + line-height: 1em; +} +.l-tab-menu-list-item:hover a { + border-color: #e6e8ec #e6e8ec #fff; +} +.active a { + color: #fff; + background: #4a8fd2; +} +.active a:hover { + border-color: transparent; +} +.l-right-section { + position: relative; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component.html b/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component.html new file mode 100644 index 000000000000..3473a8ce1d5c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component.html @@ -0,0 +1,11 @@ +
+ +
+ +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component.ts new file mode 100644 index 000000000000..d50192d0957c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component.ts @@ -0,0 +1,79 @@ +import { Component, OnInit, OnDestroy, ComponentFactoryResolver, ViewChild, ViewContainerRef, ComponentRef, ChangeDetectionStrategy } from '@angular/core'; + +import { TransactionViewJVMHeapChartContainerComponent } from 'app/core/components/inspector-chart/transaction-view-jvm-heap-chart-container.component'; +import { TransactionViewJVMNonHeapChartContainerComponent } from 'app/core/components/inspector-chart/transaction-view-jvm-non-heap-chart-container.component'; +import { TransactionViewCPUChartContainerComponent } from 'app/core/components/inspector-chart/transaction-view-cpu-chart-container.component'; +import { AnalyticsService, TRACKED_EVENT_LIST } from 'app/shared/services'; + +@Component({ + selector: 'pp-transaction-view-top-contents-container', + templateUrl: './transaction-view-top-contents-container.component.html', + styleUrls: ['./transaction-view-top-contents-container.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransactionViewTopContentsContainerComponent implements OnInit, OnDestroy { + @ViewChild('chartContainer', { read: ViewContainerRef }) chartContainer: ViewContainerRef; + + tabList: any[]; + private componentMap = new Map(); + private chartComponentList = [TransactionViewJVMHeapChartContainerComponent, TransactionViewJVMNonHeapChartContainerComponent, TransactionViewCPUChartContainerComponent]; + private aliveComponentRef: ComponentRef; + + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private analyticsService: AnalyticsService + ) {} + + ngOnInit() { + this.initTabList(); + this.initComponentMap(); + this.loadComponent(this.tabList.find((tab) => tab.isActive).id); + } + + ngOnDestroy() { + this.aliveComponentRef.destroy(); + } + + onTabClick(tabName: string): void { + this.setActiveTab(tabName); + this.clearContainer(); + this.loadComponent(tabName); + } + + private clearContainer(): void { + this.chartContainer.clear(); + } + + private initTabList(): void { + this.tabList = [{ + id: 'heap', + displayText: 'Heap', + isActive: true + }, + { + id: 'nonHeap', + displayText: 'Non Heap', + isActive: false + }, + { + id: 'cpu', + displayText: 'CPU Load', + isActive: false + }]; + } + + private initComponentMap(): void { + this.tabList.forEach((value, i) => this.componentMap.set(value.id, this.chartComponentList[i])); + } + + private loadComponent(key: string): void { + this.analyticsService.trackEvent((TRACKED_EVENT_LIST as any)[`CLICK_${key}`]); + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.componentMap.get(key)); + + this.aliveComponentRef = this.chartContainer.createComponent(componentFactory); + } + + private setActiveTab(tabName: string): void { + this.tabList.forEach((tab) => tab.isActive = tabName === tab.id); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/index.ts b/web/src/main/webapp/v2/src/app/core/components/user-group/index.ts new file mode 100644 index 000000000000..8f6f9408d990 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/index.ts @@ -0,0 +1,34 @@ + +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { SharedModule } from 'app/shared'; +import { UserGroupComponent } from './user-group.component'; +import { UserGroupCreateAndUpdateComponent } from './user-group-create-and-update.component'; +import { UserGroupContainerComponent } from './user-group-container.component'; +import { UserGroupInteractionService } from './user-group-interaction.service'; +import { UserGroupDataService } from './user-group-data.service'; + +@NgModule({ + declarations: [ + UserGroupComponent, + UserGroupCreateAndUpdateComponent, + UserGroupContainerComponent + ], + imports: [ + FormsModule, + ReactiveFormsModule, + SharedModule + ], + exports: [ + UserGroupContainerComponent + ], + entryComponents: [ + UserGroupComponent, + UserGroupContainerComponent + ], + providers: [ + UserGroupInteractionService, + UserGroupDataService + ] +}) +export class UserGroupModule { } diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-container.component.css b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-container.component.css new file mode 100644 index 000000000000..ad3f282c7bbb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-container.component.css @@ -0,0 +1,57 @@ +:host { + position: relative; +} +.l-user-group-wrapper { + color: #333; + border: 1px solid #e5e8f0; + height: 100%; + display: grid; + position: relative; + font-size: 13px; + font-family: 'Open Sans', sans-serif; + font-weight: 600; + grid-template-columns: auto; + grid-template-rows: 48px 53px 416px; + +} +.l-user-group-title { + display: flex; + padding: 0px 15px; + align-items: center; + justify-content: space-between; + background-color: #f6f8fb; + border-bottom: 1px solid #e5e8f0; +} +.l-user-group-search { + padding: 10px 15px; + border-bottom: 1px solid #e5e8f0; +} +.l-user-group-list { + overflow-y: auto; +} +.l-message { + width: 100%; + height: 100%; + z-index: 15; + display: flex; + position: absolute; + align-items: center; + justify-content: center; + background-color: rgba(226, 226, 226, 0.8); +} +.l-message span { + color: #ff8c00; + text-align: center; +} +.l-message button { + top: 0px; + right: 0px; + position: absolute; +} +.l-search-input { + width: 100%; + color: #b3b3b4; + border: 1px solid #469ae4; + padding: 6px 11px; + font-size: 13px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-container.component.html b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-container.component.html new file mode 100644 index 000000000000..6f0fb2b2b5a0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-container.component.html @@ -0,0 +1,35 @@ +
+
+ User Group ({{userGroupList.length}}) + +
+ +
+ +
+
+ + {{message}} +
+ + + +
diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-container.component.ts b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-container.component.ts new file mode 100644 index 000000000000..3b65babe3a05 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-container.component.ts @@ -0,0 +1,139 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject, combineLatest } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { WebAppSettingDataService, TranslateReplaceService } from 'app/shared/services'; +import { UserGroupInteractionService } from './user-group-interaction.service'; +import { UserGroupDataService, IUserGroup, IUserGroupCreated, IUserGroupDeleted } from './user-group-data.service'; + +@Component({ + selector: 'pp-user-group-container', + templateUrl: './user-group-container.component.html', + styleUrls: ['./user-group-container.component.css'] +}) +export class UserGroupContainerComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + private searchQuery = ''; + i18nText: { [key: string]: string } = { + NAME_LABEL: '', + USER_GROUP_NAME_REQUIRED: '', + USER_GROUP_NAME_MIN_LENGTH: '', + USER_GROUP_SERACH_MIN_LENGTH: '' + }; + USER_GROUP_NAME_MIN_LENGTH = 3; + SEARCH_MIN_LENGTH = 2; + searchUseEnter = false; + userGroupList: IUserGroup[] = []; + useDisable = true; + showLoading = true; + showCreate = false; + message = ''; + selectedUserGroupId = ''; + constructor( + private webAppSettingDataService: WebAppSettingDataService, + private userGroupDataService: UserGroupDataService, + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + private userGroupInteractionService: UserGroupInteractionService + ) {} + ngOnInit() { + this.getI18NText(); + this.webAppSettingDataService.getUserId().subscribe((userId: string = '') => { + this.getUserGroupList(userId === '' ? null : { + userId: userId + }); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private getI18NText(): void { + combineLatest( + this.translateService.get('COMMON.MIN_LENGTH'), + this.translateService.get('COMMON.REQUIRED'), + this.translateService.get('CONFIGURATION.COMMON.NAME') + ).subscribe((i18n: string[]) => { + this.i18nText.USER_GROUP_NAME_MIN_LENGTH = this.translateReplaceService.replace(i18n[0], this.USER_GROUP_NAME_MIN_LENGTH); + this.i18nText.USER_GROUP_SEARCH_MIN_LENGTH = this.translateReplaceService.replace(i18n[0], this.SEARCH_MIN_LENGTH); + this.i18nText.USER_GROUP_NAME_REQUIRED = this.translateReplaceService.replace(i18n[1], i18n[2]); + this.i18nText.NAME_LABEL = i18n[2]; + }); + } + private getUserGroupList(param: any): void { + this.userGroupDataService.retrieve(param).subscribe((userGroupData: IUserGroup[]) => { + this.userGroupList = userGroupData; + this.hideProcessing(); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + } + private makeUserGroupQuery(userId: string): any { + return this.searchQuery === '' ? { + userId: userId + } : { + userGroupId: this.searchQuery + }; + } + onRemoveUserGroup(id: string): void { + this.showProcessing(); + this.webAppSettingDataService.getUserId().subscribe((userId: string = '') => { + this.userGroupDataService.remove(id, userId).subscribe((response: IUserGroupDeleted) => { + if (response.result === 'SUCCESS') { + this.userGroupInteractionService.setSelectedUserGroup(''); + this.getUserGroupList(this.makeUserGroupQuery(userId)); + } else { + this.hideProcessing(); + } + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + }); + } + onCreateUserGroup(newUserGroupName: string): void { + this.showProcessing(); + this.webAppSettingDataService.getUserId().subscribe((userId: string = '') => { + this.userGroupDataService.create(newUserGroupName, userId).subscribe((userGroupData: IUserGroupCreated) => { + this.userGroupList.push({ + id: newUserGroupName, + number: userGroupData.number + }); + this.hideProcessing(); + }, (error: string) => { + this.hideProcessing(); + this.message = error; + }); + }); + } + onCloseCreateUserPopup(): void { + this.showCreate = false; + } + onShowCreateUserPopup(): void { + this.showCreate = true; + } + hasMessage(): boolean { + return this.message !== ''; + } + onSelectUserGroup(userGroupId: string): void { + this.selectedUserGroupId = userGroupId; + this.userGroupInteractionService.setSelectedUserGroup(userGroupId); + } + onCloseMessage(): void { + this.message = ''; + } + onSearch(query: string): void { + this.searchQuery = query; + this.webAppSettingDataService.getUserId().subscribe((userId: string = '') => { + this.getUserGroupList(this.makeUserGroupQuery(userId)); + }); + } + private showProcessing(): void { + this.useDisable = true; + this.showLoading = true; + } + private hideProcessing(): void { + this.useDisable = false; + this.showLoading = false; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-create-and-update.component.css b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-create-and-update.component.css new file mode 100644 index 000000000000..7cfb2acc6531 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-create-and-update.component.css @@ -0,0 +1,50 @@ +.l-wrapper { + top: 0px; + left: 0px; + width: 100%; + height: 100%; + z-index: 15; + display: flex; + padding: 0px 20px; + position: absolute; + align-items: center; + flex-direction: column; + justify-content: center; + background-color: rgba(226, 226, 226, 0.9); +} +.l-wrapper h1 { + margin-bottom: 6px; +} +.l-wrapper form { + width: 100%; +} +.l-wrapper input { + width: 100%; + border: 1px solid #469ae4; + padding: 6px 11px; + font-size: 13px; + margin-bottom: 2px; + background-color: #FFF; +} +.l-wrapper .l-create { + width: 100%; + margin-top: 6px; +} +.l-wrapper .l-close { + top: 0px; + right: 0px; + position: absolute; +} +.l-wrapper .l-alert { + color: #FFF; + padding: 4px; + margin-bottom: 4px; + background-color: #000; +} +.ng-valid[required], .ng-valid.required { + border-left: 5px solid #42A948 !important; +} + +.ng-invalid:not(form) { + border-left: 5px solid #a94442 !important; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-create-and-update.component.html b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-create-and-update.component.html new file mode 100644 index 000000000000..ca4fb51ada9a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-create-and-update.component.html @@ -0,0 +1,15 @@ +
+ +

{{title}}

+
+
+ + +
+
{{nameGuide}}
+
{{nameLengthGuide}}
+
+
+ +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-create-and-update.component.ts b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-create-and-update.component.ts new file mode 100644 index 000000000000..3d58be7d835c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-create-and-update.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, AfterViewChecked, Input, Output, EventEmitter } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; + +@Component({ + selector: 'pp-user-group-create-and-update', + templateUrl: './user-group-create-and-update.component.html', + styleUrls: ['./user-group-create-and-update.component.css'] +}) +export class UserGroupCreateAndUpdateComponent implements OnInit, AfterViewChecked { + @Input() showCreate = false; + @Input() minLength: number; + @Input() nameLabel: string; + @Input() nameGuide: string; + @Input() nameLengthGuide: string; + @Output() outCreateUserGroup: EventEmitter = new EventEmitter(); + @Output() outClose: EventEmitter = new EventEmitter(); + newUserGroupModel = ''; + userGroupForm: FormGroup; + title = 'User Group'; + constructor() {} + ngOnInit() { + this.userGroupForm = new FormGroup({ + 'userGroupName': new FormControl(this.newUserGroupModel, [ + Validators.required, + Validators.minLength(3) + ]) + }); + } + ngAfterViewChecked() {} + onCreateOrUpdate(): void { + this.outCreateUserGroup.emit(this.userGroupForm.get('userGroupName').value); + this.onClose(); + } + onClose(): void { + this.outClose.emit(); + this.userGroupForm.reset(); + } + get userGroupName() { + return this.userGroupForm.get('userGroupName'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-data.service.ts b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-data.service.ts new file mode 100644 index 000000000000..87184a37fd74 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-data.service.ts @@ -0,0 +1,67 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, tap } from 'rxjs/operators'; + +export interface IUserGroup { + id: string; + number: string; +} +export interface IUserGroupCreated { + number: string; +} +export interface IUserGroupDeleted { + result: string; +} + +@Injectable() +export class UserGroupDataService { + url = 'userGroup.pinpoint'; + constructor(private http: HttpClient) { } + retrieve(param?: any): Observable { + return this.http.get(this.url, this.makeRequestOptionsArgs(param)).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + create(id: string, userId: string): Observable { + return this.http.post(this.url, this.makeCreateRemoveOptionsArgs(id, userId)).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + remove(id: string, userId: string): Observable { + return this.http.request('delete', this.url, { + body: this.makeCreateRemoveOptionsArgs(id, userId) + }).pipe( + tap((data: any) => { + if (data.errorCode) { + throw data.errorMessage; + } + }), + catchError(this.handleError) + ); + } + private handleError(error: HttpErrorResponse) { + return throwError(error.statusText || error); + } + private makeRequestOptionsArgs(param?: any): object { + return param ? { + params: param + } : {}; + } + private makeCreateRemoveOptionsArgs(id: string, userId: string): object { + return { + id: id, + userId: userId + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-interaction.service.ts b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-interaction.service.ts new file mode 100644 index 000000000000..0b3269a50bb9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group-interaction.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; + +@Injectable() +export class UserGroupInteractionService { + private outSelect = new Subject(); + onSelect$: Observable; + + constructor() { + this.onSelect$ = this.outSelect.asObservable(); + } + setSelectedUserGroup(id: string): void { + this.outSelect.next(id); + } +} + diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group.component.css b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group.component.css new file mode 100644 index 000000000000..3d5b434688a4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group.component.css @@ -0,0 +1,31 @@ +li { + color: #333; + height: 28px; + margin: 0; + cursor: pointer; + padding: 6px 15px 0px 15px; + font-size: 13px; +} +li:hover { + background:#fff0f0; +} +li.selected { + background:#fff0f0; +} +.l-item-wrapper { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.l-item-wrapper > div { + float: right; +} +.l-item-wrapper .fa-trash-alt { + color: #b3b6bf; + font-size: 14px; +} +.l-item-wrapper .fa-check { + color: #F00; + margin-left: 10px; +} diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group.component.html b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group.component.html new file mode 100644 index 000000000000..712b23f8bb0a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group.component.html @@ -0,0 +1,12 @@ +
    +
  • +
    +
    + + + +
    + {{group.id}} +
    +
  • +
diff --git a/web/src/main/webapp/v2/src/app/core/components/user-group/user-group.component.ts b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group.component.ts new file mode 100644 index 000000000000..930e8188ab19 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/components/user-group/user-group.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { IUserGroup } from './user-group-data.service'; + +@Component({ + selector: 'pp-user-group', + templateUrl: './user-group.component.html', + styleUrls: ['./user-group.component.css'] +}) +export class UserGroupComponent implements OnInit { + @Input() groupList: IUserGroup[]; + @Output() outRemove: EventEmitter = new EventEmitter(); + @Output() outSelected: EventEmitter = new EventEmitter(); + private removeConformId = ''; + private selectedUserGroup: string; + constructor() {} + ngOnInit() {} + onRemove(id: string): void { + this.removeConformId = id; + } + onSelect($event: MouseEvent, userGroup: IUserGroup): void { + if ($event.target['tagName'].toLowerCase() === 'button') { + return; + } + if (this.selectedUserGroup !== userGroup.id) { + this.selectedUserGroup = userGroup.id; + this.outSelected.emit(this.selectedUserGroup); + } + } + onCancelRemove(): void { + this.removeConformId = ''; + } + onConfirmRemove(): void { + this.outRemove.emit(this.removeConformId); + this.removeConformId = ''; + } + isRemoveTarget(id: string): boolean { + return this.removeConformId === id; + } + isSelected(id: string): boolean { + return this.selectedUserGroup === id; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/httpInterceptor/index.ts b/web/src/main/webapp/v2/src/app/core/httpInterceptor/index.ts new file mode 100644 index 000000000000..200ca4b3c85d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/httpInterceptor/index.ts @@ -0,0 +1,7 @@ +import { HTTP_INTERCEPTORS } from '@angular/common/http'; + +import { MarkingInterceptor } from './marking-interceptor'; + +export const httpInterceptorProviders = [ + { provide: HTTP_INTERCEPTORS, useClass: MarkingInterceptor, multi: true } +]; diff --git a/web/src/main/webapp/v2/src/app/core/httpInterceptor/marking-interceptor.ts b/web/src/main/webapp/v2/src/app/core/httpInterceptor/marking-interceptor.ts new file mode 100644 index 000000000000..97dff8287525 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/httpInterceptor/marking-interceptor.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http'; + +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +const validUrlList: string[] = []; +const urlPrefix = ''; + +@Injectable() +export class MarkingInterceptor implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + let patchedReq = req; + if (/.*\.pinpoint(ws)?$/.test(req.url)) { + patchedReq = req.clone({ + url: urlPrefix + req.url + }); + } + return next.handle(patchedReq).pipe( + map((event => { + if (event instanceof HttpResponse) { + if (!(event.body instanceof Array)) { + if (this.isValidUrl(event.url)) { + event = event.clone({ + body: { ...event.body, 'pinpointPageBaseApi': true } + }); + } + } + } + return event; + })) + ); + } + private isValidUrl(url: string): boolean { + const index = validUrlList.findIndex((validUrl: string) => { + return validUrl.indexOf(url) !== -1; + }); + return index >= 0 ? true : false; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/models/application.ts b/web/src/main/webapp/v2/src/app/core/models/application.ts new file mode 100644 index 000000000000..d261d9f43a32 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/models/application.ts @@ -0,0 +1,30 @@ +export class Application implements IApplication { + constructor( + public applicationName: string, + public serviceType: string, + public code: number, + public key?: string + ) {} + equals(target: IApplication): boolean { + if ( target ) { + return this.applicationName === target.applicationName && this.serviceType === target.serviceType; + } else { + return false; + } + } + getApplicationName(): string { + return this.applicationName; + } + getServiceType(): string { + return this.serviceType; + } + getCode(): number { + return this.code; + } + getUrlStr(): string { + return `${this.applicationName}@${this.serviceType}`; + } + getKeyStr(): string { + return this.key ? this.key : `${this.applicationName}^${this.serviceType}`; + } +} diff --git a/web/src/main/webapp/v2/src/app/core/models/end-time.ts b/web/src/main/webapp/v2/src/app/core/models/end-time.ts new file mode 100644 index 000000000000..03adc925e056 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/models/end-time.ts @@ -0,0 +1,34 @@ +import * as moment from 'moment'; + +const DATE_TIME_FORMAT = 'YYYY-MM-DD-HH-mm-ss'; +export class EndTime { + public static formatDate(time: number): string { + return moment(time).format(DATE_TIME_FORMAT); + } + public static newByNumber(time: number): EndTime { + return new EndTime(EndTime.formatDate(time)); + } + constructor(private endTimeStr: string) {} + getEndTime(): string { + return this.endTimeStr; + } + getDate(): Date { + return moment(this.endTimeStr, DATE_TIME_FORMAT).toDate(); + } + getMilliSecond(): number { + return this.getDate().valueOf(); + } + calcuStartTime(minute: number): EndTime { + return new EndTime(moment(this.endTimeStr, DATE_TIME_FORMAT).subtract(minute, 'minutes').format(DATE_TIME_FORMAT)); + } + calcuNextTime(minute: number): EndTime { + return new EndTime(moment(this.endTimeStr, DATE_TIME_FORMAT).add(minute, 'minutes').format(DATE_TIME_FORMAT)); + } + equals(target: EndTime) { + if ( target ) { + return this.endTimeStr === target.getEndTime(); + } else { + return false; + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/models/filter.ts b/web/src/main/webapp/v2/src/app/core/models/filter.ts new file mode 100644 index 000000000000..406c37788388 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/models/filter.ts @@ -0,0 +1,129 @@ +export class Filter { + // paramName = fa + fromApplication = ''; + // paramName = fst + fromServiceType = ''; + // paramName = ta + toApplication = ''; + // paramName = tst + toServiceType = ''; + // paramName = ie + transactionResult: null | boolean = null; + // paramName = rf + responseFrom?: number; + // paramName = rt + responseTo?: number; + // paramName = url + urlPattern?: string; + // paramName = fan + fromAgentName?: string; + // paramName = tan + toAgentName?: string; + static instanceFromString(str: string): Filter[] { + const returnFilter: Filter[] = []; + let aFilterFromStr: any; + try { + aFilterFromStr = JSON.parse(str); + } catch (exception) { + return returnFilter; + } + + for ( let i = 0 ; i < aFilterFromStr.length ; i++ ) { + const filterFromStr = aFilterFromStr[i]; + const newFilter = new Filter( + filterFromStr.fa, + filterFromStr.fst, + filterFromStr.ta, + filterFromStr.tst, + filterFromStr.ie, + ); + if (filterFromStr.rf || filterFromStr.rf === 0) { + newFilter.setResponseFrom(filterFromStr.rf); + } + if (filterFromStr.rt) { + newFilter.setResponseTo(filterFromStr.rt); + } + if (filterFromStr.url) { + newFilter.setUrlPattern(filterFromStr.url); + } + if (filterFromStr.fan) { + newFilter.setFromAgentName(filterFromStr.fan); + } + if (filterFromStr.tan) { + newFilter.setToAgentName(filterFromStr.tan); + } + returnFilter.push(newFilter); + } + return returnFilter; + } + constructor(fa: string, fst: string, ta: string, tst: string, ie: null | boolean = null) { + this.fromApplication = fa; + this.fromServiceType = fst; + this.toApplication = ta; + this.toServiceType = tst; + this.transactionResult = ie; + } + equal(filter: Filter): boolean { + return ( + this.fromApplication === filter.fromApplication && + this.fromServiceType === filter.fromServiceType && + this.toApplication === filter.toApplication && + this.toServiceType === filter.toServiceType + ); + } + setResponseFrom(rf: number): void { + this.responseFrom = rf; + } + setResponseTo(rt: number): void { + this.responseTo = rt; + } + setUrlPattern(url: string): void { + this.urlPattern = url; + } + setFromAgentName(fan: string): void { + this.fromAgentName = fan; + } + setToAgentName(tan: string): void { + this.toAgentName = tan; + } + getToKey(): string { + return `${this.toApplication}^${this.toServiceType}`; + } + getFromKey(): string { + return `${this.fromApplication}^${this.fromServiceType}`; + } + getTransactionResultStr(): string { + if (this.transactionResult === true) { + return 'Success Only'; + } else if (this.transactionResult === false) { + return 'Failed Only'; + } + return 'Success + Failed'; + } + toString(): string { + + const param: { [key: string]: any } = { + fa: this.fromApplication, + fst: this.fromServiceType, + ta: this.toApplication, + tst: this.toServiceType, + ie: this.transactionResult + }; + if (this.responseFrom || this.responseFrom === 0) { + param['rf'] = this.responseFrom; + } + if (this.responseTo) { + param['rt'] = this.responseTo; + } + if (this.urlPattern) { + param['url'] = this.urlPattern; + } + if (this.fromAgentName) { + param['fan'] = this.fromAgentName; + } + if (this.toAgentName) { + param['tan'] = this.toAgentName; + } + return JSON.stringify(param); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/models/index.ts b/web/src/main/webapp/v2/src/app/core/models/index.ts new file mode 100644 index 000000000000..7e1400e6faa2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/models/index.ts @@ -0,0 +1,4 @@ +export * from './application'; +export * from './end-time'; +export * from './filter'; +export * from './period'; diff --git a/web/src/main/webapp/v2/src/app/core/models/period.ts b/web/src/main/webapp/v2/src/app/core/models/period.ts new file mode 100644 index 000000000000..c5a922c7df86 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/models/period.ts @@ -0,0 +1,68 @@ +const ONE_HOUR = 60; +const ONE_DAY = 1440; +const MINUTE = 'm'; +const HOUR = 'h'; +const DAY = 'd'; + +export class Period { + private viewValue: string; + public static parseToMinute(time: string): number { + const timeChar = time.substr(-1).toLowerCase(); + const yourTime = parseInt(time, 10); + switch ( timeChar ) { + case MINUTE: + return yourTime; + case HOUR: + return yourTime * ONE_HOUR; + case DAY: + return yourTime * ONE_DAY; + default: + return yourTime; + } + } + constructor(private minute: number, private prefix?: string, private postfix?: string) { + this.calcuDisplay(); + } + private calcuDisplay() { + if (this.minute < ONE_HOUR ) { + this.viewValue = this.minute + MINUTE; + } else if (this.minute < ONE_DAY) { + if ( this.minute % ONE_HOUR === 0 ) { + this.viewValue = (this.minute / ONE_HOUR) + HOUR; + } else { + this.viewValue = this.minute + MINUTE; + } + } else { + if ( this.minute % ONE_DAY === 0 ) { + this.viewValue = (this.minute / ONE_DAY) + DAY; + } else { + this.viewValue = this.minute + MINUTE; + } + } + } + + getValueWithTime(): string { + return this.viewValue; + } + getValueWithAddedWords(): string { + const prefix = this.prefix ? this.prefix + ' ' : ''; + const postfix = this.postfix ? this.postfix + ' ' : ''; + return prefix + this.getValueWithTime() + postfix; + } + getValue(): number { + return this.minute; + } + getMiliSeconds(): number { + return this.minute * 60 * 1000; + } + equalValue(target: number): boolean { + return this.minute === target; + } + equals(target: Period): boolean { + if ( target ) { + return this.getValue() === target.getValue(); + } else { + return false; + } + } +} diff --git a/web/src/main/webapp/v2/src/app/core/utils/chart-data-param-maker.ts b/web/src/main/webapp/v2/src/app/core/utils/chart-data-param-maker.ts new file mode 100644 index 000000000000..f6eaa56fce4d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/utils/chart-data-param-maker.ts @@ -0,0 +1,21 @@ +export function getParamForAgentChartData(agentId: string, [from, to]: number[]): object { + return { + params: { + agentId, + from, + to, + sampleRate: 1 + } + }; +} + +export function getParamForApplicationChartData(applicationId: string, [from, to]: number[]): object { + return { + params: { + applicationId, + from, + to, + sampleRate: 1 + } + }; +} diff --git a/web/src/main/webapp/v2/src/app/core/utils/filter-param-maker.ts b/web/src/main/webapp/v2/src/app/core/utils/filter-param-maker.ts new file mode 100644 index 000000000000..c9407e492f5d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/utils/filter-param-maker.ts @@ -0,0 +1,23 @@ +import { Filter } from '../models/filter'; + +export class FilterParamMaker { + static makeParam(currentFilter: string, filter: Filter): string { + const aCurrentFilter: Filter[] = Filter.instanceFromString(currentFilter || '[]'); + if (aCurrentFilter.length === 0) { + aCurrentFilter.push(filter); + } else { + let searchIndex = -1; + for ( let i = 0 ; i < aCurrentFilter.length ; i++ ) { + if ( aCurrentFilter[i].equal(filter) ) { + searchIndex = i; + aCurrentFilter[i] = filter; // replace previous param object + break; + } + } + if (searchIndex === -1) { + aCurrentFilter.push(filter); + } + } + return '/' + encodeURIComponent('[' + aCurrentFilter.map(f => f.toString()).join(',') + ']'); + } +} diff --git a/web/src/main/webapp/v2/src/app/core/utils/hint-param-maker.ts b/web/src/main/webapp/v2/src/app/core/utils/hint-param-maker.ts new file mode 100644 index 000000000000..d6bbf2638c95 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/core/utils/hint-param-maker.ts @@ -0,0 +1,93 @@ +/* + from server + filterTargetRpcList : [{ + rpc: string, + rpcServiceTypeCode: number + }, ...] + > URL string size를 줄이기 위해 다음과 같이 변경 함. + url + { + [toNode.applicationName] : [ rpc, rpcServiceTypeCode, rpc, rpcServiceTypeCode ... ], + ... + } +*/ +interface IUrlFormat { + [key: string]: any[]; +} +interface IServerFormat { + [key: string]: { + rpc: string, + rpcServiceTypeCode: number + }[]; +} +export class HintParamMaker { + static makeParam(currentHint: string, addedHint: IServerFormat): string { + if (addedHint) { + const parsedCurrntHint = JSON.parse(currentHint || '{}'); + const currentHintKeys = Object.keys(parsedCurrntHint); + + if (currentHintKeys.length === 0) { + return '/' + encodeURIComponent(JSON.stringify(HintParamMaker.makeToUrlFormatFromServerFormat(addedHint))); + } else { + const urlFormatOfCurrentHint = HintParamMaker.makeToServerFormatFromUrlFormat(parsedCurrntHint); + const mergedFormat = HintParamMaker.mergeFormat(urlFormatOfCurrentHint, addedHint); + return '/' + encodeURIComponent(JSON.stringify(HintParamMaker.makeToUrlFormatFromServerFormat(mergedFormat))); + } + } else { + return '/' + currentHint; + } + } + static makeToUrlFormatFromServerFormat(addedHint: IServerFormat): IUrlFormat { + const addedHintKeys = Object.keys(addedHint); + const urlFormat: IUrlFormat = {}; + addedHintKeys.forEach((key: string) => { + urlFormat[key] = addedHint[key].reduce((acc: any, data: any) => { + acc.push(data.rpc, data.rpcServiceTypeCode); + return acc; + }, []); + }); + return urlFormat; + } + static makeToServerFormatFromUrlFormat(urlHint: IUrlFormat): IServerFormat { + const newFormat: IServerFormat = {}; + const urlHintKeys = Object.keys(urlHint); + urlHintKeys.forEach((key: string) => { + const urlHintValue = urlHint[key]; + const value = []; + for (let i = 0 ; i < urlHintValue.length ; i = i + 2) { + value.push({ + rpc: urlHintValue[i], + rpcServiceTypeCode: urlHintValue[i + 1] + }); + } + newFormat[key] = value; + }); + return newFormat; + } + static mergeFormat(urlFormat: IServerFormat, addedFormat: IServerFormat): IServerFormat { + const mergedFormat: IServerFormat = {}; + const urlFormatKeys = Object.keys(urlFormat); + const addedFormatKeys = Object.keys(addedFormat); + + urlFormatKeys.forEach((key: string) => { + mergedFormat[key] = urlFormat[key]; + }); + addedFormatKeys.forEach((key: string) => { + if (mergedFormat[key]) { + mergedFormat[key] = mergedFormat[key].concat(addedFormat[key]); + const m = mergedFormat[key]; + for (let i = 0 ; i < m.length ; i++) { + for (let j = i + 1 ; j < m.length ; j++ ) { + if (m[i].rpc === m[j].rpc && m[i].rpcServiceTypeCode === m[j].rpcServiceTypeCode) { + m.splice(j, 1); + j--; + } + } + } + } else { + mergedFormat[key] = addedFormat[key]; + } + }); + return mergedFormat; + } +} diff --git a/web/src/main/webapp/v2/src/app/index.ts b/web/src/main/webapp/v2/src/app/index.ts new file mode 100644 index 000000000000..875bdb2f254f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/index.ts @@ -0,0 +1,2 @@ +export * from './app.component'; +export * from './app.module'; diff --git a/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.component.css b/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.component.css new file mode 100644 index 000000000000..9b58cf19b304 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.component.css @@ -0,0 +1,28 @@ +header { + background-color:#000; +} +.l-widget-group { + flex: 1; + color: #FFF; + height: 100%; + padding: 0 15px; + display: flex; + flex-flow: row; + font-size: 14px; + font-family: monospace; + align-items: flex-end; + justify-content: start; +} +.l-widget-group a { + padding: 6px 20px; +} +.active { + background-color: #408dd4; + box-shadow: 1px -1px 0px 1px rgba(255, 255, 255, 1); +} +.l-main-container { + display: flex; + flex-flow: column nowrap; + overflow-y: hidden; + height: calc(100vh - 50px); +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.component.html b/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.component.html new file mode 100644 index 000000000000..b558afff6704 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.component.html @@ -0,0 +1,11 @@ +
+ + +
+
+ +
+ \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.component.ts b/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.component.ts new file mode 100644 index 000000000000..3444d704619f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.component.ts @@ -0,0 +1,14 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; + +import { RouteInfoCollectorService } from 'app/shared/services'; + +@Component({ + selector: 'pp-admin-page', + templateUrl: './admin-page.component.html', + styleUrls: ['./admin-page.component.css'] +}) +export class AdminPageComponent implements OnInit, OnDestroy { + constructor(private routeInfoCollectorService: RouteInfoCollectorService) {} + ngOnInit() {} + ngOnDestroy() {} +} diff --git a/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.routing.ts new file mode 100644 index 000000000000..746c05a8b52b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/admin-page/admin-page.routing.ts @@ -0,0 +1,29 @@ + +import { Routes } from '@angular/router'; + +import { UrlPathId } from 'app/shared/models'; +import { SystemConfigurationResolverService, ServerTimeResolverService } from 'app/shared/services'; +import { AgentStatContentsContainerComponent } from 'app/core/components/agent-stat-contents/agent-stat-contents-container.component'; +import { AgentManagementContentsContainerComponent } from 'app/core/components/agent-management-contents/agent-management-contents-container.component'; +import { AdminPageComponent } from './admin-page.component'; + +export const routing: Routes = [ + { + path: '', + component: AdminPageComponent, + resolve: { + configuration: SystemConfigurationResolverService, + serverTime: ServerTimeResolverService + }, + children: [ + { + path: UrlPathId.AGENT, + component: AgentManagementContentsContainerComponent + }, + { + path: UrlPathId.STAT, + component: AgentStatContentsContainerComponent + } + ] + } +]; diff --git a/web/src/main/webapp/v2/src/app/routes/admin-page/index.ts b/web/src/main/webapp/v2/src/app/routes/admin-page/index.ts new file mode 100644 index 000000000000..b78989e691e4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/admin-page/index.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { SharedModule } from 'app/shared'; +import { AdminPageComponent } from './admin-page.component'; +import { routing } from './admin-page.routing'; + +import { AgentManagementContentsModule } from 'app/core/components/agent-management-contents'; +import { AgentStatContentsModule } from 'app/core/components/agent-stat-contents'; + +@NgModule({ + declarations: [ + AdminPageComponent + ], + imports: [ + RouterModule.forChild(routing), + SharedModule, + AgentStatContentsModule, + AgentManagementContentsModule + ], + exports: [], + providers: [] +}) +export class AdminPageModule { } diff --git a/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.component.css b/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.component.css new file mode 100644 index 000000000000..9f8680bc63ce --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.component.css @@ -0,0 +1,60 @@ +:host { + display: block; + height: 100%; + background-color: #edf2f8; +} + +.l-support-page-logo { + display: flex; + justify-content: center; + align-items: center; +} + +.l-info-text { + text-align: center; + margin-bottom: 40px; + font-size: 17px; + color: #757575; +} + +.l-support-page-contents { + position: relative; + top: 50px; +} + +.l-browser-list { + width: 420px; + margin: auto; +} + +.l-browser-list-item { + background-color: #fff; + border-radius: 5px; + border: 1px solid #e5e8f0; + margin-bottom: 3px; +} + +.l-browser-list-item:hover { + background-color: #e5e8f0; +} + +.l-browser-link { + display: inline-block; + width: 100%; + padding: 20px 30px; +} + +.l-browser-img { + display: inline-block; + vertical-align: middle; + width: 42px; + height: 42px; +} + +.l-browser-name { + display: inline-block; + vertical-align: middle; + margin-left: 13px; + font-size: 17p; + color: #757575; +} diff --git a/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.component.html b/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.component.html new file mode 100644 index 000000000000..3dfffb16e77c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.component.html @@ -0,0 +1,14 @@ +
+ +
+
+

{{i18nText$ | async}}

+ +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.component.ts b/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.component.ts new file mode 100644 index 000000000000..0f2f1240aafc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable } from 'rxjs'; +import * as bowser from 'bowser'; + +import { WebAppSettingDataService } from 'app/shared/services'; + +interface IBrowserInfo { + downloadLink: string; + name: string; + displayName: string; +} + +@Component({ + templateUrl: './browser-support-page.component.html', + styleUrls: ['./browser-support-page.component.css'] +}) +export class BrowserSupportPageComponent implements OnInit { + private browserInfoList: IBrowserInfo[]; + funcImagePath: Function; + i18nText$: Observable; + + constructor( + private webAppSettingDataService: WebAppSettingDataService, + private translateService: TranslateService + ) {} + + ngOnInit() { + this.funcImagePath = this.webAppSettingDataService.getImagePathMakeFunc(); + this.browserInfoList = [ + { + downloadLink: 'https://www.google.com/chrome', + name: 'chrome', + displayName: 'Google Chrome' + }, { + downloadLink: 'https://www.mozilla.org/en/firefox/new', + name: 'firefox', + displayName: 'Mozilla Firefox' + }, { + downloadLink: 'https://support.apple.com/en-us/HT204416', + name: 'safari', + displayName: 'Apple Safari' + }, { + downloadLink: 'https://www.microsoft.com/en-us/windows/microsoft-edge', + name: 'edge', + displayName: 'Microsoft Edge' + } + ]; + this.i18nText$ = this.translateService.get('SUPPORT.INSTALL_GUIDE'); + } + + getFilteredBrowserInfoList(): IBrowserInfo[] { + const userOSName = bowser.osname; + + return this.browserInfoList.filter((browserInfo: IBrowserInfo) => { + switch (userOSName) { + case 'Windows': + return browserInfo.name !== 'safari'; + case 'macOS': + return browserInfo.name !== 'edge'; + default: + return browserInfo.name === 'chrome' || browserInfo.name === 'firefox'; + } + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.routing.ts new file mode 100644 index 000000000000..ced6cebf3501 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/browser-support-page/browser-support-page.routing.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { BrowserSupportPageComponent } from './browser-support-page.component'; + +const routes: Routes = [ + { + path: '', + component: BrowserSupportPageComponent + } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes) + ], + exports: [ + RouterModule + ] +}) +export class BrowserSupportPageRoutingModule {} diff --git a/web/src/main/webapp/v2/src/app/routes/browser-support-page/index.ts b/web/src/main/webapp/v2/src/app/routes/browser-support-page/index.ts new file mode 100644 index 000000000000..61979ce8a07e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/browser-support-page/index.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from 'app/shared'; +import { BrowserSupportPageRoutingModule } from './browser-support-page.routing'; +import { BrowserSupportPageComponent } from './browser-support-page.component'; + +@NgModule({ + declarations: [ + BrowserSupportPageComponent + ], + imports: [ + SharedModule, + BrowserSupportPageRoutingModule + ], + exports: [], + providers: [] +}) +export class BrowserSupportPageModule {} diff --git a/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.component.css b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.component.css new file mode 100644 index 000000000000..affb06f4e3a9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.component.css @@ -0,0 +1,29 @@ +.l-widget-group { + display: flex; + flex-flow: row wrap; + flex: 1; + padding: 0 15px; + height: 100%; + align-items: center; +} +.l-main-container { + display: flex; + flex-flow: row wrap; + height: calc(100vh - 50px); + overflow-y: hidden; +} +.l-main-section { + display: flex; + flex-flow: column nowrap; + flex: 1; + background: #edf2f8; + position: relative; + height: 100%; +} +.l-main-contents { + height: 100%; + position: relative; +} +button { + outline: none; +} diff --git a/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.component.html b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.component.html new file mode 100644 index 000000000000..02f37f36d488 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.component.html @@ -0,0 +1,17 @@ + +
+ +
+ +
+ + +
+
+
+
+ +
+
+ +
diff --git a/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.component.ts b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.component.ts new file mode 100644 index 000000000000..95234dfd4a03 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit } from '@angular/core'; + +import { RouteInfoCollectorService } from 'app/shared/services'; + +@Component({ + selector: 'pp-filtered-map-page', + templateUrl: './filtered-map-page.component.html', + styleUrls: ['./filtered-map-page.component.css'] +}) +export class FilteredMapPageComponent implements OnInit { + constructor(private routeInfoCollectorService: RouteInfoCollectorService) {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.routing.ts new file mode 100644 index 000000000000..4e241538f492 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/filtered-map-page.routing.ts @@ -0,0 +1,143 @@ +import { Routes } from '@angular/router'; + +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { FilteredMapContentsContainerComponent } from 'app/core/components/filtered-map-contents/filtered-map-contents-container.component'; +import { SideBarForFilteredMapContainerComponent } from 'app/core/components/side-bar/side-bar-for-filtered-map-container.component'; +// import { NoneComponent } from 'app/shared/components/empty-contents'; +import { SystemConfigurationResolverService, ApplicationListResolverService } from 'app/shared/services'; +// import { UrlValidateGuard } from 'app/shared/services'; +import { UrlRedirectorComponent } from 'app/shared/components/url-redirector/url-redirector.component'; +import { FilteredMapPageComponent } from './filtered-map-page.component'; + +export const routing: Routes = [ + { + path: '', + component: FilteredMapPageComponent, + resolve: { + configuration: SystemConfigurationResolverService, + applicationList: ApplicationListResolverService + }, + children: [ + { + path: '', + redirectTo: '/' + UrlPath.MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.APPLICATION, + data: { + path: UrlPath.MAIN + }, + component: UrlRedirectorComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD, + data: { + path: UrlPath.MAIN + }, + component: UrlRedirectorComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME, + children: [ + { + path: '', + component: FilteredMapContentsContainerComponent + }, + { + path: '', + component: SideBarForFilteredMapContainerComponent, + outlet: 'sidebar' + }, + { + path: ':' + UrlPathId.FILTER, + children: [ + { + path: '', + component: FilteredMapContentsContainerComponent + }, + { + path: ':' + UrlPathId.HINT, + children: [ + { + path: '', + component: FilteredMapContentsContainerComponent + } + ] + } + ] + } + ] + } + ] + } +]; + +// export const routing: Routes = [ +// { +// path: UrlPath.FILTERED_MAP, +// component: FilteredMapPageComponent, +// canActivate: [ UrlValidateGuard ], +// resolve: { +// configuration: SystemConfigurationResolverService, +// applicationList: ApplicationListResolverService +// }, +// children: [ +// { +// path: '', +// component: NoneComponent +// }, +// { +// path: ':' + UrlPathId.APPLICATION, +// component: NoneComponent +// }, +// { +// path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD, +// component: NoneComponent +// }, +// { +// path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME, +// children: [ +// { +// path: '', +// component: FilteredMapContentsContainerComponent +// }, +// { +// path: '', +// component: SideBarForFilteredMapContainerComponent, +// outlet: 'sidebar' +// } +// ] +// }, +// { +// path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME + '/:' + UrlPathId.FILTER, +// children: [ +// { +// path: '', +// component: FilteredMapContentsContainerComponent +// }, +// { +// path: '', +// component: SideBarForFilteredMapContainerComponent, +// outlet: 'sidebar' +// } +// ] +// }, +// { +// path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME + '/:' + UrlPathId.FILTER + '/:' + UrlPathId.HINT, +// children: [ +// { +// path: '', +// component: FilteredMapContentsContainerComponent +// }, +// { +// path: '', +// component: SideBarForFilteredMapContainerComponent, +// outlet: 'sidebar' +// } +// ] +// } + +// ] +// } +// ]; diff --git a/web/src/main/webapp/v2/src/app/routes/filtered-map-page/index.ts b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/index.ts new file mode 100644 index 000000000000..39f83b93adb5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/filtered-map-page/index.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { routing } from './filtered-map-page.routing'; +import { SharedModule } from 'app/shared'; +import { NoticeModule } from 'app/core/components/notice'; +import { DataLoadIndicatorModule } from 'app/core/components/data-load-indicator'; +import { StateButtonModule } from 'app/core/components/state-button'; +import { CommandGroupModule } from 'app/core/components/command-group'; +import { FilteredMapContentsModule } from 'app/core/components/filtered-map-contents'; +import { SideBarModule } from 'app/core/components/side-bar'; + +import { FilteredMapPageComponent } from './filtered-map-page.component'; + +@NgModule({ + declarations: [ + FilteredMapPageComponent + ], + imports: [ + SharedModule, + NoticeModule, + DataLoadIndicatorModule, + StateButtonModule, + CommandGroupModule, + FilteredMapContentsModule, + SideBarModule, + RouterModule.forChild(routing) + ], + exports: [ + RouterModule + ], + providers: [] +}) +export class FilteredMapPageModule { } diff --git a/web/src/main/webapp/v2/src/app/routes/inspector-page/index.ts b/web/src/main/webapp/v2/src/app/routes/inspector-page/index.ts new file mode 100644 index 000000000000..462570bb6a8a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/inspector-page/index.ts @@ -0,0 +1,43 @@ +import { NgModule } from '@angular/core'; +import { MatTooltipModule } from '@angular/material'; +import { RouterModule } from '@angular/router'; + +import { routing } from './inspector-page.routing'; +import { SharedModule } from 'app/shared'; +import { NoticeModule } from 'app/core/components/notice'; +import { ApplicationListModule } from 'app/core/components/application-list'; +import { PeriodSelectorModule } from 'app/core/components/period-selector'; +import { CommandGroupModule } from 'app/core/components/command-group'; +import { ApplicationInspectorTitleModule } from 'app/core/components/application-inspector-title'; +import { ServerAndAgentListModule } from 'app/core/components/server-and-agent-list'; +import { AgentSearchInputModule } from 'app/core/components/agent-search-input'; +import { ApplicationInspectorContentsModule } from 'app/core/components/application-inspector-contents'; +import { AgentInspectorContentsModule } from 'app/core/components/agent-inspector-contents'; +import { EmptyInspectorContentsModule } from 'app/core/components/empty-inspector-contents'; +import { InspectorPageComponent } from './inspector-page.component'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + InspectorPageComponent + ], + imports: [ + MatTooltipModule, + SharedModule, + NoticeModule, + ApplicationListModule, + PeriodSelectorModule, + CommandGroupModule, + ApplicationInspectorTitleModule, + ServerAndAgentListModule, + ApplicationInspectorContentsModule, + AgentInspectorContentsModule, + AgentSearchInputModule, + EmptyInspectorContentsModule, + HelpViewerPopupModule, + RouterModule.forChild(routing) + ], + exports: [], + providers: [] +}) +export class InspectorPageModule {} diff --git a/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.component.css b/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.component.css new file mode 100644 index 000000000000..c7b3647a1cf5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.component.css @@ -0,0 +1,42 @@ +.l-widget-group { + display: flex; + flex-flow: row wrap; + flex: 1; + padding: 0 15px; + height: 100%; + align-items: center; +} +.l-main-container { + display: flex; + flex-flow: row wrap; + height: calc(100vh - 50px); + overflow-y: hidden; +} +.l-sidemenu-wrap { + display: flex; + flex-flow: column nowrap; + position: relative; + height: 100%; +} +.l-sidemenu-left { + width: 260px; + overflow: visible; + border-right: 1px solid #E5E8F0; + background: #FFF; + height: 100%; +} +.l-main-section { + display: flex; + flex-flow: column nowrap; + flex: 1; + background: #edf2f8; + position: relative; + height: 100%; +} +.fa-question-circle { + color: white; + font-size: 18px; +} +button { + outline: none; +} diff --git a/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.component.html b/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.component.html new file mode 100644 index 000000000000..3ef10ff84e7b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.component.html @@ -0,0 +1,22 @@ + +
+ +
+ + +
+ + +
+
+
+
+ + + +
+
+
+ +
+
diff --git a/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.component.ts b/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.component.ts new file mode 100644 index 000000000000..031e1ba83683 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit } from '@angular/core'; +import { state, style, animate, transition, trigger } from '@angular/animations'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { RouteInfoCollectorService, NewUrlStateNotificationService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { EndTime } from 'app/core/models'; +import { UrlPathId } from 'app/shared/models'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-inspector-page', + animations: [ + trigger('fadeInOut', [ + state('in', style({transform: 'translateX(0)', opacity: 1})), + transition(':leave', [ // is alias to '* => void' + animate('1.5s 0.1s ease-in', style({ + transform: 'translateX(-100%)', + opacity: 0 + })) + ]), + transition(':enter', [ // is alias to 'void => *' + style({ + transform: 'translateX(-100%)', + opacity: 0 + }), + animate('2s ease-out') + ]) + ]) + ], + templateUrl: './inspector-page.component.html', + styleUrls: ['./inspector-page.component.css'] +}) +export class InspectorPageComponent implements OnInit { + endTime$: Observable; + + constructor( + private routeInfoCollectorService: RouteInfoCollectorService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.endTime$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe( + map((urlService: NewUrlStateNotificationService) => { + return urlService.getPathValue(UrlPathId.END_TIME); + }) + ); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.NAVBAR); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.NAVBAR, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.routing.ts new file mode 100644 index 000000000000..2b02daaf2f80 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/inspector-page/inspector-page.routing.ts @@ -0,0 +1,55 @@ +import { Routes } from '@angular/router'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ApplicationInspectorContentsContainerComponent } from 'app/core/components/application-inspector-contents/application-inspector-contents-container.component'; +import { AgentInspectorContentsContainerComponent } from 'app/core/components/agent-inspector-contents/agent-inspector-contents-container.component'; +import { EmptyInspectorContentsContainerComponent } from 'app/core/components/empty-inspector-contents/empty-inspector-contents-container.component'; +import { UrlRedirectorComponent } from 'app/shared/components/url-redirector/url-redirector.component'; +import { SystemConfigurationResolverService, ApplicationListResolverService } from 'app/shared/services'; +import { InspectorPageComponent } from './inspector-page.component'; + +export const routing: Routes = [ + { + path: '', + component: InspectorPageComponent, + resolve: { + configuration: SystemConfigurationResolverService, + applicationList: ApplicationListResolverService + }, + children: [ + { + path: '', + component: EmptyInspectorContentsContainerComponent + }, + { + path: ':' + UrlPathId.APPLICATION, + data: { + path: UrlPath.INSPECTOR + }, + component: UrlRedirectorComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD, + data: { + path: UrlPath.INSPECTOR + }, + component: UrlRedirectorComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME, + component: ApplicationInspectorContentsContainerComponent, + data: { + showRealTimeButton: false, + enableRealTimeMode: false + } + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME + '/:' + UrlPathId.AGENT_ID, + component: AgentInspectorContentsContainerComponent, + data: { + showRealTimeButton: false, + enableRealTimeMode: false + } + } + ] + } +]; diff --git a/web/src/main/webapp/v2/src/app/routes/main-page/index.ts b/web/src/main/webapp/v2/src/app/routes/main-page/index.ts new file mode 100644 index 000000000000..3074ac76a9f0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/main-page/index.ts @@ -0,0 +1,39 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { SharedModule } from 'app/shared'; +import { NoticeModule } from 'app/core/components/notice'; +import { ApplicationListModule } from 'app/core/components/application-list'; +import { ServerMapOptionsModule } from 'app/core/components/server-map-options'; +import { PeriodSelectorModule } from 'app/core/components/period-selector'; +import { CommandGroupModule } from 'app/core/components/command-group'; +import { MainContentsModule } from 'app/core/components/main-contents'; +import { RealTimeModule } from 'app/core/components/real-time'; +import { SideBarModule } from 'app/core/components/side-bar'; +import { MainPageComponent } from './main-page.component'; +import { routing } from './main-page.routing'; +import { HelpViewerPopupModule } from 'app/core/components/help-viewer-popup'; + +@NgModule({ + declarations: [ + MainPageComponent + ], + imports: [ + SharedModule, + NoticeModule, + ApplicationListModule, + ServerMapOptionsModule, + PeriodSelectorModule, + CommandGroupModule, + MainContentsModule, + RealTimeModule, + SideBarModule, + HelpViewerPopupModule, + RouterModule.forChild(routing) + ], + exports: [ + + ], + providers: [] +}) +export class MainPageModule { } diff --git a/web/src/main/webapp/v2/src/app/routes/main-page/main-page.component.css b/web/src/main/webapp/v2/src/app/routes/main-page/main-page.component.css new file mode 100644 index 000000000000..1b9767653bc6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/main-page/main-page.component.css @@ -0,0 +1,37 @@ +:host { + display: block; +} +.l-widget-group { + display: flex; + flex-flow: row wrap; + flex: 1; + padding: 0 15px; + height: 100%; + align-items: center; +} +.l-main-container { + display: flex; + flex-flow: row wrap; + height: calc(100vh - 50px); /* 50px: header 높이 */ + overflow-y: hidden; +} +.l-main-section { + display: flex; + flex-flow: column nowrap; + flex: 1; + background: #edf2f8; + position: relative; + height: 100%; +} +.l-main-contents { + height: 100%; + position: relative; +} +.fa-question-circle { + color: white; + font-size: 18px; +} + +button { + outline: none; +} diff --git a/web/src/main/webapp/v2/src/app/routes/main-page/main-page.component.html b/web/src/main/webapp/v2/src/app/routes/main-page/main-page.component.html new file mode 100644 index 000000000000..68e82d3606ee --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/main-page/main-page.component.html @@ -0,0 +1,20 @@ + +
+ +
+ + + +
+ + +
+
+
+
+ +
+ +
+ +
diff --git a/web/src/main/webapp/v2/src/app/routes/main-page/main-page.component.ts b/web/src/main/webapp/v2/src/app/routes/main-page/main-page.component.ts new file mode 100644 index 000000000000..07b2368c59d9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/main-page/main-page.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { RouteInfoCollectorService, WebAppSettingDataService, NewUrlStateNotificationService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; +import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component'; + +@Component({ + selector: 'pp-main-page', + templateUrl: './main-page.component.html', + styleUrls: ['./main-page.component.css'] +}) +export class MainPageComponent implements OnInit { + enableRealTime$: Observable; + constructor( + private routeInfoCollectorService: RouteInfoCollectorService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private webAppSettingDataService: WebAppSettingDataService, + private analyticsService: AnalyticsService, + private dynamicPopupService: DynamicPopupService + ) {} + ngOnInit() { + this.enableRealTime$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe( + map((urlService: NewUrlStateNotificationService) => { + return urlService.isRealTimeMode(); + }) + ); + this.webAppSettingDataService.getVersion().subscribe((version: string) => { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.VERSION, version); + }); + } + onShowHelp($event: MouseEvent): void { + this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.NAVBAR); + const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect(); + + this.dynamicPopupService.openPopup({ + data: HELP_VIEWER_LIST.NAVBAR, + coord: { + coordX: left + width / 2, + coordY: top + height / 2 + }, + component: HelpViewerPopupContainerComponent + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/main-page/main-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/main-page/main-page.routing.ts new file mode 100644 index 000000000000..bd48533d1a3c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/main-page/main-page.routing.ts @@ -0,0 +1,170 @@ + +import { Routes } from '@angular/router'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { MainContentsContainerComponent } from 'app/core/components/main-contents/main-contents-container.component'; +import { SideBarContainerComponent } from 'app/core/components/side-bar/side-bar-container.component'; +import { RealTimeContainerComponent } from 'app/core/components/real-time/real-time-container.component'; +import { EmptyContentsComponent, NoneComponent } from 'app/shared/components/empty-contents'; +import { UrlRedirectorComponent } from 'app/shared/components/url-redirector'; +// import { UrlValidateGuard } from 'app/shared/services'; +import { SystemConfigurationResolverService, ApplicationListResolverService, ServerTimeResolverService } from 'app/shared/services'; + +import { MainPageComponent } from './main-page.component'; + +export const routing: Routes = [ + { + path: '', + component: MainPageComponent, + resolve: { + configuration: SystemConfigurationResolverService, + applicationList: ApplicationListResolverService + }, + children: [ + { + path: '', + component: EmptyContentsComponent, + data: { + showRealTimeButton: false, + enableRealTimeMode: false + }, + }, + { + path: '', + component: SideBarContainerComponent, + outlet: 'sidebar' + }, + { + path: ':' + UrlPathId.APPLICATION, + children: [ + { + path: '', + data: { + path: UrlPath.MAIN + }, + component: UrlRedirectorComponent + }, + { + path: UrlPath.REAL_TIME, + resolve: { + serverTime: ServerTimeResolverService + }, + children: [ + { + path: '', + component: MainContentsContainerComponent, + data: { + showRealTimeButton: true, + enableRealTimeMode: true + } + }, + { + path: '', + component: RealTimeContainerComponent, + outlet: 'realtime' + } + ] + }, + { + path: ':' + UrlPathId.PERIOD, + children: [ + { + path: '', + data: { + path: UrlPath.MAIN + }, + component: UrlRedirectorComponent + }, + { + path: ':' + UrlPathId.END_TIME, + children: [ + { + path: '', + component: MainContentsContainerComponent, + data: { + showRealTimeButton: true, + enableRealTimeMode: false + } + } + ] + } + ] + } + ] + } + ] + } +]; + +// export const routing: Routes = [ +// { +// path: UrlPath.MAIN, +// component: MainPageComponent, +// canActivate: [ UrlValidateGuard ], +// resolve: { +// configuration: SystemConfigurationResolverService, +// applicationList: ApplicationListResolverService +// }, +// children: [ +// { +// path: '', +// component: EmptyContentsComponent, +// data: { +// showRealTimeButton: false, +// enableRealTimeMode: false +// }, +// }, +// { +// path: ':' + UrlPathId.APPLICATION + '/' + UrlPath.REAL_TIME, +// resolve: { +// serverTime: ServerTimeResolverService +// }, +// children: [ +// { +// path: '', +// component: MainContentsContainerComponent, +// data: { +// showRealTimeButton: true, +// enableRealTimeMode: true +// } +// }, +// { +// path: '', +// component: SideBarContainerComponent, +// outlet: 'sidebar' +// }, +// { +// path: '', +// component: RealTimeContainerComponent, +// outlet: 'realtime' +// } +// ] +// }, +// { +// path: ':' + UrlPathId.APPLICATION, +// component: NoneComponent +// }, +// { +// path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD, +// component: NoneComponent +// }, +// { +// path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME, +// children: [ +// { +// path: '', +// component: MainContentsContainerComponent, +// data: { +// showRealTimeButton: true, +// enableRealTimeMode: false +// } +// }, +// { +// path: '', +// component: SideBarContainerComponent, +// outlet: 'sidebar' +// } +// ] +// } +// ] +// } +// ]; diff --git a/web/src/main/webapp/v2/src/app/routes/real-time-page/index.ts b/web/src/main/webapp/v2/src/app/routes/real-time-page/index.ts new file mode 100644 index 000000000000..69beb5ced2e1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/real-time-page/index.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { SharedModule } from 'app/shared'; +import { RealTimeModule } from 'app/core/components/real-time'; +import { RealTimePageComponent } from './real-time-page.component'; +import { routing } from './real-time-page.routing'; + +@NgModule({ + declarations: [ + RealTimePageComponent + ], + imports: [ + SharedModule, + RealTimeModule, + RouterModule.forChild(routing) + ], + exports: [], + providers: [] +}) +export class RealTimePageModule { } diff --git a/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.component.css b/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.component.css new file mode 100644 index 000000000000..332fa2063ca6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.component.css @@ -0,0 +1,32 @@ +.realtime-application { + color: #FFF; +} +.realtime-application span { + margin-left: 10px; +} +.realtime-application span:last-child { + margin-left: 20px; + font-size: 12px; +} +.l-widget-group { + display: flex; + flex-flow: row wrap; + flex: 1; + padding: 0 15px; + height: 100%; + align-items: center; +} +.l-main-container { + display: flex; + flex-flow: row wrap; + height: calc(100vh - 50px); /* 50px: header 높이 */ + overflow-y: hidden; +} +.l-main-section { + display: flex; + flex-flow: column nowrap; + flex: 1; + background: #edf2f8; + position: relative; + height: 100%; +} diff --git a/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.component.html b/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.component.html new file mode 100644 index 000000000000..c6286d19be90 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.component.html @@ -0,0 +1,13 @@ +
+ +
+ + {{applicationName}} + RealTime Active Thread Chart +
+
+
+
+ +
+
diff --git a/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.component.ts b/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.component.ts new file mode 100644 index 000000000000..0bcbde39540f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { RouteInfoCollectorService, WebAppSettingDataService, NewUrlStateNotificationService } from 'app/shared/services'; + +@Component({ + selector: 'pp-real-time', + templateUrl: './real-time-page.component.html', + styleUrls: ['./real-time-page.component.css'] +}) +export class RealTimePageComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + applicationImgPath: string; + applicationName: string; + constructor( + private routeInfoCollectorService: RouteInfoCollectorService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private webAppSettingDataService: WebAppSettingDataService + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.APPLICATION); + } + )).subscribe((urlService: NewUrlStateNotificationService) => { + this.applicationName = urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName(); + this.applicationImgPath = this.webAppSettingDataService.getIconImagePath() + urlService.getPathValue(UrlPathId.APPLICATION).getServiceType() + this.webAppSettingDataService.getImageExt(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.routing.ts new file mode 100644 index 000000000000..547dd92c1510 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/real-time-page/real-time-page.routing.ts @@ -0,0 +1,38 @@ + +import { Routes } from '@angular/router'; + +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { RealTimePagingContainerComponent } from 'app/core/components/real-time/real-time-paging-container.component'; +import { SystemConfigurationResolverService, ServerTimeResolverService } from 'app/shared/services'; +import { RealTimePageComponent } from './real-time-page.component'; + +const TO_MAIN = '/' + UrlPath.MAIN; + +export const routing: Routes = [ + { + path: '', + component: RealTimePageComponent, + resolve: { + configuration: SystemConfigurationResolverService + }, + children: [ + { + path: '', + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.APPLICATION, + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PAGE, + resolve: { + serverTime: ServerTimeResolverService + }, + component: RealTimePagingContainerComponent + } + ] + } +]; diff --git a/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/index.ts b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/index.ts new file mode 100644 index 000000000000..c8548905b9fb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/index.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { SharedModule } from 'app/shared'; +import { ScatterChartModule } from 'app/core/components/scatter-chart'; +import { ApplicationListModule } from 'app/core/components/application-list'; +import { NoticeModule } from 'app/core/components/notice'; +import { ScatterFullScreenModePageComponent } from './scatter-full-screen-mode-page.component'; +import { routing } from './scatter-full-screen-mode-page.routing'; + +@NgModule({ + declarations: [ + ScatterFullScreenModePageComponent + ], + imports: [ + RouterModule.forChild(routing), + SharedModule, + ApplicationListModule, + ScatterChartModule, + NoticeModule, + ], + exports: [ + ], + providers: [] +}) +export class ScatterFullScreenModePageModule {} diff --git a/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.component.css b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.component.css new file mode 100644 index 000000000000..7be5d4c36c17 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.component.css @@ -0,0 +1,28 @@ +.l-widget-group { + flex:1; + padding: 0 15px; + height: 100%; + align-items: center; + color: #FFF; + display: flex; + flex-flow: row wrap; +} +.l-widget-group img { + margin-right: 10px; +} +.l-main-container { + display: flex; + flex-flow: row wrap; + justify-content: center; + width: 100%; + height: calc(100vh - 50px); +} +.l-sidemenu-wrap { + display: flex; + position:relative; + height: 100%; +} +.l-sidemenu { + margin-top: 50px; + width: 691px; +} diff --git a/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.component.html b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.component.html new file mode 100644 index 000000000000..f40e9a8b76fb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.component.html @@ -0,0 +1,14 @@ + +
+ +
+ {{applicationName}} - {{selectedAgent}} +
+
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.component.ts b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.component.ts new file mode 100644 index 000000000000..752e07ed34cc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { RouteInfoCollectorService, NewUrlStateNotificationService, WebAppSettingDataService } from 'app/shared/services'; + +@Component({ + selector: 'pp-scatter-full-screen-mode-page', + templateUrl: './scatter-full-screen-mode-page.component.html', + styleUrls: ['./scatter-full-screen-mode-page.component.css'] +}) +export class ScatterFullScreenModePageComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + applicationImgPath: string; + applicationName: string; + selectedAgent: string; + constructor( + private routeInfoCollectorService: RouteInfoCollectorService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private webAppSettingDataService: WebAppSettingDataService, + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.applicationName = urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName(); + if (urlService.hasValue(UrlPathId.AGENT_ID)) { + this.selectedAgent = urlService.getPathValue(UrlPathId.AGENT_ID); + } else { + this.selectedAgent = 'All'; + } + this.applicationImgPath = this.webAppSettingDataService.getIconImagePath() + urlService.getPathValue(UrlPathId.APPLICATION).getServiceType() + this.webAppSettingDataService.getImageExt(); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.routing.ts new file mode 100644 index 000000000000..0578912fbc35 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/scatter-full-screen-mode-page/scatter-full-screen-mode-page.routing.ts @@ -0,0 +1,56 @@ +import { Routes } from '@angular/router'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ScatterChartForFullScreenModeContainerComponent } from 'app/core/components/scatter-chart/scatter-chart-for-full-screen-mode-container.component'; +import { UrlRedirectorComponent } from 'app/shared/components/url-redirector/url-redirector.component'; +import { SystemConfigurationResolverService, ApplicationListResolverService, ServerTimeResolverService } from 'app/shared/services'; +import { ScatterFullScreenModePageComponent } from './scatter-full-screen-mode-page.component'; + +export const routing: Routes = [ + { + path: '', + component: ScatterFullScreenModePageComponent, + resolve: { + configuration: SystemConfigurationResolverService, + applicationList: ApplicationListResolverService + }, + children: [ + { + path: '', + redirectTo: '/' + UrlPath.MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.APPLICATION, + data: { + path: UrlPath.MAIN + }, + component: UrlRedirectorComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/' + UrlPathId.REAL_TIME, + resolve: { + serverTime: ServerTimeResolverService + }, + data: { + enableRealTimeMode: true + }, + component: ScatterChartForFullScreenModeContainerComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD, + data: { + path: UrlPath.MAIN + }, + component: UrlRedirectorComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME, + component: ScatterChartForFullScreenModeContainerComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME + '/:' + UrlPathId.AGENT_ID, + component: ScatterChartForFullScreenModeContainerComponent + } + ] + } +]; diff --git a/web/src/main/webapp/v2/src/app/routes/thread-dump-page/index.ts b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/index.ts new file mode 100644 index 000000000000..a42c47a80918 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/index.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { SharedModule } from 'app/shared'; +import { NoticeModule } from 'app/core/components/notice'; +import { ThreadDumpListModule } from 'app/core/components/thread-dump-list'; +import { ThreadDumpLogModule } from 'app/core/components/thread-dump-log'; +import { ThreadDumpPageComponent } from './thread-dump-page.component'; +import { routing } from './thread-dump-page.routing'; + +@NgModule({ + declarations: [ + ThreadDumpPageComponent + ], + imports: [ + SharedModule, + NoticeModule, + ThreadDumpListModule, + ThreadDumpLogModule, + RouterModule.forChild(routing) + ], + exports: [], + providers: [] +}) +export class ThreadDumpPageModule { } diff --git a/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.component.css b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.component.css new file mode 100644 index 000000000000..ac38fa98b833 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.component.css @@ -0,0 +1,43 @@ +.l-widget-group { + display: flex; + flex-flow: row wrap; + flex: 1; + padding: 0 15px; + height: 100%; + align-items: center; +} +.l-widget-group span { + color: #FFF; +} +.l-main-container { + display: flex; + flex-flow: column nowrap; + padding-top: 20px; + overflow-y: hidden; + height: calc(100vh - 50px); +} +.l-main-container h3 { + color: #00ccff; + padding-left: 20px; + font-weight: 200; + border-bottom: 1px solid #D0D7E1; + padding-bottom: 20px; +} +.l-main-section { + display: flex; + flex-flow: column nowrap; + background-color: #FFF; + flex: 1; + position: relative; + height: 100%; +} +.l-thread-dump-list { + height: 40%; + margin: 20px; + position: relative; +} +.l-thread-dump-log { + height: 60%; + position: relative; + background-color: #edf2f8; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.component.html b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.component.html new file mode 100644 index 000000000000..82c9e1a82bee --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.component.html @@ -0,0 +1,18 @@ + +
+ +
+ {{applicationName}} / {{agentId}} +
+
+
+

Thread Dump

+
+
+ +
+
+ +
+
+
diff --git a/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.component.ts b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.component.ts new file mode 100644 index 000000000000..360884985c39 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.component.ts @@ -0,0 +1,39 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { RouteInfoCollectorService, NewUrlStateNotificationService } from 'app/shared/services'; + +@Component({ + selector: 'pp-thread-dump-page', + templateUrl: './thread-dump-page.component.html', + styleUrls: ['./thread-dump-page.component.css'] +}) +export class ThreadDumpPageComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + applicationName: string; + agentId: string; + constructor( + private routeInfoCollectorService: RouteInfoCollectorService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.APPLICATION, UrlPathId.AGENT_ID)) { + return true; + } + return false; + }) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.applicationName = urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName(); + this.agentId = urlService.getPathValue(UrlPathId.AGENT_ID); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.routing.ts new file mode 100644 index 000000000000..d392dd8e42eb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/thread-dump-page/thread-dump-page.routing.ts @@ -0,0 +1,45 @@ + +import { Routes } from '@angular/router'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { ThreadDumpListContainerComponent } from 'app/core/components/thread-dump-list/thread-dump-list-container.component'; +import { UrlRedirectorComponent } from 'app/shared/components/url-redirector'; +import { SystemConfigurationResolverService, ServerTimeResolverService } from 'app/shared/services'; +import { ThreadDumpPageComponent } from './thread-dump-page.component'; + +export const routing: Routes = [ + { + path: '', + component: ThreadDumpPageComponent, + resolve: { + configuration: SystemConfigurationResolverService + }, + children: [ + { + path: '', + redirectTo: '/' + UrlPath.MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.APPLICATION, + data: { + path: UrlPath.MAIN + }, + component: UrlRedirectorComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.AGENT_ID, + data: { + path: UrlPath.MAIN + }, + component: UrlRedirectorComponent + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.AGENT_ID + '/:' + UrlPathId.FOCUS_TIMESTAMP, + resolve: { + serverTime: ServerTimeResolverService + }, + component: ThreadDumpListContainerComponent + } + ] + } +]; diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/index.ts b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/index.ts new file mode 100644 index 000000000000..a0a842f8e4e5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/index.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { SharedModule } from 'app/shared'; +import { NoticeModule } from 'app/core/components/notice'; +import { ApplicationListModule } from 'app/core/components/application-list'; +import { CommandGroupModule } from 'app/core/components/command-group'; +import { TransactionDetailContentsModule } from 'app/core/components/transaction-detail-contents'; +import { TransactionDetailPageComponent } from './transaction-detail-page.component'; +import { routing } from './transaction-detail-page.routing'; + +@NgModule({ + declarations: [ + TransactionDetailPageComponent + ], + imports: [ + SharedModule, + NoticeModule, + ApplicationListModule, + CommandGroupModule, + TransactionDetailContentsModule, + RouterModule.forChild(routing) + ], + exports: [], + providers: [] +}) +export class TransactionDetailPageModule {} diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.component.css b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.component.css new file mode 100644 index 000000000000..edb5a625303d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.component.css @@ -0,0 +1,34 @@ +.l-transaction-content { + height:100%; +} +.l-main-container { + display: flex; + flex-flow: row wrap; + overflow-y: hidden; + height: calc(100vh - 50px); +} +.l-main-section { + display: flex; + flex-flow: column nowrap; + flex:1; + background:#edf2f8; + position: relative; + height: 100%; +} +.l-main-section .l-main-contents { + height: 100%; + position: relative; + overflow: auto; +} +.l-main-section .l-main-contents.l-main-contents-transaction { + overflow: hidden; +} +.l-main-contents .l-content-item { + margin:10px; +} +.l-main-contents .l-content-item.l-margin-none { + margin:0; +} +.l-main-contents .l-content-item.l-border-none { + border:none; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.component.html b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.component.html new file mode 100644 index 000000000000..3707f8d2ee6e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.component.html @@ -0,0 +1,14 @@ + +
+ + +
+
+
+
+
+ +
+
+
+
diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.component.ts b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.component.ts new file mode 100644 index 000000000000..cd3a6fdea3db --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil, filter } from 'rxjs/operators'; + +import { Actions } from 'app/shared/store'; +import { UrlPathId } from 'app/shared/models'; +import { StoreHelperService, RouteInfoCollectorService, NewUrlStateNotificationService, TransactionDetailDataService } from 'app/shared/services'; + +@Component({ + selector: 'pp-transaction-detail-page', + templateUrl: './transaction-detail-page.component.html', + styleUrls: ['./transaction-detail-page.component.css'] +}) +export class TransactionDetailPageComponent implements OnInit, OnDestroy { + private unsubscribe: Subject = new Subject(); + constructor( + private routeInfoCollectorService: RouteInfoCollectorService, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private transactionDetailDataService: TransactionDetailDataService + ) {} + ngOnInit() { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + filter((urlService: NewUrlStateNotificationService) => { + return urlService.hasValue(UrlPathId.AGENT_ID, UrlPathId.SPAN_ID, UrlPathId.TRACE_ID, UrlPathId.FOCUS_TIMESTAMP); + }) + ).subscribe((urlService: NewUrlStateNotificationService) => { + this.transactionDetailDataService.getData( + urlService.getPathValue(UrlPathId.AGENT_ID), + urlService.getPathValue(UrlPathId.SPAN_ID), + urlService.getPathValue(UrlPathId.TRACE_ID), + urlService.getPathValue(UrlPathId.FOCUS_TIMESTAMP) + ).subscribe((transactionDetailInfo: ITransactionDetailData) => { + this.storeHelperService.dispatch(new Actions.UpdateTransactionDetailData(transactionDetailInfo)); + }); + }); + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.routing.ts new file mode 100644 index 000000000000..3ec2c8c9740b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-detail-page/transaction-detail-page.routing.ts @@ -0,0 +1,68 @@ +import { Routes } from '@angular/router'; + +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { TransactionDetailContentsContainerComponent } from 'app/core/components/transaction-detail-contents/transaction-detail-contents-container.component'; +import { SystemConfigurationResolverService, ApplicationListResolverService } from 'app/shared/services'; +import { TransactionDetailPageComponent } from './transaction-detail-page.component'; + +const TO_MAIN = '/' + UrlPath.MAIN; + +export const routing: Routes = [ + { + path: '', + component: TransactionDetailPageComponent, + resolve: { + configuration: SystemConfigurationResolverService, + applicationList: ApplicationListResolverService + }, + children: [ + { + path: '', + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.TRACE_ID, + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.TRACE_ID + '/:' + UrlPathId.FOCUS_TIMESTAMP, + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.TRACE_ID + '/:' + UrlPathId.FOCUS_TIMESTAMP + '/:' + UrlPathId.AGENT_ID, + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.TRACE_ID + '/:' + UrlPathId.FOCUS_TIMESTAMP + '/:' + UrlPathId.AGENT_ID + '/:' + UrlPathId.SPAN_ID, + children: [ + { + path: '', + component: TransactionDetailContentsContainerComponent + }, + { + path: ':' + UrlPathId.VIEW_TYPE, + children: [ + { + path: '', + component: TransactionDetailContentsContainerComponent, + }, + { + path: ':' + UrlPathId.SEARCH_ID, + children: [ + { + path: '', + component: TransactionDetailContentsContainerComponent, + } + ] + } + ] + } + ] + } + ] + } +]; diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-list-page/index.ts b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/index.ts new file mode 100644 index 000000000000..86fc552fa3ee --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/index.ts @@ -0,0 +1,38 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { SharedModule } from 'app/shared'; +import { AngularSplitModule } from 'app/core/components/angular-split/angular-split'; +import { NoticeModule } from 'app/core/components/notice'; +import { ApplicationListModule } from 'app/core/components/application-list'; +import { DataLoadIndicatorModule } from 'app/core/components/data-load-indicator'; +import { StateButtonModule } from 'app/core/components/state-button'; +import { CommandGroupModule } from 'app/core/components/command-group'; +import { TransactionTableGridModule } from 'app/core/components/transaction-table-grid'; +import { TransactionListBottomContentsModule } from 'app/core/components/transaction-list-bottom-contents'; + +import { TransactionListEmptyComponent } from './transaction-list-empty.component'; +import { TransactionListPageComponent } from './transaction-list-page.component'; +import { routing } from './transaction-list-page.routing'; + +@NgModule({ + declarations: [ + TransactionListEmptyComponent, + TransactionListPageComponent + ], + imports: [ + AngularSplitModule, + SharedModule, + NoticeModule, + ApplicationListModule, + DataLoadIndicatorModule, + StateButtonModule, + CommandGroupModule, + TransactionTableGridModule, + TransactionListBottomContentsModule, + RouterModule.forChild(routing) + ], + exports: [], + providers: [] +}) +export class TransactionListPageModule {} diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-empty.component.ts b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-empty.component.ts new file mode 100644 index 000000000000..534964ab3546 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-empty.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'pp-transaction-list-empty', + template: ` +
+ {{message}} +
+ `, + styles: [` + div { + width: 100%; + height: 100%; + display: flex; + font-size: 20px; + font-weight: 600; + align-items: center; + justify-content: center; + background-color: rgba(30, 87, 153, 0.3); + } + `] +}) +export class TransactionListEmptyComponent implements OnInit { + message: string; + constructor(private translateService: TranslateService) {} + ngOnInit() { + this.translateService.get('TRANSACTION_LIST.SELECT_TRANSACTION').subscribe((text: string) => { + this.message = text; + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.component.css b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.component.css new file mode 100644 index 000000000000..5ecc00916670 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.component.css @@ -0,0 +1,17 @@ +.l-widget-group { + display: flex; + flex-flow: row wrap; + flex: 1; + padding: 0 15px; + height: 100%; + align-items: center; +} +.l-main-container { + display: flex; + flex-flow: row wrap; + height: calc(100vh - 50px); + overflow-y: hidden; +} +split-area:first-child { + overflow: hidden !important; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.component.html b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.component.html new file mode 100644 index 000000000000..aaf760ee84fb --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.component.html @@ -0,0 +1,19 @@ + +
+ +
+ +
+ + +
+
+ + + + + + + + +
diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.component.ts b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.component.ts new file mode 100644 index 000000000000..d69d58b652d5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; + +import { WebAppSettingDataService, RouteInfoCollectorService, GutterEventService } from 'app/shared/services'; + +@Component({ + selector: 'pp-transaction-list-page', + templateUrl: './transaction-list-page.component.html', + styleUrls: ['./transaction-list-page.component.css'] +}) +export class TransactionListPageComponent implements OnInit { + direction = 'vertical'; + handlePosition: number[]; + constructor( + private routeInfoCollectorService: RouteInfoCollectorService, + private webAppSettingDataService: WebAppSettingDataService, + private gutterEventService: GutterEventService, + ) {} + ngOnInit() { + this.handlePosition = this.webAppSettingDataService.getListHandlePosition(); + } + onGutterResized({sizes}: {sizes: number[]}): void { + this.webAppSettingDataService.setListHandlePosition(sizes.map((size: number): number => { + return Number.parseFloat(size.toFixed(2)); + })); + this.gutterEventService.resizedGutter(sizes); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.routing.ts new file mode 100644 index 000000000000..b1ecc6cd0d4c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-list-page/transaction-list-page.routing.ts @@ -0,0 +1,70 @@ +import { Routes } from '@angular/router'; + +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { TransactionListEmptyComponent } from './transaction-list-empty.component'; +import { TransactionListBottomContentsContainerComponent } from 'app/core/components/transaction-list-bottom-contents/transaction-list-bottom-contents-container.component'; +import { TransactionTableGridContainerComponent } from 'app/core/components/transaction-table-grid/transaction-table-grid-container.component'; +import { SystemConfigurationResolverService, ApplicationListResolverService } from 'app/shared/services'; +import { TransactionListPageComponent } from './transaction-list-page.component'; + +const TO_MAIN = '/' + UrlPath.MAIN; + +export const routing: Routes = [ + { + path: '', + component: TransactionListPageComponent, + resolve: { + configuration: SystemConfigurationResolverService, + applicationList: ApplicationListResolverService + }, + children: [ + { + path: '', + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.APPLICATION, + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD, + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME, + children: [ + { + path: '', + component: TransactionListEmptyComponent + }, + { + path: '', + component: TransactionTableGridContainerComponent, + outlet: 'transaction-table' + }, + { + path: ':' + UrlPathId.TRANSACTION_INFO, + children: [ + { + path: '', + component: TransactionListBottomContentsContainerComponent, + }, + { + path: ':' + UrlPathId.VIEW_TYPE, + children: [ + { + path: '', + component: TransactionListBottomContentsContainerComponent, + } + ] + } + ] + } + ] + } + ] + } +]; diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-view-page/index.ts b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/index.ts new file mode 100644 index 000000000000..024bae77e9cc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/index.ts @@ -0,0 +1,35 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { SharedModule } from 'app/shared'; +import { ApplicationListModule } from 'app/core/components/application-list'; +import { NoticeModule } from 'app/core/components/notice'; +import { CommandGroupModule } from 'app/core/components/command-group'; +import { TransactionShortInfoModule } from 'app/core/components/transaction-short-info'; +import { TransactionViewTopContentsModule } from 'app/core/components/transaction-view-top-contents'; +import { TransactionViewBottomContentsModule } from 'app/core/components/transaction-view-bottom-contents'; + +import { AngularSplitModule } from 'app/core/components/angular-split/angular-split'; +import { TransactionViewPageComponent } from './transaction-view-page.component'; +import { routing } from './transaction-view-page.routing'; + +@NgModule({ + declarations: [ + TransactionViewPageComponent + ], + imports: [ + AngularSplitModule, + SharedModule, + ApplicationListModule, + NoticeModule, + CommandGroupModule, + TransactionShortInfoModule, + TransactionViewTopContentsModule, + TransactionViewBottomContentsModule, + RouterModule.forChild(routing) + ], + exports: [ + ], + providers: [] +}) +export class TransactionViewPageModule {} diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.component.css b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.component.css new file mode 100644 index 000000000000..69c5b0cdace4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.component.css @@ -0,0 +1,10 @@ +.l-main-container { + display: flex; + flex-flow: row wrap; + overflow-y: hidden; + height: calc(100vh - 50px); +} +.l-component-wrapper { + position: relative; + width: 100%; +} diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.component.html b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.component.html new file mode 100644 index 000000000000..42678b242007 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.component.html @@ -0,0 +1,18 @@ + +
+ + +
+
+
+ +
+ + + + + + + + +
diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.component.ts b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.component.ts new file mode 100644 index 000000000000..d79ef0030cfe --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from '@angular/core'; +import { Subject, Observable, of } from 'rxjs'; +import { take, takeUntil, switchMap } from 'rxjs/operators'; + +import { Actions } from 'app/shared/store'; +import { UrlPathId } from 'app/shared/models'; +import { StoreHelperService, RouteInfoCollectorService, NewUrlStateNotificationService, TransactionDetailDataService, GutterEventService } from 'app/shared/services'; + +@Component({ + selector: 'pp-transaction-view-page', + templateUrl: './transaction-view-page.component.html', + styleUrls: ['./transaction-view-page.component.css'] +}) +export class TransactionViewPageComponent implements OnInit { + private unsubscribe: Subject = new Subject(); + splitSize: number[]; + + constructor( + private routeInfoCollectorService: RouteInfoCollectorService, + private storeHelperService: StoreHelperService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private transactionDetailDataService: TransactionDetailDataService, + private gutterEventService: GutterEventService, + ) { } + + ngOnInit() { + this.initSplitRatio(); + this.initTransactionViewInfo(); + } + private initSplitRatio(): void { + this.gutterEventService.onGutterResized$.pipe( + take(1) + ).subscribe((splitSize: number[]) => this.splitSize = splitSize); + } + private initTransactionViewInfo(): void { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + takeUntil(this.unsubscribe), + switchMap((urlService: NewUrlStateNotificationService) => { + return this.transactionDetailDataService.getData( + urlService.getPathValue(UrlPathId.AGENT_ID), + urlService.getPathValue(UrlPathId.SPAN_ID), + urlService.getPathValue(UrlPathId.TRACE_ID), + urlService.getPathValue(UrlPathId.FOCUS_TIMESTAMP) + ); + }) + ).subscribe((transactionDetailInfo: ITransactionDetailData) => { + this.storeHelperService.dispatch(new Actions.UpdateTransactionDetailData(transactionDetailInfo)); + }); + } + onAjaxError(err: Error): Observable { + // TODO: Error발생시 띄워줄 팝업 컴포넌트 Call - issue#170 + return of({}); + } +} diff --git a/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.routing.ts b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.routing.ts new file mode 100644 index 000000000000..4414ef9c37a3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/routes/transaction-view-page/transaction-view-page.routing.ts @@ -0,0 +1,55 @@ +import { Routes } from '@angular/router'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { TransactionViewTopContentsContainerComponent } from 'app/core/components/transaction-view-top-contents/transaction-view-top-contents-container.component'; +import { TransactionViewBottomContentsContainerComponent } from 'app/core/components/transaction-view-bottom-contents/transaction-view-bottom-contents-container.component'; +import { SystemConfigurationResolverService, ApplicationListResolverService } from 'app/shared/services'; +import { TransactionViewPageComponent } from './transaction-view-page.component'; + +const TO_MAIN = '/' + UrlPath.MAIN; + +export const routing: Routes = [ + { + path: '', + component: TransactionViewPageComponent, + resolve: { + configuration: SystemConfigurationResolverService, + applicationList: ApplicationListResolverService + }, + children: [ + { + path: '', + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.AGENT_ID, + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.AGENT_ID + '/:' + UrlPathId.TRACE_ID, + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.AGENT_ID + '/:' + UrlPathId.TRACE_ID + '/:' + UrlPathId.FOCUS_TIMESTAMP, + redirectTo: TO_MAIN, + pathMatch: 'full' + }, + { + path: ':' + UrlPathId.AGENT_ID + '/:' + UrlPathId.TRACE_ID + '/:' + UrlPathId.FOCUS_TIMESTAMP + '/:' + UrlPathId.SPAN_ID, + children: [ + { + path: '', + component: TransactionViewTopContentsContainerComponent + }, + { + path: '', + component: TransactionViewBottomContentsContainerComponent, + outlet: 'transaction-view-detail' + } + ] + } + ] + } +]; diff --git a/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.css b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.css new file mode 100644 index 000000000000..924588cb7673 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.css @@ -0,0 +1,11 @@ +div { + text-align: center; + padding: 10% 20px 20px 20px; + vertical-align: middle; + height: 100%; +} +img { + width: 80%; + filter: grayscale(90%); + opacity: .5; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.html b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.html new file mode 100644 index 000000000000..8b92dedb9dec --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.spec.ts b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.spec.ts new file mode 100644 index 000000000000..436daa628444 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EmptyContentsComponent } from './empty-contents.component'; + +describe('EmptyContentsComponent', () => { + let component: EmptyContentsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [EmptyContentsComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EmptyContentsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.ts b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.ts new file mode 100644 index 000000000000..75d207214166 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/empty-contents.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService } from 'app/shared/services'; + +@Component({ + selector: 'pp-empty-contents', + templateUrl: './empty-contents.component.html', + styleUrls: ['./empty-contents.component.css'] +}) +export class EmptyContentsComponent implements OnInit { + hiddenComponent$: Observable; + constructor( + private newUrlStateNotificationService: NewUrlStateNotificationService + ) {} + ngOnInit() { + this.hiddenComponent$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe( + map((urlService: NewUrlStateNotificationService) => { + return !urlService.hasValue(UrlPathId.PERIOD, UrlPathId.END_TIME); + }) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/components/empty-contents/index.ts b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/index.ts new file mode 100644 index 000000000000..3dd9c774a2fe --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/index.ts @@ -0,0 +1,2 @@ +export { EmptyContentsComponent } from './empty-contents.component'; +export { NoneComponent } from './none.component'; diff --git a/web/src/main/webapp/v2/src/app/shared/components/empty-contents/none.component.ts b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/none.component.ts new file mode 100644 index 000000000000..ee1f6360e957 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/empty-contents/none.component.ts @@ -0,0 +1,10 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'pp-none', + template: '' +}) +export class NoneComponent implements OnInit { + constructor() {} + ngOnInit() {} +} diff --git a/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/film-for-disable.component.css b/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/film-for-disable.component.css new file mode 100644 index 000000000000..1eca87e53427 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/film-for-disable.component.css @@ -0,0 +1,7 @@ +.l-disabled { + top: 0px; + left: 0px; + height: 100%; + position: absolute; + background: linear-gradient(135deg, rgba(226,226,226,0.7) 0%, rgba(219,219,219,0.7) 43%, rgba(209,209,209,0.7) 51%, rgba(254,254,254,0.7) 100%); +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/film-for-disable.component.html b/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/film-for-disable.component.html new file mode 100644 index 000000000000..1ea029adc822 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/film-for-disable.component.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/film-for-disable.component.ts b/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/film-for-disable.component.ts new file mode 100644 index 000000000000..a2daac5c8094 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/film-for-disable.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit, Input, ElementRef, Renderer2 } from '@angular/core'; + +@Component({ + selector: 'pp-film-for-disable', + templateUrl: './film-for-disable.component.html', + styleUrls: ['./film-for-disable.component.css'] +}) +export class FilmForDisableComponent implements OnInit { + @Input() zIndex: number; + @Input() marginWidth: number; + constructor( + private el: ElementRef, + private renderer: Renderer2 + ) {} + + ngOnInit() { + const el = this.el.nativeElement.querySelector('.l-disabled'); + this.renderer.setStyle(el, 'z-index', this.zIndex); + this.renderer.setStyle(el, 'width', `calc(100% - ${this.marginWidth}px)`); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/index.ts b/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/index.ts new file mode 100644 index 000000000000..f929c3b993b9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/film-for-disable/index.ts @@ -0,0 +1 @@ +export { FilmForDisableComponent } from './film-for-disable.component'; diff --git a/web/src/main/webapp/v2/src/app/shared/components/header-logo/header-logo.component.css b/web/src/main/webapp/v2/src/app/shared/components/header-logo/header-logo.component.css new file mode 100644 index 000000000000..c402b32623e5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/header-logo/header-logo.component.css @@ -0,0 +1,14 @@ +.logo { + width: 165px; + height: 100%; + box-shadow: 1px 0 0 #55a2e6; + background: #408dd4; + border-right: 1px solid #3881c4; +} +.logo a { + width:100%; + height:100%; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/web/src/main/webapp/v2/src/app/shared/components/header-logo/header-logo.component.html b/web/src/main/webapp/v2/src/app/shared/components/header-logo/header-logo.component.html new file mode 100644 index 000000000000..4b3cb2522238 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/header-logo/header-logo.component.html @@ -0,0 +1,5 @@ +

+ + PINPOINT LOGO + +

\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/shared/components/header-logo/header-logo.component.ts b/web/src/main/webapp/v2/src/app/shared/components/header-logo/header-logo.component.ts new file mode 100644 index 000000000000..85a998adcea3 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/header-logo/header-logo.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; +import { WebAppSettingDataService } from 'app/shared/services'; + +@Component({ + selector: 'pp-header-logo', + templateUrl: './header-logo.component.html', + styleUrls: ['./header-logo.component.css'] +}) +export class HeaderLogoComponent implements OnInit { + logoPath: string; + constructor( + private webAppSettingDataService: WebAppSettingDataService + ) { } + + ngOnInit() { + this.logoPath = this.webAppSettingDataService.getLogoPath(); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/components/header-logo/index.ts b/web/src/main/webapp/v2/src/app/shared/components/header-logo/index.ts new file mode 100644 index 000000000000..f14db7e5060e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/header-logo/index.ts @@ -0,0 +1 @@ +export { HeaderLogoComponent } from './header-logo.component'; diff --git a/web/src/main/webapp/v2/src/app/shared/components/index.ts b/web/src/main/webapp/v2/src/app/shared/components/index.ts new file mode 100644 index 000000000000..61b8de77f0b1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/index.ts @@ -0,0 +1,5 @@ +import { EmptyContentsComponent } from './empty-contents'; + +export const SHARED_COMPONENTS = [ + EmptyContentsComponent +]; diff --git a/web/src/main/webapp/v2/src/app/shared/components/loading/index.ts b/web/src/main/webapp/v2/src/app/shared/components/loading/index.ts new file mode 100644 index 000000000000..5d28caaf62c9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/loading/index.ts @@ -0,0 +1 @@ +export { LoadingComponent } from './loading.component'; diff --git a/web/src/main/webapp/v2/src/app/shared/components/loading/loading.component.css b/web/src/main/webapp/v2/src/app/shared/components/loading/loading.component.css new file mode 100644 index 000000000000..c196e42aabb8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/loading/loading.component.css @@ -0,0 +1,5 @@ +:host { + position: absolute; + top: 40%; + left: 47%; +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/shared/components/loading/loading.component.html b/web/src/main/webapp/v2/src/app/shared/components/loading/loading.component.html new file mode 100644 index 000000000000..3c612393afce --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/loading/loading.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/shared/components/loading/loading.component.ts b/web/src/main/webapp/v2/src/app/shared/components/loading/loading.component.ts new file mode 100644 index 000000000000..0cf5be37bdaa --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/loading/loading.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit, Input, ElementRef, Renderer2 } from '@angular/core'; +import { style, animate, transition, trigger } from '@angular/animations'; + +@Component({ + selector: 'pp-loading', + animations: [ + trigger('fadeOut', [ + transition(':leave', [ // is alias to '* => void' + animate(1000, style({ + opacity: 0 + })) + ]) + ]) + ], + templateUrl: './loading.component.html', + styleUrls: ['./loading.component.css'] +}) +export class LoadingComponent implements OnInit { + @Input() showLoading: boolean; + @Input() zIndex: number; + + constructor( + private el: ElementRef, + private renderer: Renderer2 + ) {} + + ngOnInit() { + this.renderer.setStyle(this.el.nativeElement, 'z-index', this.zIndex); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/components/page-not-found/index.ts b/web/src/main/webapp/v2/src/app/shared/components/page-not-found/index.ts new file mode 100644 index 000000000000..59f9c154443e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/page-not-found/index.ts @@ -0,0 +1 @@ +export * from './page-not-found.component'; diff --git a/web/src/main/webapp/v2/src/app/shared/components/page-not-found/page-not-found.component.css b/web/src/main/webapp/v2/src/app/shared/components/page-not-found/page-not-found.component.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/app/shared/components/page-not-found/page-not-found.component.html b/web/src/main/webapp/v2/src/app/shared/components/page-not-found/page-not-found.component.html new file mode 100644 index 000000000000..61ed4e6a016d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/page-not-found/page-not-found.component.html @@ -0,0 +1,5 @@ +
+
+ Page Not Found 사공사 +
+
\ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/shared/components/page-not-found/page-not-found.component.ts b/web/src/main/webapp/v2/src/app/shared/components/page-not-found/page-not-found.component.ts new file mode 100644 index 000000000000..6d7496a76210 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/page-not-found/page-not-found.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'pp-page-not-found', + templateUrl: './page-not-found.component.html', + styleUrls: ['./page-not-found.component.css'] +}) +export class PageNotFoundComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/index.ts b/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/index.ts new file mode 100644 index 000000000000..f1bc08c2e1b6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/index.ts @@ -0,0 +1 @@ +export * from './simple-progress-slider.component'; diff --git a/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/simple-progress-slider.component.css b/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/simple-progress-slider.component.css new file mode 100644 index 000000000000..bba669ac8b77 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/simple-progress-slider.component.css @@ -0,0 +1,132 @@ + :host { + width: 100%; + } + .slider-for-progress { + width: 100%; + padding-top: 10px; + } + .slider-for-progress .sfp-line { + width: 100%; + height: 14px; + position: relative; + } + .slider-for-progress .sfp-line div, .slider-for-progress .sfp-line span { + position: absolute; + } + .slider-for-progress .sfp-handle { + top: -4px; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: #FFF; + box-shadow: 1px 1px 3px 1px rgba(0,0,0,0.4); + } + .slider-for-progress .sfp-handle span { + top: 3px; + left: 3px; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #4b8ed4; + } + .slider-for-progress .sfp-left-handler { + left: 100%; /* @script position reset */ + margin-left: -7px; + } + .slider-for-progress .sfp-right-handler { + right: 0px; /* @script position reset */ + margin-right: -7px; + } + .slider-for-progress .sfp-label { + top: -2px; + position: absolute; + font-size: 10px; + } + .slider-for-progress .sfp-label span { + border: 1px solid #3e83c6; + padding: 2px 4px 1px 4px; + position: relative; + background-color: #FFF; + box-shadow: 1px 1px 3px 1px rgba(0,0,0,0.4); + } + .slider-for-progress .sfp-label span:before, .slider-for-progress .sfp-label span:after { + top: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + } + .slider-for-progress .sfp-label span:before { + border-color: rgba(62, 131, 198, 0); + border-width: 5px; + margin-top: -5px; + } + .slider-for-progress .sfp-label span:after { + border-color: rgba(255, 255, 255, 0); + border-width: 4px; + margin-top: -4px; + } + .slider-for-progress .sfp-label-right-arrow span:before, .slider-for-progress .sfp-label-right-arrow span:after { + left: 100%; + } + .slider-for-progress .sfp-label-right-arrow span:before { + border-left-color: #3e83c6; + } + .slider-for-progress .sfp-label-right-arrow span:after { + border-left-color: #ffffff; + } + .slider-for-progress .sfp-label-left-arrow span:before, .slider-for-progress .sfp-label-left-arrow span:after { + right: 100%; + } + .slider-for-progress .sfp-label-left-arrow span:before { + border-right-color: #3e83c6; + } + .slider-for-progress .sfp-label-left-arrow span:after { + border-right-color: #ffffff; + } + + .slider-for-progress .sfp-label-right-arrow { + margin-left: -50px; + left: 0%;/* @script position reset */ + } + .slider-for-progress .sfp-label-left-arrow { + margin-left: 13px; + left: 0%; /* @script position reset */ + } + .slider-for-progress .sfp-value { + width: 100%; + height: 20px; + } + .slider-for-progress .sfp-background { + top: 0px; + left: 0px; + width: 100%; + height: 7px; + background-color: #c9def3; + } + .slider-for-progress .sfp-foreground { + top: 0px; + right: 0px; + width: 0px; /* @script position reset */ + height: 7px; + background-color: #3dcfa8; + } + .slider-for-progress .sfp-value { + color: #FFF; + width: 100%; + position: relative; + font-size: 10px; + } + .slider-for-progress .sfp-value span { + position: absolute; + } + .slider-for-progress .sfp-value .first { + left: 0px; + margin-left: -15px; + } + .slider-for-progress .sfp-value .last { + right: 0px; + margin-right: -15px; + } \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/simple-progress-slider.component.html b/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/simple-progress-slider.component.html new file mode 100644 index 000000000000..f47b5a4c61f9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/simple-progress-slider.component.html @@ -0,0 +1,19 @@ +
+
+
+
+
+
+
+ {{selectedStartValue}} +
+
+ {{selectedStartValue}} +
+
+
+ {{rangeStartValue}} + {{d.value}} + {{rangeEndValue}} +
+
diff --git a/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/simple-progress-slider.component.ts b/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/simple-progress-slider.component.ts new file mode 100644 index 000000000000..982a95bd819b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/simple-progress-slider/simple-progress-slider.component.ts @@ -0,0 +1,145 @@ +import { Component, OnInit, OnChanges, SimpleChanges, Input } from '@angular/core'; +import { trigger, state, style, animate, transition } from '@angular/animations'; +import * as moment from 'moment-timezone'; + +interface IRangePosition { + value: string; + position: number; +} + +@Component({ + selector: 'pp-simple-progress-slider', + templateUrl: './simple-progress-slider.component.html', + styleUrls: ['./simple-progress-slider.component.css'], + animations: [ + trigger('handlerTrigger', [ + state('start', style({ + left: '100%' + })), + state(':enter', style({ + left: '100%' + })), + state('next', style({ + left: '{{value}}' + }), {params: {value: '100%'}}), + state('andNext', style({ + left: '{{value}}' + }), {params: {value: '100%'}}), + transition('* => *', [ + animate('0.3s 0.3s ease-out') + ]) + ]), + trigger('barTrigger', [ + state('start', style({ + width: '0%' + })), + state(':enter', style({ + width: '0%' + })), + state('next', style({ + width: '{{value}}' + }), {params: {value: '0%'}}), + state('andNext', style({ + width: '{{value}}' + }), {params: {value: '0%'}}), + transition('* => *', [ + animate('0.3s 0.3s ease-out') + ]) + ]), + ] +}) +export class SimpleProgressSliderComponent implements OnInit, OnChanges { + @Input() timezone: string; + @Input() dateFormat: string; + @Input() rangeValue: number[] = [0, 100]; + @Input() selectedRangeValue: number[] = [100, 100]; + @Input() type = 'date'; // count + @Input() gradationCount = 6; + private triggerStep: { [key: string]: string } = { + start: 'next', + next: 'andNext', + andNext: 'next' + }; + barTrigger = 'start'; + handlerTrigger = 'start'; + showLabel = false; + rangeStartValue = ''; + rangeEndValue = ''; + selectedStartValue = ''; + gradationValue: IRangePosition[] = []; + selectedStartPosition = 100; + constructor() {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if (changes['rangeValue'] && changes['rangeValue']['currentValue']) { + this.initXAxis(); + } + if (changes['selectedRangeValue'] && changes['selectedRangeValue']['currentValue']) { + this.selectedStartValue = this.convertToType(this.selectedRangeValue[0]); + this.calcuSelectedStartPosition(); + this.setAnimationNextStep(); + } + if (changes['timezone'] && changes['timezone'].firstChange === false) { + this.changeTimeDisplay(); + } + if (changes['dateFormat'] && changes['dateFormat'].firstChange === false) { + this.changeTimeDisplay(); + } + } + private setAnimationNextStep(): void { + this.barTrigger = this.triggerStep[this.barTrigger]; + this.handlerTrigger = this.triggerStep[this.handlerTrigger]; + } + private initXAxis(): void { + this.rangeStartValue = this.convertToType(this.rangeValue[0]); + this.rangeEndValue = this.convertToType(this.rangeValue[1]); + this.calcuGradationValue(); + } + private changeTimeDisplay(): void { + this.initXAxis(); + this.selectedStartValue = this.convertToType(this.selectedRangeValue[0]); + } + animationStart($event: any): void { + this.showLabel = false; + } + animationDone($event: any): void { + if ($event.fromState !== 'void') { + this.showLabel = true; + } + } + hiddenRightArrowLabel(): boolean { + return this.selectedStartPosition < 50 || this.showLabel === false; + } + hiddenLeftArrowLabel(): boolean { + return this.selectedStartPosition >= 50 || this.showLabel === false; + } + convertToType(value: number): string { + switch (this.type) { + case 'count': + return value.toString(); + case 'date': + return moment(value).tz(this.timezone).format(this.dateFormat); + default: + return value.toString(); + } + } + calcuGradationValue(): void { + const gradationCount = this.gradationCount - 1; + const gap = this.rangeValue[1] - this.rangeValue[0]; + const gapPosition = 100 / gradationCount; + const gapValue = gap / gradationCount; + this.gradationValue.length = 0; + for (let i = 1; i <= gradationCount - 1; i++) { + this.gradationValue.push({ + value: this.convertToType(this.rangeValue[0] + (gapValue * i)), + position: (gapPosition * i), + }); + } + } + calcuSelectedStartPosition(): void { + const gap = this.rangeValue[1] - this.rangeValue[0]; + const selectedGap = this.selectedRangeValue[1] - this.selectedRangeValue[0]; + const tempStartPosition = 100 - (selectedGap * 100) / gap; + this.selectedStartPosition = Math.min(100, Math.max(0, tempStartPosition)); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/components/url-redirector/index.ts b/web/src/main/webapp/v2/src/app/shared/components/url-redirector/index.ts new file mode 100644 index 000000000000..4182656f6fb7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/url-redirector/index.ts @@ -0,0 +1 @@ +export * from './url-redirector.component'; diff --git a/web/src/main/webapp/v2/src/app/shared/components/url-redirector/url-redirector.component.ts b/web/src/main/webapp/v2/src/app/shared/components/url-redirector/url-redirector.component.ts new file mode 100644 index 000000000000..4683d5bab98f --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/components/url-redirector/url-redirector.component.ts @@ -0,0 +1,59 @@ +import { Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { WebAppSettingDataService, UrlRouteManagerService } from 'app/shared/services'; + +@Component({ + selector: 'pp-url-redirector', + template: '' +}) +export class UrlRedirectorComponent { + + constructor( + private activatedRoute: ActivatedRoute, + private webAppSettingDataService: WebAppSettingDataService, + private urlRouteManagerService: UrlRouteManagerService, + ) { + // @ALERT + // URL Redirector가 호출되는 상황은 RouteInfoCollectorService가 호출되지 않는 상황이기 때문에 + // RouteInfoCollectorService를 통해 초기화 되는 UrlStateNotifactionService 의 URL 초기화 정보를 사용하면 안됨. + this.activatedRoute.data.subscribe((urlData: any) => { + const params = this.getUrlParams(); + if (params.period) { + this.urlRouteManagerService.move({ + url: [ + urlData['path'] || params.startPath, + params.application, + params.period + ], + needServerTimeRequest: true + }); + } else { + this.urlRouteManagerService.move({ + url: [ + urlData['path'] || params.startPath, + params.application, + this.webAppSettingDataService.getUserDefaultPeriod().getValueWithTime() + ], + needServerTimeRequest: true + }); + } + }); + } + private getUrlParams(): any { + const params: { [key: string]: string } = {}; + let activatedRoute: ActivatedRoute | null = this.activatedRoute; + while ( activatedRoute ) { + activatedRoute.snapshot.paramMap.keys.forEach((key: string) => { + params[key] = activatedRoute.snapshot.paramMap.get(key); + }); + if ( activatedRoute.parent === null ) { + break; + } else { + activatedRoute = activatedRoute.parent; + } + } + params['startPath'] = activatedRoute.snapshot.root.firstChild.url[0].path; + return params; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/directives/context-popup.directive.ts b/web/src/main/webapp/v2/src/app/shared/directives/context-popup.directive.ts new file mode 100644 index 000000000000..ef30574d50f9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/directives/context-popup.directive.ts @@ -0,0 +1,8 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +@Directive({ + selector: '[ppContextPopup]', +}) +export class ContextPopupDirective { + constructor(public viewContainerRef: ViewContainerRef) {} +} diff --git a/web/src/main/webapp/v2/src/app/shared/directives/index.ts b/web/src/main/webapp/v2/src/app/shared/directives/index.ts new file mode 100644 index 000000000000..4e3066282c30 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/directives/index.ts @@ -0,0 +1,9 @@ +import { SettingHeightDirective } from './setting-height.directive'; +import { ContextPopupDirective } from './context-popup.directive'; +import { SplitterDirective } from './splitter.directive'; + +export const DIRECTIVES = [ + SettingHeightDirective, + ContextPopupDirective, + SplitterDirective +]; diff --git a/web/src/main/webapp/v2/src/app/shared/directives/search-input.directive.ts b/web/src/main/webapp/v2/src/app/shared/directives/search-input.directive.ts new file mode 100644 index 000000000000..bcdaa6c2bdcf --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/directives/search-input.directive.ts @@ -0,0 +1,83 @@ +import { Directive, OnInit, OnChanges, OnDestroy, SimpleChanges, EventEmitter, HostListener, Input, Output } from '@angular/core'; +import { Subject } from 'rxjs'; +import { debounceTime, distinctUntilChanged, filter, takeUntil } from 'rxjs/operators'; + +@Directive({ + selector: '[ppSearchInput]' +}) +export class SearchInputDirective implements OnInit, OnChanges, OnDestroy { + @Input() searchMinLength = 0; + @Input() searchMaxLength = Number.MAX_SAFE_INTEGER; + @Input() useEnter: boolean; + @Input() debounceTime = 100; + @Output() outSearch: EventEmitter = new EventEmitter(); + @Output() outArrowKey: EventEmitter = new EventEmitter(); + private unsubscribe: Subject = new Subject(); + private userInput: Subject = new Subject(); + + @HostListener('keydown', ['$event']) onKeyDown($event: KeyboardEvent): void { + const keyCode = $event.keyCode; + if (this.isArrowKey(keyCode)) { + this.outArrowKey.emit(keyCode); + return; + } + } + @HostListener('keyup', ['$event']) onKeyUp($event: KeyboardEvent): void { + const keyCode = $event.keyCode; + const element = ($event.srcElement as HTMLInputElement); + const value = element.value.trim(); + if (this.isArrowKey(keyCode)) { + return; + } + if (this.isESC(keyCode)) { + element.value = ''; + return; + } + if (this.useEnter) { + if (this.isEnter(keyCode) && this.isValidLength(value)) { + this.outSearch.next(value); + } + } else { + this.userInput.next(value); + } + } + constructor() {} + ngOnInit() {} + ngOnChanges(changes: SimpleChanges) { + if (changes['useEnter']) { + if (this.useEnter) { + this.unsubscribe.next(); + } else { + this.setObservable(); + } + } + } + ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + private setObservable(): void { + this.userInput.pipe( + takeUntil(this.unsubscribe), + debounceTime(this.debounceTime), + distinctUntilChanged(), + filter((query: string) => { + return this.isValidLength(query); + }) + ).subscribe((query: string) => { + this.outSearch.next(query); + }); + } + private isValidLength(value: string): boolean { + return value.length === 0 || (value.length >= this.searchMinLength && value.length < this.searchMaxLength); + } + private isESC(key: number): boolean { + return key === 27; + } + private isEnter(key: number): boolean { + return key === 13; + } + private isArrowKey(key: number): boolean { + return key >= 37 && key <= 40; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/directives/setting-height.directive.ts b/web/src/main/webapp/v2/src/app/shared/directives/setting-height.directive.ts new file mode 100644 index 000000000000..92d173051cfc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/directives/setting-height.directive.ts @@ -0,0 +1,38 @@ +import { Directive, ElementRef, OnInit, OnChanges, SimpleChanges, Renderer2, Input } from '@angular/core'; +import { WindowRefService } from 'app/shared/services'; + +@Directive({ + selector: '[ppSettingHeight]' +}) +export class SettingHeightDirective implements OnInit, OnChanges { + // tslint:disable-next-line:no-input-rename + @Input('ppSettingHeight') heightConfig: {[key: string]: any}; + + constructor( + private elementRef: ElementRef, + private renderer: Renderer2, + private windowRefService: WindowRefService + ) { } + + ngOnChanges(changes: SimpleChanges) { + this.setHeight(this.getHeightValue()); + } + + ngOnInit() { + } + + private getHeightValue(): string { + return this.heightConfig.height || (this.heightConfig.setHeightAuto ? 'auto' : this.getComputedHeight()); + } + + private getComputedHeight(): string { + const width = this.windowRefService.nativeWindow.getComputedStyle(this.elementRef.nativeElement).getPropertyValue('width'); + const height = Number(width.replace(/px/, '')) / this.heightConfig.ratio; + + return height + 'px'; + } + + private setHeight(height: string): void { + this.renderer.setStyle(this.elementRef.nativeElement, 'height', height); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/directives/splitter.directive.ts b/web/src/main/webapp/v2/src/app/shared/directives/splitter.directive.ts new file mode 100644 index 000000000000..0997ad82515c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/directives/splitter.directive.ts @@ -0,0 +1,20 @@ +import { Directive, OnInit, HostListener } from '@angular/core'; +import { GutterEventService } from 'app/shared/services'; + +@Directive({ + selector: '[ppSplitter]' +}) +export class SplitterDirective implements OnInit { + constructor( + private gutterEventService: GutterEventService + ) { } + + ngOnInit() { + } + + @HostListener('dragEnd', ['$event']) + @HostListener('dragProgress', ['$event']) + onDragEndProgress({sizes}: {sizes: number[]}) { + this.gutterEventService.resizedGutter(sizes); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/index.ts b/web/src/main/webapp/v2/src/app/shared/index.ts new file mode 100644 index 000000000000..f1499d1a1708 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/index.ts @@ -0,0 +1,107 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { ClipboardModule } from 'ngx-clipboard'; + +import { ClickOutsideModule } from 'ng-click-outside'; +import { TranslateReplaceService } from './services/translate-replace.service'; +import { ServerTimeDataService } from './services/server-time-data.service'; +import { WebAppSettingDataService } from './services/web-app-setting-data.service'; +import { ComponentDefaultSettingDataService } from './services/component-default-setting-data.service'; +import { RouteInfoCollectorService } from './services/route-info-collector.service'; +import { ServerTimeResolverService } from './services/server-time-resolver.service'; +import { NewUrlStateNotificationService } from './services/new-url-state-notification.service'; +import { UrlRouteManagerService } from './services/url-route-manager.service'; +import { SystemConfigurationDataService } from './services/system-configuration-data.service'; +import { SystemConfigurationResolverService } from './services/system-configuration-resolver.service'; +import { SplitRatioService } from './services/split-ratio.service'; +import { GutterEventService } from './services/gutter-event.service'; +import { AjaxExceptionCheckerService } from './services/ajax-exception-checker.service'; +import { ApplicationListResolverService } from './services/application-list-resolver.service'; +import { AnalyticsService } from './services/analytics.service'; +import { BrowserSupportCheckService } from './services/browser-support-check.service'; +import { AgentHistogramDataService } from './services/agent-histogram-data.service'; +import { TransactionDetailDataService } from './services/transaction-detail-data.service'; +import { TransactionViewTypeService } from './services/transaction-view-type.service'; +import { StoreHelperService } from './services/store-helper.service'; +import { UrlValidateGuard } from './services/url-validate.guard'; + +import { HeaderLogoComponent } from './components/header-logo'; +import { EmptyContentsComponent, NoneComponent } from './components/empty-contents'; +import { UrlRedirectorComponent } from './components/url-redirector'; +import { LoadingComponent } from './components/loading'; +import { FilmForDisableComponent } from './components/film-for-disable'; +import { SimpleProgressSliderComponent } from './components/simple-progress-slider'; +import { SettingHeightDirective } from './directives/setting-height.directive'; +import { ContextPopupDirective } from './directives/context-popup.directive'; +import { SplitterDirective } from './directives/splitter.directive'; +import { SearchInputDirective } from './directives/search-input.directive'; +import { SafeHtmlPipe } from './pipes/safe-html.pipe'; +import { JSONTextParserPipe } from './pipes/json-text-parser.pipe'; +import { DynamicPopupService } from 'app/shared/services/dynamic-popup.service'; + +@NgModule({ + declarations: [ + NoneComponent, + HeaderLogoComponent, + EmptyContentsComponent, + UrlRedirectorComponent, + LoadingComponent, + FilmForDisableComponent, + SimpleProgressSliderComponent, + SafeHtmlPipe, + JSONTextParserPipe, + SettingHeightDirective, + ContextPopupDirective, + SplitterDirective, + SearchInputDirective + ], + imports: [ + CommonModule, + ClickOutsideModule + ], + exports: [ + CommonModule, + FormsModule, + ClipboardModule, + ClickOutsideModule, + HeaderLogoComponent, + EmptyContentsComponent, + UrlRedirectorComponent, + LoadingComponent, + FilmForDisableComponent, + SimpleProgressSliderComponent, + SafeHtmlPipe, + JSONTextParserPipe, + SettingHeightDirective, + ContextPopupDirective, + SplitterDirective, + SearchInputDirective + ], + providers: [ + TranslateReplaceService, + ServerTimeDataService, + ServerTimeResolverService, + ComponentDefaultSettingDataService, + RouteInfoCollectorService, + WebAppSettingDataService, + NewUrlStateNotificationService, + UrlRouteManagerService, + SystemConfigurationDataService, + SystemConfigurationResolverService, + SplitRatioService, + GutterEventService, + AjaxExceptionCheckerService, + ApplicationListResolverService, + AnalyticsService, + BrowserSupportCheckService, + AgentHistogramDataService, + TransactionDetailDataService, + TransactionViewTypeService, + StoreHelperService, + UrlValidateGuard, + DynamicPopupService + ] +}) +export class SharedModule { } diff --git a/web/src/main/webapp/v2/src/app/shared/models/app-state.ts b/web/src/main/webapp/v2/src/app/shared/models/app-state.ts new file mode 100644 index 000000000000..6ba6b8b5fcb7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/models/app-state.ts @@ -0,0 +1,4 @@ +export interface AppState { + timezone: string; + [key: string]: any; +} diff --git a/web/src/main/webapp/v2/src/app/shared/models/index.ts b/web/src/main/webapp/v2/src/app/shared/models/index.ts new file mode 100644 index 000000000000..2c10bf1a61e2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/models/index.ts @@ -0,0 +1,3 @@ +export * from './url-path'; +export * from './url-path-id'; +export * from './url-query'; diff --git a/web/src/main/webapp/v2/src/app/shared/models/url-path-id.ts b/web/src/main/webapp/v2/src/app/shared/models/url-path-id.ts new file mode 100644 index 000000000000..08ed675936be --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/models/url-path-id.ts @@ -0,0 +1,142 @@ +import { Application, Period, EndTime } from 'app/core/models'; + +export interface IUrlPathId { + get(): T; + equals(value: IUrlPathId): boolean; + toString(): string; +} + +export class UrlApplication implements IUrlPathId { + constructor(private application: IApplication) {} + equals(value: IUrlPathId): boolean { + if (value === null) { + return false; + } + return this.application.equals(value.get()); + } + get(): IApplication { + return this.application; + } + toString(): string { + return this.application.toString(); + } +} +export class UrlPeriod implements IUrlPathId { + constructor(private period: Period) {} + equals(value: IUrlPathId): boolean { + if (value === null) { + return false; + } + return this.period.equals(value.get()); + } + get(): Period { + return this.period; + } + toString(): string { + return this.period.toString(); + } +} +export class UrlEndTime implements IUrlPathId { + constructor(private endTime: EndTime) {} + equals(value: IUrlPathId): boolean { + if (value === null) { + return false; + } + return this.endTime.equals(value.get()); + } + get(): EndTime { + return this.endTime; + } + toString(): string { + return this.endTime.toString(); + } +} +export class UrlGeneral implements IUrlPathId { + constructor(private value: T) {} + equals(target: IUrlPathId): boolean { + if (target === null) { + return false; + } + return this.value.toString() === target.toString(); + } + get(): T { + return this.value; + } + toString(): string { + return this.value.toString(); + } +} + +export class UrlPathId { + static APPLICATION = 'application'; + static PERIOD = 'period'; + static END_TIME = 'endTime'; + static FILTER = 'filter'; + static HINT = 'hint'; + static REAL_TIME = 'realtime'; + static AGENT_ID = 'agentId'; + static TRANSACTION_INFO = 'transactionInfo'; + static TRACE_ID = 'traceId'; + static FOCUS_TIMESTAMP = 'focusTimestamp'; + static SPAN_ID = 'spanId'; + static VIEW_TYPE = 'viewType'; + static AGENT_LIST = 'agentList'; + static PAGE = 'page'; + static SEARCH_ID = 'searchId'; + static STAT = 'stat'; + static AGENT = 'agent'; + + constructor() {} + static getPathIdList(): string[] { + return [ + UrlPathId.AGENT_LIST, + UrlPathId.AGENT_ID, + UrlPathId.APPLICATION, + UrlPathId.END_TIME, + UrlPathId.FILTER, + UrlPathId.FOCUS_TIMESTAMP, + UrlPathId.HINT, + UrlPathId.PAGE, + UrlPathId.PERIOD, + UrlPathId.REAL_TIME, + UrlPathId.SEARCH_ID, + UrlPathId.SPAN_ID, + UrlPathId.TRACE_ID, + UrlPathId.TRANSACTION_INFO, + UrlPathId.VIEW_TYPE, + UrlPathId.STAT, + UrlPathId.AGENT + ]; + } +} + +export class UrlPathIdFactory { + constructor() {} + static createPath(paramName: string, paramValue: string): IUrlPathId { + switch (paramName) { + case UrlPathId.APPLICATION: + const params = paramValue.split('@'); + return new UrlApplication(new Application(params[0], params[1], 0)) as IUrlPathId; + case UrlPathId.PERIOD: + return new UrlPeriod(new Period(Period.parseToMinute(paramValue))) as IUrlPathId; + case UrlPathId.END_TIME: + return new UrlEndTime(new EndTime(paramValue)) as IUrlPathId; + case UrlPathId.PAGE: + return new UrlGeneral(paramValue || '2') as IUrlPathId; + case UrlPathId.FILTER: + case UrlPathId.HINT: + case UrlPathId.REAL_TIME: + case UrlPathId.AGENT_ID: + case UrlPathId.TRANSACTION_INFO: + case UrlPathId.TRACE_ID: + case UrlPathId.FOCUS_TIMESTAMP: + case UrlPathId.SPAN_ID: + case UrlPathId.VIEW_TYPE: + case UrlPathId.AGENT_LIST: + case UrlPathId.SEARCH_ID: + return new UrlGeneral(paramValue) as IUrlPathId; + default: + return new UrlGeneral(paramValue) as IUrlPathId; + } + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/models/url-path.ts b/web/src/main/webapp/v2/src/app/shared/models/url-path.ts new file mode 100644 index 000000000000..963733fd13b1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/models/url-path.ts @@ -0,0 +1,31 @@ +export class UrlPath { + static ADMIN = 'admin'; + static FILTERED_MAP = 'filteredMap'; + static INSPECTOR = 'inspector'; + static MAIN = 'main'; + static REAL_TIME = 'realtime'; + static SCATTER_FULL_SCREEN_MODE = 'scatterFullScreenMode'; + static THREAD_DUMP = 'threadDump'; + static TRANSACTION_DETAIL = 'transactionDetail'; + static TRANSACTION_LIST = 'transactionList'; + static TRANSACTION_VIEW = 'transactionView'; + static BROWSER_NOT_SUPPORT = 'browserNotSupported'; + + constructor() {} + static getParamList(): string[] { + return [ + UrlPath.ADMIN, + UrlPath.FILTERED_MAP, + UrlPath.INSPECTOR, + UrlPath.MAIN, + UrlPath.REAL_TIME, + UrlPath.SCATTER_FULL_SCREEN_MODE, + UrlPath.THREAD_DUMP, + UrlPath.TRANSACTION_DETAIL, + UrlPath.TRANSACTION_LIST, + UrlPath.TRANSACTION_VIEW + ]; + } +} + +export default UrlPath; diff --git a/web/src/main/webapp/v2/src/app/shared/models/url-query.ts b/web/src/main/webapp/v2/src/app/shared/models/url-query.ts new file mode 100644 index 000000000000..510de9a2819c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/models/url-query.ts @@ -0,0 +1,54 @@ +export interface IUrlQuery { + get(): T; + equals(value: IUrlQuery): boolean; + toString(): string; +} + +export class UrlQueryClass implements IUrlQuery { + constructor(private value: T) {} + equals(target: IUrlQuery): boolean { + if (target === null) { + return false; + } + return this.value.toString() === target.toString(); + } + get(): T { + return this.value; + } + toString(): string { + return this.value.toString(); + } +} + +export class UrlQuery { + static BIDIRECTIONAL = 'bidirectional'; + static INBOUND = 'inbound'; + static OUTBOUND = 'outbound'; + static WAS_ONLY = 'wasOnly'; + + constructor() {} + static getQueryList(): string[] { + return [ + UrlQuery.BIDIRECTIONAL, + UrlQuery.INBOUND, + UrlQuery.OUTBOUND, + UrlQuery.WAS_ONLY, + ]; + } +} + +export class UrlQueryFactory { + constructor() {} + static createQuery(queryName: string, queryValue?: T): IUrlQuery { + switch (queryName) { + case UrlQuery.INBOUND: + case UrlQuery.OUTBOUND: + return new UrlQueryClass(queryValue) as IUrlQuery; + case UrlQuery.BIDIRECTIONAL: + case UrlQuery.WAS_ONLY: + return new UrlQueryClass(queryValue) as IUrlQuery; + default: + return new UrlQueryClass(queryValue) as IUrlQuery; + } + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/pipes/index.ts b/web/src/main/webapp/v2/src/app/shared/pipes/index.ts new file mode 100644 index 000000000000..19c411774261 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/pipes/index.ts @@ -0,0 +1,7 @@ +import { JSONTextParserPipe } from './json-text-parser.pipe'; +import { SafeHtmlPipe } from './safe-html.pipe'; + +export const PIPES = [ + SafeHtmlPipe, + JSONTextParserPipe +]; diff --git a/web/src/main/webapp/v2/src/app/shared/pipes/json-text-parser.pipe.ts b/web/src/main/webapp/v2/src/app/shared/pipes/json-text-parser.pipe.ts new file mode 100644 index 000000000000..e8521cfc8fb6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/pipes/json-text-parser.pipe.ts @@ -0,0 +1,45 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { WebAppSettingDataService } from 'app/shared/services'; +/** + * sson에서 icon, image등의 키워드를 캐치해서 + * 맞는 icon, image로 해당 텍스트를 대체 또는 추가. + * [Type]:{property=value} + * Chaining with '|' + * ex: [ICON]:{className=fa-clock-o\\style=font-size:17px}|[TEXT]:{value=X-Axis} + */ +@Pipe({ name: 'jsonTextParser' }) +export class JSONTextParserPipe implements PipeTransform { + constructor( + private webAppSettingDataService: WebAppSettingDataService + ) {} + + transform(text: string): string { + if (text) { + return text.split('|').map((textElem: string) => { + const i = textElem.indexOf(':'); + const textType = textElem.substr(0, i).replace(/\[|\]/g, ''); + const textInfoArr = textElem.substr(i + 1).replace(/\{|\}/g, '').split('\\').map((textInfo: string) => { + return textInfo.split('=')[1]; + }); + + switch (textType) { + case 'ICON': + return ``; + case 'TEXT': + return textInfoArr[0]; + case 'IMAGE': + const path = this.webAppSettingDataService.getImagePath(); + const extension = this.webAppSettingDataService.getImageExt(); + + return ``; + case 'LINK': + return `${textInfoArr[3]}`; + default: + return text; + } + }).join(' '); + } else { + return ''; + } + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/pipes/safe-html.pipe.ts b/web/src/main/webapp/v2/src/app/shared/pipes/safe-html.pipe.ts new file mode 100644 index 000000000000..f147803879fc --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/pipes/safe-html.pipe.ts @@ -0,0 +1,13 @@ +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'safeHtml' }) +export class SafeHtmlPipe implements PipeTransform { + constructor( + private sanitized: DomSanitizer + ) {} + + transform(value: string): SafeHtml { + return this.sanitized.bypassSecurityTrustHtml(value); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/agent-histogram-data.service.ts b/web/src/main/webapp/v2/src/app/shared/services/agent-histogram-data.service.ts new file mode 100644 index 000000000000..a42230a35619 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/agent-histogram-data.service.ts @@ -0,0 +1,117 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import * as moment from 'moment-timezone'; +import { Observable } from 'rxjs'; +import { shareReplay } from 'rxjs/operators'; +import { NewUrlStateNotificationService } from 'app/shared/services/new-url-state-notification.service'; + +@Injectable() +export class AgentHistogramDataService { + readonly url = 'getResponseTimeHistogramDataV2.pinpoint'; + + private previousFrom: number; + private previousTo: number; + private previousName: string; + private previousCode: string; + private previousKey: string; + private previousObservable: any; + constructor( + private http: HttpClient, + private newUrlStateNotificationService: NewUrlStateNotificationService + ) {} + getData(key: string, applicationName: string, serviceTypeCode: string, serverMapData: any, from?: number, to?: number): Observable { + if (this.isCached(key, applicationName, serviceTypeCode, from, to) === false) { + this.previousObservable = this.http.post(this.url, this.makeBodyData(key, serverMapData), this.makeRequestOptionsArgs(applicationName, serviceTypeCode, from, to)).pipe( + shareReplay(1) + ); + this.previousFrom = from || this.newUrlStateNotificationService.getStartTimeToNumber(); + this.previousTo = to || this.newUrlStateNotificationService.getEndTimeToNumber(); + this.previousName = applicationName; + this.previousCode = serviceTypeCode; + this.previousKey = key; + } + return this.previousObservable; + } + isCached(key: string, applicationName: string, serviceTypeCode: string, from?: number, to?: number): boolean { + return this.previousCode === serviceTypeCode && + this.previousKey === key && + this.previousName === applicationName && + this.previousFrom === (from || this.newUrlStateNotificationService.getStartTimeToNumber()) && + this.previousTo === (to || this.newUrlStateNotificationService.getEndTimeToNumber()); + } + private makeRequestOptionsArgs(applicationName: string, serviceTypeCode: string, from?: number, to?: number): object { + return { + params: { + applicationName: applicationName, + serviceTypeCode: serviceTypeCode, + from: from || this.newUrlStateNotificationService.getStartTimeToNumber(), + to: to || this.newUrlStateNotificationService.getEndTimeToNumber() + } + }; + } + private makeBodyData(nodeKey: string, serverMapData: any): any { + const linkedNodeData: { [key: string]: any } = { + from: [], + to: [] + }; + serverMapData.linkList.forEach((link: ILinkInfo) => { + if ( link.from === nodeKey ) { + if ( link.targetInfo instanceof Array ) { + link.targetInfo.forEach((targetLinkInfo: ILinkInfo) => { + linkedNodeData.to.push([targetLinkInfo.targetInfo.applicationName, targetLinkInfo.targetInfo.serviceTypeCode]); + }); + } else { + linkedNodeData.to.push([link.targetInfo.applicationName, link.targetInfo.serviceTypeCode]); + } + } else if ( link.to === nodeKey ) { + linkedNodeData.from.push([link.sourceInfo.applicationName, link.sourceInfo.serviceTypeCode]); + } + }); + return linkedNodeData; + } + makeChartDataForResponseSummary(histogramData: IResponseTime | IResponseMilliSecondTime, yMax?: number): any { + let newData: {[key: string]: any}; + if (histogramData) { + newData = { + keys: Object.keys(histogramData), + values: [] + }; + newData['keys'].forEach((key: string, index: number) => { + newData['values'][index] = histogramData[key]; + }); + } else { + return newData; + } + if (yMax) { + newData['max'] = yMax; + } + return newData; + } + makeChartDataForLoad(histogramData: IHistogram[], timezone: string, dateFormat: string[], yMax?: number): any { + let newData: { [key: string]: any }; + if (histogramData) { + newData = { + labels: [], + keyValues: [] + }; + histogramData.forEach((histogram: IHistogram, index: number) => { + newData.keyValues[index] = { + key: histogram.key, + values: [] + }; + histogram.values.forEach((aValue: number[]) => { + newData.keyValues[index].values.push(aValue[1]); + if (index === 0) { + newData.labels.push(moment(aValue[0]).tz(timezone).format(dateFormat[0]) + '#' + moment(aValue[0]).tz(timezone).format(dateFormat[1])); + } + }); + }); + } else { + return newData; + } + if (yMax) { + newData['max'] = yMax; + } + return newData; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/ajax-exception-checker.service.ts b/web/src/main/webapp/v2/src/app/shared/services/ajax-exception-checker.service.ts new file mode 100644 index 000000000000..a54d6fd602e1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/ajax-exception-checker.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class AjaxExceptionCheckerService { + constructor() { + } + + isAjaxException(data: AjaxException | any): data is AjaxException { + return (data).exception !== undefined; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/analytics.service.ts b/web/src/main/webapp/v2/src/app/shared/services/analytics.service.ts new file mode 100644 index 000000000000..6a2369f58ae7 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/analytics.service.ts @@ -0,0 +1,110 @@ +import { Injectable } from '@angular/core'; + +import { WindowRefService } from 'app/shared/services/window-ref.service'; +import { WebAppSettingDataService } from 'app/shared/services/web-app-setting-data.service'; + +export enum TRACKED_EVENT_LIST { + TOGGLE_HELP_VIEWER = 'Toggle HelpViewer', + VERSION = 'Version', + SELECT_APPLICATION = 'Select Application', + SELECT_PERIOD = 'Select Period', + SEARCH_NODE = 'Search Node', + SELECT_APPLICATION_IN_SEARCH_RESULT = 'Select Application in Search Result', + CLICK_NODE = 'Click Node', + CLICK_NODE_IN_GROUPED_VIEW = 'Click Node in Grouped View', + SHOW_GROUPED_NODE_VIEW = 'Show Grouped Node View', + CLICK_LINK = 'Click Link', + CLICK_LINK_IN_GROUPED_VIEW = 'Click Link in GROUPED View', + SHOW_GROUPED_LINK_VIEW = 'Show Grouped Link View', + CLICK_SCATTER_SETTING = 'Click Scatter Setting', + DOWNLOAD_SCATTER = 'Download Scatter', + GO_TO_FULL_SCREEN_SCATTER = 'Go to FullScreen Scatter', + CLICK_RESPONSE_GRAPH = 'Click Response Graph', + CLICK_LOAD_GRAPH = 'Click Load Graph', + SHOW_SERVER_LIST = 'Show Server List', + OPEN_INSPECTOR = 'Open Inspector', + CLICK_FILTER_TRANSACTION = 'Click Filter Transaction', + OPEN_FILTER_TRANSACTION_WIZARD = 'Open Filter Transaction Wizard', + CLICK_callTree = 'Click CallTree Tab', + CLICK_serverMap = 'Click ServerMap Tab', + CLICK_timeline = 'Click Timeline Tab', + SELECT_TRANSACTION = 'Select Transaction', + OPEN_TRANSACTION_VIEW = 'Open Transaction View', + CLICK_heap = 'Click Heap in Transaction View', + CLICK_nonHeap = 'Click Non Heap in Transaction View', + CLICK_cpu = 'Click CPU Load in Transaction View', + REFRESH_SERVER_MAP = 'Refresh Server Map', + SET_SERVER_MAP_OPTION = 'Set ServerMap Option', + PIN_UP_REAL_TIME_CHART = 'Pin up RealTime Chart', + REMOVE_PIN_ON_REAL_TIME_CHART = 'Remove Pin on RealTime Chart', + TOGGLE_SERVER_TYPE_DETAIL = 'Toggle Server Type Detail', + SELECT_AGENT = 'Select Agent', + SET_PERIOD_AS_REAL_TIME = 'Set Period as RealTime', + OPEN_THREAD_DUMP = 'Open Thread Dump', + OPEN_CONFIGURATION_POPUP = 'Open Configuration Popup', + SET_BOUND_IN_CONFIGURATION = 'Set Bound in Configuration', + SET_SEARCH_PERIOD_IN_CONFIGURATION = 'Set Search Period in Configuration', + SET_FAVORITE_APPLICATION_IN_CONFIGURATION = 'Set Favorite Application in Configuration', + SET_TIMEZONE_IN_CONFIGURATION = 'Set Timezone in Configuration', + SET_DATE_FORMAT_IN_CONFIGURATION = 'Set Date Format in Configuration', + TOGGLE_PERIOD_SELECT_TYPE = 'Toggle Period Select Type', + CLICK_MORE_STATE_BUTTON = 'Click MORE State Button', + CLICK_RELOAD_APPLICATION_LIST_BUTTON = 'Click Reload Application List Button', + CLICK_FIXED_PERIOD_MOVE_BUTTON = 'Click Fixed Period Move Button', + OPEN_INSPECTOR_WITH_AGENT = 'Open Inspector with Agent', + OPEN_TRANSACTION_LIST = 'Open Transaction List', + GO_TO_APPLICATION_INSPECTOR = 'Go to Application Inspector', + GO_TO_AGENT_INSPECTOR = 'Go To Agent Inspector', + ZOOM_IN_TIMELINE = 'Zoom in Timeline', + ZOOM_OUT_TIMELINE = 'Zoom out Timeline', + MOVE_TO_PREV_ON_TIMELINE = 'Move to Prev on Timeline', + MOVE_TO_NEXT_ON_TIMELINE = 'Move to Next on Timeline', + MOVE_TO_NOW_ON_TIMELINE = 'Move to Now on Timeline', + CHANGE_POINTING_TIME_ON_TIMELINE = 'Change Pointing Time on Timeline', + CHANGE_SELECTION_RANGE_ON_TIMELINE = 'Change Selection Range on Timeline', + SEARCH_AGENT = 'Search Agent', + SEARCH_TRANSACTION = 'Search Transaction', + SELECT_SQL = 'Select SQL', + OPEN_TRANSACTION_DETAIL = 'Open Transaction Detail', + CHANGE_SCATTER_CHART_STATE = 'Change Scatter Chart State', + TOGGLE_SERVER_MAP_MERGE_STATE = 'Toggle ServerMap Merge State', + SELECT_TRANSACTION_IN_TIMELINE = 'Select Transaction in Timeline', +} + +@Injectable() +export class AnalyticsService { + private sendUsage: boolean; + private currentPage: string; + + constructor( + private windowRefService: WindowRefService, + private webAppSettingDataService: WebAppSettingDataService + ) { + this.webAppSettingDataService.isDataUsageAllowed().subscribe((result: boolean) => { + this.sendUsage = result; + }); + } + + trackPage(pageName: string): void { + if (this.sendUsage) { + if (this.windowRefService.nativeWindow.ga && typeof ga === 'function') { + this.currentPage = pageName; + ga('set', 'page', `/${pageName}`); + ga('send', 'pageview'); + } + } + } + /** + * eventCategory: 각 페이지 별 라우팅 주소 + * eventAction: 액션 정보 ex. 동영상 다운로드 + * eventLabel: 액션에 대한 추가 정보(Optional) ex. 동영상 이름 + * eventValue: 액션에 대한 추가 정보2(Optional) 단, 수치로 제공 ex. 동영상 다운로드 액션 이벤트 발생 시, 다운로드 시간. + */ + trackEvent(eventAction: string, eventLabel?: string, eventValue?: number): void { + if (this.sendUsage) { + if (this.windowRefService.nativeWindow.ga && typeof ga === 'function') { + ga('send', 'event', { eventCategory: this.currentPage, eventAction, eventLabel, eventValue }); + } + } + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/application-list-resolver.service.ts b/web/src/main/webapp/v2/src/app/shared/services/application-list-resolver.service.ts new file mode 100644 index 000000000000..3a0f87b893a0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/application-list-resolver.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; + +import { ApplicationListDataService } from 'app/core/components/application-list/application-list-data.service'; + +@Injectable() +export class ApplicationListResolverService implements Resolve { + constructor( + private applicationListDataService: ApplicationListDataService) { } + resolve(reoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.applicationListDataService.getApplicationList(); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/browser-support-check.service.ts b/web/src/main/webapp/v2/src/app/shared/services/browser-support-check.service.ts new file mode 100644 index 000000000000..32c81df1bec4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/browser-support-check.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core'; +import * as bowser from 'bowser'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { TranslateReplaceService } from 'app/shared/services/translate-replace.service'; + +@Injectable() +export class BrowserSupportCheckService { + // 2018.06.01 기준 지원하는 브라우저 최신버전 + private latestBrowserList = [ + { + name: 'Chrome', + version: 66 + }, { + name: 'Firefox', + version: 60 + }, { + name: 'Safari', + version: 11 + }, { + name: 'Microsoft Edge', + version: 42 + } + ]; + + constructor( + private translateService: TranslateService, + private translateReplaceService: TranslateReplaceService, + ) {} + + private isBrowserSupported(): boolean { + const userBrowserInfo = { + name: bowser.name, + version: Math.trunc(Number(bowser.version)) + }; + + return this.latestBrowserList.findIndex((browserInfo) => { + return browserInfo.name === userBrowserInfo.name && browserInfo.version <= userBrowserInfo.version; + }) !== -1; + } + + getMessage(): Observable { + if (this.isBrowserSupported()) { + return of(''); + } else { + return this.translateService.get('SUPPORT.RESTRICT_USAGE').pipe( + map(((message: string) => { + return this.translateReplaceService.replace(message, bowser.name + ' ' + Math.trunc(Number(bowser.version))); + })) + ); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/component-default-setting-data.service.ts b/web/src/main/webapp/v2/src/app/shared/services/component-default-setting-data.service.ts new file mode 100644 index 000000000000..3104452f3e0c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/component-default-setting-data.service.ts @@ -0,0 +1,86 @@ +import { Injectable } from '@angular/core'; +import { Period } from 'app/core/models/period'; +import { UrlPath } from 'app/shared/models'; +@Injectable() +export class ComponentDefaultSettingDataService { + + private inboundList = ['1', '2', '3', '4']; + private outboundList = ['1', '2', '3', '4']; + private periodList = { + [UrlPath.MAIN]: [ + new Period(5, 'Last'), + new Period(20), + new Period(60), + new Period(180), + new Period(360), + new Period(720), + new Period(1440), + new Period(2880) + ], + [UrlPath.INSPECTOR]: [ + new Period(5, 'Last'), + new Period(20), + new Period(60), + new Period(180), + new Period(360), + new Period(720), + new Period(1440), + new Period(2880), + new Period(10080) + ], + [UrlPath.TRANSACTION_VIEW]: [ + new Period(20), + ] + }; + private maxPeriodTime = 60 * 24 * 2; // 2day + private colorByRequest: string[] = [ + 'rgba(52, 185, 148, 0.5)', // #34b994 + 'rgba(81, 175, 223, 0.5)', // #51afdf + 'rgba(255, 186, 0, 0.5)', // #ffba00 + 'rgba(230, 127, 34, 0.5)', // #e67f22 + 'rgba(233, 84, 89, 0.5)' // #e95459 + ]; + private dateFormatList = [ + // [default, default + timezone, default + millisecond, year+month+day, time, variation1, variation2] + ['YYYY.MM.DD HH:mm:ss', 'YYYY.MM.DD HH:mm:ss Z', 'YYYY.MM.DD HH:mm:ss SSS', 'YYYY.MM.DD', 'HH:mm:ss', 'MM.DD', 'HH:mm'], + ['YYYY.MM.DD h:mm:ss a', 'YYYY.MM.DD h:mm:ss a Z', 'YYYY.MM.DD h:mm:ss SSS a ', 'YYYY.MM.DD', 'h:mm:ss a', 'MM.DD', 'h:mm a'], + ['MMM D, YYYY HH:mm:ss', 'MMM D, YYYY HH:mm:ss Z', 'MMM D, YYYY HH:mm:ss SSS', 'MMM D, YYYY', 'HH:mm:ss', 'MMM D', 'HH:mm'], + ['MMM D, YYYY h:mm:ss a', 'MMM D, YYYY h:mm:ss a Z', 'MMM D, YYYY h:mm:ss SSS a', 'MMM D, YYYY', 'h:mm:ss a', 'MMM D', 'h:mm a'], + ['D MMM YYYY HH:mm:ss', 'D MMM YYYY HH:mm:ss Z', 'D MMM YYYY HH:mm:ss SSS', 'D MMM YYYY', 'HH:mm:ss', 'D MMM', 'HH:mm'], + ['D MMM YYYY h:mm:ss a', 'D MMM YYYY h:mm:ss a Z', 'D MMM YYYY h:mm:ss SSS a', 'D MMM YYYY', 'h:mm:ss a', 'D MMM', 'h:mm a'] + ]; + constructor() {} + getInboundList(): string[] { + return this.inboundList; + } + getOutboundList(): string[] { + return this.outboundList; + } + getPeriodList(path: string): Period[] { + return this.periodList[path]; + } + getSystemDefaultPeriod(): Period { + return this.periodList[UrlPath.MAIN][0]; + } + getSystemDefaultTransactionViewPeriod(): Period { + return this.periodList[UrlPath.TRANSACTION_VIEW][0]; + } + getSystemDefaultInbound(): string { + return this.inboundList[0]; + } + getSystemDefaultOutbound(): string { + return this.outboundList[0]; + } + getMaxPeriodTime(): number { + return this.maxPeriodTime; + } + getColorByRequest(): string[] { + return this.colorByRequest; + } + getDateFormatList(): string[][] { + return this.dateFormatList; + } + getDefaultDateFormat(): string[] { + return this.dateFormatList[4]; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/dynamic-popup.service.ts b/web/src/main/webapp/v2/src/app/shared/services/dynamic-popup.service.ts new file mode 100644 index 000000000000..a0d67e25a6af --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/dynamic-popup.service.ts @@ -0,0 +1,118 @@ +import { + Injectable, + ComponentFactoryResolver, + ComponentRef, + ApplicationRef, + Injector, + EmbeddedViewRef, + Renderer2, + RendererFactory2, + EventEmitter +} from '@angular/core'; + +export interface DynamicPopup { + data?: any; + coord?: ICoordinate; + outCreated?: EventEmitter; + outClose?: EventEmitter; + outReInit?: EventEmitter<{[key: string]: any}>; // HelpViewer처럼 컴포넌트를 아예 destroy하고 새로 init해야 하는 경우 + onInputChange?: Function; +} + +interface IPopupParam { + data?: any; + coord?: ICoordinate; + component: any; + onCloseCallback?: Function; +} + +@Injectable() +export class DynamicPopupService { + private renderer: Renderer2; + private componentRef: ComponentRef; + + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private appRef: ApplicationRef, + private injector: Injector, + rendererFactory: RendererFactory2, + ) { + this.renderer = rendererFactory.createRenderer(null, null); + } + + openPopup($param: IPopupParam): void { + const {data, coord, component} = $param; + + if (!this.componentRef) { + // The very first + this.initPopup($param); + } else if (this.componentRef && this.componentRef.instance instanceof component) { + // Update input binding + this.bindInputProps(this.componentRef.instance, data, coord); + } else { + // Close the current popup and create a new one + this.closePopup(); + this.initPopup($param); + } + } + + closePopup(): void { + this.appRef.detachView(this.componentRef.hostView); + this.componentRef.destroy(); + this.componentRef = null; + } + + private initPopup({data, coord, component, onCloseCallback}: IPopupParam): void { + this.componentRef = this.componentFactoryResolver.resolveComponentFactory(component).create(this.injector); + const popupComponent = this.componentRef.instance; + const domElem = (this.componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement; + + this.bindInputProps(popupComponent, data, coord); + this.bindOutputProps(popupComponent, domElem, onCloseCallback); + this.renderer.addClass(domElem, 'popup'); + this.renderer.appendChild((this.appRef.components[0].location).nativeElement, domElem); + this.appRef.attachView(this.componentRef.hostView); + } + + private bindInputProps(popupComponent: DynamicPopup, data: any, coord: ICoordinate): void { + if (popupComponent.onInputChange) { + popupComponent.onInputChange({data, coord}); + } + if (data) { + popupComponent.data = data; + } + if (coord) { + popupComponent.coord = coord; + } + } + + private bindOutputProps(popupComponent: DynamicPopup, domElem: HTMLElement, onCloseCallback: Function): void { + if (popupComponent.outCreated) { + popupComponent.outCreated.subscribe(({coordX, coordY}: {coordX: number, coordY: number}) => { + this.renderer.setStyle(domElem, 'left', coordX < 0 ? 0 : coordX + 'px'); + this.renderer.setStyle(domElem, 'top', coordY < 0 ? 0 : coordY + 'px'); + }); + } + if (popupComponent.outClose) { + popupComponent.outClose.subscribe(() => { + if (onCloseCallback) { + onCloseCallback(); + } + + this.closePopup(); + }); + } + if (popupComponent.outReInit) { + popupComponent.outReInit.subscribe(({data, coord}: {data: any, coord: ICoordinate}) => { + const component = this.componentRef.componentType; + + this.closePopup(); + this.openPopup({ + data, + coord, + component + }); + }); + } + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/gutter-event.service.ts b/web/src/main/webapp/v2/src/app/shared/services/gutter-event.service.ts new file mode 100644 index 000000000000..42ad42b90844 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/gutter-event.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +import { NewUrlStateNotificationService } from 'app/shared/services/new-url-state-notification.service'; +import { SplitRatioService } from './split-ratio.service'; + +@Injectable() +export class GutterEventService { + private outGutterResized: BehaviorSubject; + onGutterResized$: Observable; + + constructor( + private newUrlStateNotificationService: NewUrlStateNotificationService, + private splitRatioService: SplitRatioService + ) { + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + filter((urlService: NewUrlStateNotificationService) => !!urlService), + ).subscribe((urlService: NewUrlStateNotificationService) => { + const view = urlService.getStartPath(); + + this.outGutterResized = new BehaviorSubject(this.splitRatioService.getSplitRatio(view)); + this.onGutterResized$ = this.outGutterResized.asObservable(); + }); + } + + resizedGutter(sizes: number[]): void { + this.outGutterResized.next(sizes); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/index.ts b/web/src/main/webapp/v2/src/app/shared/services/index.ts new file mode 100644 index 000000000000..77c59487cf97 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/index.ts @@ -0,0 +1,23 @@ +export * from './agent-histogram-data.service'; +export * from './ajax-exception-checker.service'; +export * from './analytics.service'; +export * from './application-list-resolver.service'; +export * from './browser-support-check.service'; +export * from './component-default-setting-data.service'; +export * from './gutter-event.service'; +export * from './new-url-state-notification.service'; +export * from './route-info-collector.service'; +export * from './server-time-data.service'; +export * from './server-time-resolver.service'; +export * from './split-ratio.service'; +export * from './store-helper.service'; +export * from './system-configuration-data.service'; +export * from './system-configuration-resolver.service'; +export * from './transaction-detail-data.service'; +export * from './transaction-view-type.service'; +export * from './translate-replace.service'; +export * from './url-route-manager.service'; +export * from './url-validate.guard'; +export * from './web-app-setting-data.service'; +export * from './window-ref.service'; +export * from './dynamic-popup.service'; diff --git a/web/src/main/webapp/v2/src/app/shared/services/new-url-state-notification.service.ts b/web/src/main/webapp/v2/src/app/shared/services/new-url-state-notification.service.ts new file mode 100644 index 000000000000..902896bc8d17 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/new-url-state-notification.service.ts @@ -0,0 +1,182 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { WebAppSettingDataService } from 'app/shared/services/web-app-setting-data.service'; +import { EndTime } from 'app/core/models/end-time'; +import { UrlPath, UrlPathIdFactory, UrlPathId, IUrlPathId, UrlQueryFactory, UrlQuery, IUrlQuery } from 'app/shared/models'; + +interface IGeneral { + [key: string]: any; +} +interface IUrlState { + [key: string]: { + prev: IUrlPathId | IUrlQuery; + curr: IUrlPathId | IUrlQuery; + }; +} + +@Injectable() +export class NewUrlStateNotificationService { + private startPath: string; + private urlState: IUrlState = {}; + private innerRouteData: IGeneral = {}; + private onUrlStateChange: BehaviorSubject = new BehaviorSubject(null); + + onUrlStateChange$: Observable; + constructor(private webAppSettingDataService: WebAppSettingDataService) { + this.onUrlStateChange$ = this.onUrlStateChange.asObservable(); + this.initState(); + } + private initState(): void { + UrlPathId.getPathIdList().forEach((path: string) => { + this.urlState[path] = { + prev: null, + curr: null + }; + }); + UrlQuery.getQueryList().forEach((query: string) => { + this.urlState[query] = { + prev: null, + curr: null + }; + }); + } + updateUrl(startPath: string, pathParams: IGeneral, queryParams: IGeneral, routeData: IGeneral): void { + const bStartPathChanged = this.updateStartPath(startPath); + const bPathChanged = this.updatePathId(pathParams); + const bQueryChanged = this.updateQuery(queryParams); + this.updateRouteData(routeData); + + if (bStartPathChanged || bPathChanged || bQueryChanged) { + // this.onUrlStateChange.next(this.urlState); + this.onUrlStateChange.next(this); + } + } + private updateStartPath(path: string): boolean { + if ( this.startPath === path ) { + return false; + } else { + this.startPath = path; + return true; + } + } + private updatePathId(pathParams: IGeneral): boolean { + let updated = false; + const hasValuePathIdList: string[] = []; + UrlPathId.getPathIdList().forEach((path: string) => { + if (this.changedPathId(path, pathParams[path])) { + this.changePathIdState(path, pathParams[path] ? UrlPathIdFactory.createPath(path, pathParams[path]) : null); + hasValuePathIdList.push(path); + updated = true; + } + }); + this.setConnectedPath(hasValuePathIdList, pathParams); + return updated; + } + private changedPathId(path: string, pathValue: any): boolean { + if (pathValue === null || pathValue === undefined) { + return this.urlState[path].curr !== null; + } else { + if (this.urlState[path].curr === null) { + return true; + } else { + return !UrlPathIdFactory.createPath(path, pathValue).equals(this.urlState[path].curr); + } + } + } + private changePathIdState(path: string, newPathIdObject: IUrlPathId): void { + this.urlState[path].prev = this.urlState[path].curr; + this.urlState[path].curr = newPathIdObject; + } + private setConnectedPath(hasValuePathIdList: string[], pathParams: IGeneral): void { + if (hasValuePathIdList.indexOf(UrlPathId.FOCUS_TIMESTAMP) !== -1) { + this.urlState[UrlPathId.END_TIME].prev = this.urlState[UrlPathId.END_TIME].curr; + this.urlState[UrlPathId.END_TIME].curr = UrlPathIdFactory.createPath(UrlPathId.END_TIME, EndTime.formatDate((Number(pathParams[UrlPathId.FOCUS_TIMESTAMP]) + (1000 * 60 * 10)))); + this.urlState[UrlPathId.PERIOD].prev = this.urlState[UrlPathId.PERIOD].curr; + this.urlState[UrlPathId.PERIOD].curr = UrlPathIdFactory.createPath(UrlPathId.PERIOD, this.webAppSettingDataService.getSystemDefaultTransactionViewPeriod().getValueWithTime()); + } + } + private updateQuery(queryParams: IGeneral): boolean { + let updated = false; + UrlQuery.getQueryList().forEach((query: string) => { + if (this.changedQuery(query, queryParams[query])) { + this.changeQueryState(query, queryParams[query] ? UrlQueryFactory.createQuery(query, queryParams[query]) : null); + updated = true; + } + }); + return updated; + } + private changedQuery(query: string, queryValue: any): boolean { + if (queryValue === null || queryValue === undefined) { + return this.urlState[query].curr !== null; + } else { + if (this.urlState[query].curr === null) { + return true; + } else { + return !(this.urlState[query].curr as IUrlQuery).equals(UrlQueryFactory.createQuery(query, queryValue)); + } + } + } + private changeQueryState(query: string, newQueryObject: IUrlQuery): void { + this.urlState[query].prev = this.urlState[query].curr; + this.urlState[query].curr = newQueryObject; + } + private updateRouteData(routeData: IGeneral) { + this.innerRouteData = routeData; + } + isRealTimeMode(type?: string): boolean { + if (typeof type === 'string') { + return type === UrlPath.REAL_TIME; + } else { + return this.innerRouteData['enableRealTimeMode'] || false; + } + } + showRealTimeButton(): boolean { + return this.innerRouteData['showRealTimeButton'] || false; + } + getUrlServerTimeData(): number { + return this.innerRouteData['serverTime']; + } + hasValue(...names: string[]): boolean { + return names.reduce((previous: boolean, name: string) => { + return previous && (this.urlState[name].curr === null ? false : true); + }, true); + } + getStartPath(): string { + return this.startPath || UrlPath.MAIN; + } + getPathValue(path: string): any { + return this.urlState[path].curr.get(); + } + getQueryValue(query: string): any { + return this.urlState[query].curr.get(); + } + getStartTimeToNumber(): number { + if (this.isRealTimeMode()) { + return this.getUrlServerTimeData() - (this.webAppSettingDataService.getSystemDefaultPeriod().getMiliSeconds()); + } else { + return this.getPathValue(UrlPathId.END_TIME).calcuStartTime(this.getPathValue(UrlPathId.PERIOD).getValue()).getDate().valueOf(); + } + } + getEndTimeToNumber(): number { + if (this.isRealTimeMode()) { + return this.getUrlServerTimeData(); + } else { + return this.getPathValue(UrlPathId.END_TIME).getDate().valueOf(); + } + } + isChanged(path: string): boolean { + if (this.urlState[path]) { + const { prev: prev, curr: curr } = this.urlState[path]; + if ( + (prev === null && curr !== null) || + (prev !== null && curr === null) || + (prev === null && curr === null) + ) { + return false; + } else { + return true; + } + } + return false; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/route-info-collector.service.ts b/web/src/main/webapp/v2/src/app/shared/services/route-info-collector.service.ts new file mode 100644 index 000000000000..16aa9334383d --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/route-info-collector.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute, ActivatedRouteSnapshot, Router, NavigationEnd, ParamMap } from '@angular/router'; +import { filter } from 'rxjs/operators'; +import { NewUrlStateNotificationService } from './new-url-state-notification.service'; +import { AnalyticsService } from 'app/shared/services/analytics.service'; + +@Injectable() +export class RouteInfoCollectorService { + constructor( + private router: Router, + private activatedRoute: ActivatedRoute, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private analyticsService: AnalyticsService + ) { + this.router.events.pipe( + filter(event => event instanceof NavigationEnd) + ).subscribe((event: NavigationEnd) => { + const startPath = this.activatedRoute.snapshot.root.firstChild.url[0].path; + const innerData = {}; + const pathIds = {}; + const queryParams = {}; + this.collectUrlInfo(this.activatedRoute.snapshot.children, pathIds, queryParams, innerData); + this.newUrlStateNotificationService.updateUrl(startPath, pathIds, queryParams, innerData); + this.analyticsService.trackPage(startPath); + }); + } + private collectUrlInfo(activatedChildRouteSnapshot: ActivatedRouteSnapshot[], pathIds: any, queryParams: any, innerData: any): void { + if (activatedChildRouteSnapshot.length !== 0) { + for ( let i = 0 ; i < activatedChildRouteSnapshot.length ; i++ ) { + this.assign(pathIds, activatedChildRouteSnapshot[i].paramMap); + this.assign(queryParams, activatedChildRouteSnapshot[i].queryParamMap); + Object.assign(innerData, activatedChildRouteSnapshot[i].data); + this.collectUrlInfo(activatedChildRouteSnapshot[i].children, pathIds, queryParams, innerData); + } + } + } + private assign(data: any, mapData: ParamMap): void { + mapData.keys.forEach((key: string) => { + data[key] = mapData.get(key); + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/server-time-data.service.ts b/web/src/main/webapp/v2/src/app/shared/services/server-time-data.service.ts new file mode 100644 index 000000000000..8e14b01762aa --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/server-time-data.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +interface IServerTime { + currentServerTime: number; +} + +@Injectable() +export class ServerTimeDataService { + + constructor(private http: HttpClient) { } + getServerTime(): Observable { + return this.http.get('serverTime.pinpoint').pipe( + map(res => { + return res.currentServerTime; + }) + ); + } + getServerTimeToPromise(): Promise { + return this.getServerTime().toPromise(); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/server-time-resolver.service.ts b/web/src/main/webapp/v2/src/app/shared/services/server-time-resolver.service.ts new file mode 100644 index 000000000000..29177949ac5c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/server-time-resolver.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'; +import { ServerTimeDataService } from 'app/shared/services/server-time-data.service'; + +@Injectable() +export class ServerTimeResolverService implements Resolve { + + constructor(private serverTimeService: ServerTimeDataService) { } + resolve(reoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + return this.serverTimeService.getServerTimeToPromise().catch(() => { + return Date.now(); + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/split-ratio.service.ts b/web/src/main/webapp/v2/src/app/shared/services/split-ratio.service.ts new file mode 100644 index 000000000000..88165d6ab784 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/split-ratio.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { UrlPath } from 'app/shared/models'; + +@Injectable() +export class SplitRatioService { + private splitRatioMap = new Map(); + + constructor() { + this.initSplitRatioMap(); + } + + private initSplitRatioMap(): void { + this.splitRatioMap.set(UrlPath.TRANSACTION_LIST, [30, 70]); + this.splitRatioMap.set(UrlPath.TRANSACTION_VIEW, [40, 60]); + } + + getSplitRatio(view: string): number[] { + return this.splitRatioMap.get(view); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/store-helper.service.ts b/web/src/main/webapp/v2/src/app/shared/services/store-helper.service.ts new file mode 100644 index 000000000000..897a559f8af5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/store-helper.service.ts @@ -0,0 +1,178 @@ +import { Injectable } from '@angular/core'; +import { Store, Action, select } from '@ngrx/store'; +import { Observable, Subject, iif } from 'rxjs'; +import { takeUntil, map, filter, debounceTime, distinctUntilChanged } from 'rxjs/operators'; + +import { + AppState, + STORE_KEY, + selectServerMapDisableState, + selectInfoPerServerVisibleState, + selectTimelineRange, + selectTimelineSelectedTime, + selectTimelineSelectionRange +} from 'app/shared/store'; +import { WebAppSettingDataService } from './web-app-setting-data.service'; + +@Injectable() +export class StoreHelperService { + private dateFormatList: string[][]; + constructor( + private store: Store, + private webAppSettingDataService: WebAppSettingDataService + ) { + this.dateFormatList = this.webAppSettingDataService.getDateFormatList(); + } + getApplicationList(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.APPLICATION_LIST, unsubscribe); + } + getFavoriteApplicationList(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.FAVORITE_APPLICATION_LIST, unsubscribe); + } + getTimezone(unsubscribe?: Subject): Observable { + return this.getObservable(STORE_KEY.TIMEZONE, unsubscribe); + } + getDateFormatIndex(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.DATE_FORMAT, unsubscribe); + } + getDateFormat(unsubscribe: Subject, index: number): Observable { + return this.getObservable(STORE_KEY.DATE_FORMAT, unsubscribe).pipe( + map((format: number) => { + return this.dateFormatList[format][index]; + }) + ); + } + getDateFormatArray(unsubscribe: Subject, ...index: number[]): Observable { + return this.getObservable(STORE_KEY.DATE_FORMAT, unsubscribe).pipe( + map((format: number) => { + return index.map((i: number) => { + return this.dateFormatList[format][i]; + }); + }) + ); + } + getAgentList(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.ADMIN_AGENT_LIST, unsubscribe).pipe( + filter((data: T) => { + return data ? true : false; + }) + ); + } + getServerAndAgentQuery(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.SERVER_AND_AGENT, unsubscribe).pipe( + debounceTime(100), + distinctUntilChanged() + ); + } + getHoverInfo(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.HOVER_ON_INSPECTOR_CHARTS, unsubscribe); + } + getServerMapTargetSelectedByList(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.SERVER_MAP_TARGET_SELECTED_BY_LIST, unsubscribe).pipe( + filter((target: any) => { + return (target && target.key) ? true : false; + }) + ); + } + getAgentSelection(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.AGENT_SELECTION, unsubscribe); + } + getAgentSelectionForServerList(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.AGENT_SELECTION_FOR_SERVER_LIST, unsubscribe); + } + getRealTimeScatterChartRange(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.REAL_TIME_SCATTER_CHART, unsubscribe).pipe( + filter((range: IScatterXRange) => { + if (range && range.from && range.to && range.from !== -1 && range.to !== -1) { + return true; + } + return false; + }) + ); + } + getScatterChartData(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.SCATTER_CHART, unsubscribe); + } + getServerMapData(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.SERVER_MAP_DATA, unsubscribe); + } + getServerMapLoadingState(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.SERVER_MAP_LOADING_STATE, unsubscribe); + } + getServerMapTargetSelected(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.SERVER_MAP_TARGET_SELECTED, unsubscribe); + } + getTransactionData(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.TRANSACTION_DATA, unsubscribe); + } + getTransactionDetailData(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.TRANSACTION_DETAIL_DATA, unsubscribe); + } + getServerListData(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.SERVER_LIST, unsubscribe); + } + getAgentInfo(unsubscribe?: Subject): Observable { + return this.getObservable(STORE_KEY.AGENT_INFO, unsubscribe); + } + getLoadChartYMax(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.LOAD_CHART_Y_MAX, unsubscribe); + } + getResponseSummaryChartYMax(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.RESPONSE_SUMMARY_CHART_Y_MAX, unsubscribe); + } + getServerMapDisableState(unsubscribe: Subject): Observable { + return this.store.pipe( + select(selectServerMapDisableState), + takeUntil(unsubscribe) + ); + } + getInfoPerServerState(unsubscribe: Subject): Observable { + return this.store.pipe( + select(selectInfoPerServerVisibleState), + takeUntil(unsubscribe) + ); + } + getInspectorTimelineData(unsubscribe: Subject): Observable { + return this.getObservable(STORE_KEY.TIMELINE, unsubscribe); + } + getInspectorTimelineRange(unsubscribe: Subject): Observable { + return this.store.pipe( + takeUntil(unsubscribe), + select(selectTimelineRange), + filter(([from, to]: number[]) => { + return (from === 0 && to === 0) ? false : true; + }) + ); + } + getInspectorTimelineSelectionRange(unsubscribe: Subject): Observable { + return this.store.pipe( + takeUntil(unsubscribe), + select(selectTimelineSelectionRange), + filter(([from, to]: number[]) => { + return (from === 0 && to === 0) ? false : true; + }) + ); + } + getInspectorTimelineSelectedTime(unsubscribe: Subject): Observable { + return this.store.pipe( + takeUntil(unsubscribe), + select(selectTimelineSelectedTime), + filter((time: number) => { + return time === 0 ? false : true; + }) + ); + } + private getObservable(key: string, unsubscribe?: Subject): Observable { + return iif( + () => !!unsubscribe, + this.store.pipe( + select(key), + takeUntil(unsubscribe) + ), + this.store.pipe(select(key)) + ); + } + dispatch(action: Action): void { + this.store.dispatch(action); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/system-configuration-data.service.ts b/web/src/main/webapp/v2/src/app/shared/services/system-configuration-data.service.ts new file mode 100644 index 000000000000..a238f7ae9792 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/system-configuration-data.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +export interface ISystemConfiguration { + editUserInfo: boolean; + enableServerMapRealTime: boolean; + openSource: boolean; + sendUsage: boolean; + showActiveThread: boolean; + showActiveThreadDump: boolean; + showApplicationStat: boolean; + version: string; + userId?: string; + userName?: string; + userDepartment?: string; +} + +@Injectable() +export class SystemConfigurationDataService { + url = 'configuration.pinpoint'; + constructor(private http: HttpClient) {} + getConfiguration(): Observable { + return this.http.get(this.url).pipe( + map(res => { + if (res) { + return res; + } else { + return { + editUserInfo: false, + enableServerMapRealTime: false, + openSource: true, + sendUsage: true, + showActiveThread: false, + showActiveThreadDump: false, + showApplicationStat: false, + version: '', + userId: '', + userName: '', + userDepartment: '' + }; + } + }) + ); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/system-configuration-resolver.service.ts b/web/src/main/webapp/v2/src/app/shared/services/system-configuration-resolver.service.ts new file mode 100644 index 000000000000..a7c57b666bcd --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/system-configuration-resolver.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; +import { Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; +import { SystemConfigurationDataService, ISystemConfiguration } from './system-configuration-data.service'; + +@Injectable() +export class SystemConfigurationResolverService implements Resolve { + constructor(private systemConfigurationDataService: SystemConfigurationDataService) {} + resolve(reoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.systemConfigurationDataService.getConfiguration(); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/transaction-detail-data.service.ts b/web/src/main/webapp/v2/src/app/shared/services/transaction-detail-data.service.ts new file mode 100644 index 000000000000..1cc33b60cbe2 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/transaction-detail-data.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable, Subject } from 'rxjs'; +import { tap, shareReplay } from 'rxjs/operators'; + +export interface ITransactionDetailPartInfo { + completeState: string; + logPageUrl: string; + logButtonName: string; + logLinkEnable: boolean; + disableButtonMessage: string; + loggingTransactionInfo: boolean; +} + +@Injectable() +export class TransactionDetailDataService { + private partInfo: Subject = new Subject(); + lastKey: string; + cachedData: { [key: string]: Observable } = {}; + partInfo$: Observable; + requestURL = 'transactionInfo.pinpoint'; + constructor(private http: HttpClient) { + this.partInfo$ = this.partInfo.asObservable(); + } + getData(agentId: string, spanId: string, traceId: string, focusTimestamp: number): Observable { + this.lastKey = agentId + spanId + traceId + focusTimestamp; + if ( this.hasData() ) { + return this.cachedData[this.lastKey]; + } else { + const httpRequest$ = this.http.get(this.requestURL, this.makeRequestOptionsArgs(agentId, spanId, traceId, focusTimestamp)); + this.cachedData[this.lastKey] = httpRequest$.pipe( + tap((transactionInfo: any) => { + this.partInfo.next({ + logButtonName: transactionInfo.logButtonName, + logLinkEnable: transactionInfo.logLinkEnable, + logPageUrl: transactionInfo.logPageUrl, + loggingTransactionInfo: transactionInfo.loggingTransactionInfo, + disableButtonMessage: transactionInfo.disableButtonMessage, + completeState: transactionInfo.completeState, + }); + }), + shareReplay(1) + ); + return this.cachedData[this.lastKey]; + } + } + private hasData(): boolean { + return !!this.cachedData[this.lastKey]; + } + private makeRequestOptionsArgs(agentId: string, spanId: string, traceId: string, focusTimestamp: number): object { + return { + params: { + agentId: agentId, + spanId: spanId, + traceId: traceId, + focusTimestamp: focusTimestamp + } + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/transaction-view-type.service.ts b/web/src/main/webapp/v2/src/app/shared/services/transaction-view-type.service.ts new file mode 100644 index 000000000000..c3c3f0562ed8 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/transaction-view-type.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +import { UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService } from 'app/shared/services/new-url-state-notification.service'; + +export interface IViewType { + key: string; + display: string; +} +export const VIEW_TYPE = { + CALL_TREE: 'callTree', + SERVER_MAP: 'serverMap', + TIMELINE: 'timeline' +}; + +@Injectable() +export class TransactionViewTypeService { + private outChangeViewType: BehaviorSubject = new BehaviorSubject(VIEW_TYPE.CALL_TREE); + private viewTypeList = [ + { + key: VIEW_TYPE.CALL_TREE, + display: 'Call Tree' + }, { + key: VIEW_TYPE.SERVER_MAP, + display: 'Server Map' + }, { + key: VIEW_TYPE.TIMELINE, + display: 'Timeline' + } + ]; + private currentViewType = ''; + private defaultViewType = this.viewTypeList[0].key; + onChangeViewType$: Observable; + constructor( + private newUrlStateNotificationService: NewUrlStateNotificationService + ) { + this.onChangeViewType$ = this.outChangeViewType.asObservable(); + this.newUrlStateNotificationService.onUrlStateChange$.pipe( + filter((urlService: NewUrlStateNotificationService) => !!urlService) + ).subscribe((urlService: NewUrlStateNotificationService) => { + if (urlService.hasValue(UrlPathId.VIEW_TYPE)) { + this.setCurrentViewType(urlService.getPathValue(UrlPathId.VIEW_TYPE)); + } else { + this.setCurrentViewType(''); + } + }); + } + private setCurrentViewType(viewType: string): void { + if ( viewType === '' ) { + this.currentViewType = this.defaultViewType; + } else { + if ( this.currentViewType === viewType ) { + return; + } + let hasMatchedKey = false; + this.viewTypeList.forEach((obj: IViewType) => { + if ( obj.key === viewType ) { + hasMatchedKey = true; + } + }); + this.currentViewType = hasMatchedKey ? viewType : this.defaultViewType; + } + this.outChangeViewType.next(this.currentViewType); + } + getViewTypeList(): IViewType[] { + return this.viewTypeList; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/translate-replace.service.ts b/web/src/main/webapp/v2/src/app/shared/services/translate-replace.service.ts new file mode 100644 index 000000000000..ec8283d28728 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/translate-replace.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class TranslateReplaceService { + constructor() { } + replace(template: string, ...argu: any[]): string { + let replacedTemplate = template; + argu.forEach((value: string, index: number) => { + replacedTemplate = replacedTemplate.replace( new RegExp('\\!\\{' + index + '\\}'), '' + value); + }); + return replacedTemplate; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/url-route-manager.service.ts b/web/src/main/webapp/v2/src/app/shared/services/url-route-manager.service.ts new file mode 100644 index 000000000000..38b729050f48 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/url-route-manager.service.ts @@ -0,0 +1,139 @@ +import { Injectable, Inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { WindowRefService } from 'app/shared/services/window-ref.service'; +import { ServerTimeDataService } from 'app/shared/services/server-time-data.service'; +import { FilterParamMaker } from 'app/core/utils/filter-param-maker'; +import { HintParamMaker } from 'app/core/utils/hint-param-maker'; +import { EndTime } from 'app/core/models/end-time'; +import { Filter } from 'app/core/models/filter'; +import { UrlPath, UrlPathId } from 'app/shared/models'; +import { NewUrlStateNotificationService } from 'app/shared/services/new-url-state-notification.service'; +import { WebAppSettingDataService } from 'app/shared/services/web-app-setting-data.service'; +import { APP_BASE_HREF } from '@angular/common'; + +@Injectable() +export class UrlRouteManagerService { + + constructor( + private windowRef: WindowRefService, + private router: Router, + private webAppSettingDataService: WebAppSettingDataService, + private newUrlStateNotificationService: NewUrlStateNotificationService, + private serverTimeDataService: ServerTimeDataService, + @Inject(APP_BASE_HREF) private baseHref: string + ) {} + changeApplication(applicationUrlStr: string): void { + const startPath = this.newUrlStateNotificationService.getStartPath(); + if (this.newUrlStateNotificationService.isRealTimeMode()) { + this.moveToRealTime(applicationUrlStr); + } else { + this.serverTimeDataService.getServerTime().subscribe(time => { + const url = [ + startPath, + applicationUrlStr, + this.webAppSettingDataService.getUserDefaultPeriod().getValueWithTime(), + EndTime.formatDate(time) + ]; + this.router.navigate(url, { + queryParamsHandling: 'preserve' + }); + }); + } + } + moveToRealTime(applicationUrlStr?: string): void { + this.router.navigate([ + this.newUrlStateNotificationService.getStartPath(), + (applicationUrlStr || this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr()), + UrlPath.REAL_TIME + ]); + } + move({ url, needServerTimeRequest, nextUrl = [], queryParam }: { url: string[], needServerTimeRequest: boolean, nextUrl?: string[], queryParam?: any} ): void { + if (needServerTimeRequest) { + this.serverTimeDataService.getServerTime().subscribe(time => { + const newUrl = url.concat([EndTime.formatDate(time)]).concat(nextUrl).filter((v: string) => { + return v !== ''; + }); + if (queryParam) { + this.router.navigate(newUrl, { + queryParams: queryParam, + queryParamsHandling: 'merge' + }); + } else { + this.router.navigate(newUrl, { + queryParamsHandling: 'preserve' + }); + } + }); + } else { + const newUrl = [...url, ...nextUrl]; + + if (queryParam) { + this.router.navigate(newUrl, { + queryParams: queryParam, + queryParamsHandling: 'merge' + }); + } else { + this.router.navigate(newUrl, { + queryParamsHandling: 'preserve' + }); + } + } + } + moveOnPage({ url, queryParam }: { url: string[], queryParam?: any }): void { + this.move({ + url: url, + needServerTimeRequest: false, + nextUrl: [], + queryParam: queryParam + }); + } + openPage(path: string | string[], title?: string): void { + this.windowRef.nativeWindow.open(this.getBaseHref() + (path instanceof Array ? path.join('/') : path), title || ''); + } + makeFilterMapUrl( + { applicationName, serviceType, periodStr, timeStr, filterStr, hintStr, addedFilter, addedHint }: + { applicationName: string, serviceType: string, periodStr: string, timeStr: string, filterStr: string, hintStr: string, addedFilter: Filter, addedHint?: any } + ): string { + return `filteredMap/${applicationName}@${serviceType}/${periodStr}/${timeStr}` + + FilterParamMaker.makeParam(filterStr, addedFilter) + + HintParamMaker.makeParam(hintStr, addedHint); + } + openInspectorPage(realTimeMode: boolean): void { + if (realTimeMode) { + this.serverTimeDataService.getServerTime().subscribe(time => { + this.windowRef.nativeWindow.open([ + this.getBaseHref() + UrlPath.INSPECTOR, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.webAppSettingDataService.getSystemDefaultPeriod().getValueWithTime(), + EndTime.newByNumber(time).getEndTime() + ].join('/')); + }); + } else { + this.windowRef.nativeWindow.open([ + this.getBaseHref() + UrlPath.INSPECTOR, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ].join('/')); + } + } + openMainPage(): void { + this.windowRef.nativeWindow.open([ + this.getBaseHref() + UrlPath.MAIN, + this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), + this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() + ].join('/')); + } + private getBaseHref(): string { + if (this.baseHref === '/') { + return ''; + } else { + if (/.*\/$/.test(this.baseHref)) { + return this.baseHref; + } else { + return this.baseHref + '/'; + } + } + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/url-validate.guard.ts b/web/src/main/webapp/v2/src/app/shared/services/url-validate.guard.ts new file mode 100644 index 000000000000..6b4dfa00a5e9 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/url-validate.guard.ts @@ -0,0 +1,90 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, ParamMap } from '@angular/router'; + +import { UrlRouteManagerService } from 'app/shared/services/url-route-manager.service'; +import { WebAppSettingDataService } from 'app/shared/services/web-app-setting-data.service'; +import { UrlPath, UrlPathId } from 'app/shared/models'; + +interface IPathParam { + [key: string]: string; +} + +@Injectable() +export class UrlValidateGuard implements CanActivate { + constructor( + private webAppSettingDataService: WebAppSettingDataService, + private urlRouteManagerService: UrlRouteManagerService + ) {} + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + const hasPaths: IPathParam = {}; + this.collectUrlInfo(route.children, hasPaths); + switch (route.routeConfig.path) { + case UrlPath.MAIN: + return this.checkMainRoute(route, hasPaths); + case UrlPath.FILTERED_MAP: + return this.checkFilteredMapRoute(route, hasPaths); + } + return false; + } + private checkMainRoute(route: ActivatedRouteSnapshot, hasPaths: IPathParam): boolean { + const subPath = route.children[0].routeConfig.path; + switch (subPath) { + case ':' + UrlPathId.APPLICATION: + this.urlRouteManagerService.move({ + url: [ + UrlPath.MAIN, + hasPaths.application, + this.webAppSettingDataService.getUserDefaultPeriod().getValueWithTime() + ], + needServerTimeRequest: true + }); + return false; + case ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD: + this.urlRouteManagerService.move({ + url: [ + UrlPath.MAIN, + hasPaths.application, + hasPaths.period + ], + needServerTimeRequest: true + }); + return false; + case ':' + UrlPathId.APPLICATION + '/' + UrlPath.REAL_TIME: + case ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME: + default: + return true; + } + } + private checkFilteredMapRoute(route: ActivatedRouteSnapshot, hasPaths: IPathParam): boolean { + const subPath = route.children[0].routeConfig.path; + switch (subPath) { + case ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME: + case ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME + '/:' + UrlPathId.FILTER: + case ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD + '/:' + UrlPathId.END_TIME + '/:' + UrlPathId.FILTER + '/:' + UrlPathId.HINT: + return true; + case ':' + UrlPathId.APPLICATION: + case ':' + UrlPathId.APPLICATION + '/:' + UrlPathId.PERIOD: + default: + this.urlRouteManagerService.move({ + url: [ + UrlPath.MAIN + ], + needServerTimeRequest: false + }); + return false; + } + } + private collectUrlInfo(activatedChildRouteSnapshot: ActivatedRouteSnapshot[], hasPaths: any): void { + if (activatedChildRouteSnapshot.length !== 0) { + for ( let i = 0 ; i < activatedChildRouteSnapshot.length ; i++ ) { + this.assign(hasPaths, activatedChildRouteSnapshot[i].paramMap); + this.collectUrlInfo(activatedChildRouteSnapshot[i].children, hasPaths); + } + } + } + private assign(data: any, mapData: ParamMap): void { + mapData.keys.forEach((key: string) => { + data[key] = mapData.get(key); + }); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/web-app-setting-data.service.ts b/web/src/main/webapp/v2/src/app/shared/services/web-app-setting-data.service.ts new file mode 100644 index 000000000000..afc3b8f60762 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/web-app-setting-data.service.ts @@ -0,0 +1,227 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { LocalStorageService } from 'angular-2-local-storage'; +import 'moment-timezone'; +import * as moment from 'moment-timezone'; +import { pluck } from 'rxjs/operators'; + +import { AppState, Actions } from 'app/shared/store'; +import { ComponentDefaultSettingDataService } from 'app/shared/services/component-default-setting-data.service'; +import { Application, Period } from 'app/core/models'; + +interface IMinMax { + min: number; + max: number; +} + +@Injectable() +export class WebAppSettingDataService { + static KEYS = { + FAVORLIITE_APPLICATION_LIST: 'favoriteApplicationList', + TIMEZONE: 'timezone', + DATE_FORMAT: 'dateFormat', + LIST_HANDLE_POSITION: 'listHandlePosition', + LAYER_HEIGHT: 'layerHeight', + USER_DEFAULT_INBOUND: 'userDefaultInbound', + USER_DEFAULT_OUTBOUND: 'userDefaultOutbound', + USER_DEFAULT_PERIOD: 'userDefaultPeriod', + TRANSACTION_LIST_GUTTER_POSITION: 'transactionListGutterPosition' + }; + private IMAGE_PATH = './assets/img/'; + private IMAGE_EXT = '.png'; + private SERVER_MAP_PATH = 'servermap/'; + private ICON_PATH = 'icons/'; + private LOGO_IMG_NAME = 'logo.png'; + constructor( + private store: Store, + private activatedRoute: ActivatedRoute, + private localStorageService: LocalStorageService, + private componentDefaultSettingDataService: ComponentDefaultSettingDataService + ) { + this.store.dispatch(new Actions.AddFavoriteApplication(this.getFavoriteApplicationList())); + this.store.dispatch(new Actions.ChangeTimezone(this.getTimezone())); + this.store.dispatch(new Actions.ChangeDateFormat(this.getDateFormat())); + } + private getConfigurationData(): Observable { + return this.activatedRoute.children[0].children[0].data; + } + useActiveThreadChart(): Observable { + return this.getConfigurationData().pipe( + pluck('configuration', 'showActiveThread') + ); + } + getUserId(): Observable { + return this.getConfigurationData().pipe( + pluck('configuration', 'userId') + ); + } + getUserDepartment(): Observable { + return this.getConfigurationData().pipe( + pluck('configuration', 'userDepartment') + ); + } + useUserEdit(): Observable { + return this.getConfigurationData().pipe( + pluck('configuration', 'editUserInfo') + ); + } + isDataUsageAllowed(): Observable { + return this.getConfigurationData().pipe( + pluck('configuration', 'sendUsage') + ); + } + getVersion(): Observable { + return this.getConfigurationData().pipe( + pluck('configuration', 'version') + ); + } + isApplicationInspectorActivated(): Observable { + return this.getConfigurationData().pipe( + pluck('configuration', 'showApplicationStat') + ); + } + getImagePath(): string { + return this.IMAGE_PATH; + } + getServerMapImagePath(): string { + return this.getImagePath() + this.SERVER_MAP_PATH; + } + getIconImagePath(): string { + return this.getImagePath() + this.ICON_PATH; + } + getImageExt(): string { + return this.IMAGE_EXT; + } + getLogoPath(): string { + return this.getImagePath() + this.LOGO_IMG_NAME; + } + getSystemDefaultInbound(): string { + return this.componentDefaultSettingDataService.getSystemDefaultInbound(); + } + getSystemDefaultOutbound(): string { + return this.componentDefaultSettingDataService.getSystemDefaultOutbound(); + } + getSystemDefaultPeriod(): Period { + return this.componentDefaultSettingDataService.getSystemDefaultPeriod(); + } + getSystemDefaultTransactionViewPeriod(): Period { + return this.componentDefaultSettingDataService.getSystemDefaultTransactionViewPeriod(); + } + getInboundList(): string[] { + return this.componentDefaultSettingDataService.getInboundList(); + } + getOutboundList(): string[] { + return this.componentDefaultSettingDataService.getOutboundList(); + } + getPeriodList(path: string): Period[] { + return this.componentDefaultSettingDataService.getPeriodList(path); + } + getMaxPeriodTime(): number { + return this.componentDefaultSettingDataService.getMaxPeriodTime(); + } + getColorByRequest(): string[] { + return this.componentDefaultSettingDataService.getColorByRequest(); + } + private loadFavoriteList(): any[] { + return JSON.parse(this.localStorageService.get(WebAppSettingDataService.KEYS.FAVORLIITE_APPLICATION_LIST)) || []; + } + private saveFavoriteList(data: any[]): void { + this.localStorageService.set(WebAppSettingDataService.KEYS.FAVORLIITE_APPLICATION_LIST, JSON.stringify(data)); + } + addFavoriteApplication(application: IApplication): void { + this.saveFavoriteList([...this.loadFavoriteList(), { + applicationName: application.getApplicationName(), + serviceType: application.getServiceType(), + code: application.getCode() + }]); + this.store.dispatch(new Actions.AddFavoriteApplication([application])); + } + removeFavoriteApplication(application: IApplication): void { + const removedList = this.getFavoriteApplicationList().filter((app: IApplication) => { + return !app.equals(application); + }); + this.saveFavoriteList(removedList); + this.store.dispatch(new Actions.RemoveFavoriteApplication([application])); + } + private getFavoriteApplicationList(): IApplication[] { + return this.loadFavoriteList().map(({applicationName, serviceType, code}) => { + return new Application(applicationName, serviceType, code); + }); + } + getScatterY(key: string): IMinMax { + return this.localStorageService.get(key) || { min: 0, max: 10000 }; + } + setScatterY(key: string, value: IMinMax): void { + this.localStorageService.set(key, value); + } + setTimezone(value: string): void { + this.localStorageService.set(WebAppSettingDataService.KEYS.TIMEZONE, value); + } + private getTimezone(): string { + return this.localStorageService.get(WebAppSettingDataService.KEYS.TIMEZONE) || this.getDefaultTimezone(); + } + getDefaultTimezone(): string { + return moment.tz.guess(); + } + setDateFormat(value: number): void { + this.localStorageService.set(WebAppSettingDataService.KEYS.DATE_FORMAT, value); + } + private getDateFormat(): number { + return this.localStorageService.get(WebAppSettingDataService.KEYS.DATE_FORMAT) || 0; + } + getDefaultDateFormat(): string[] { + return this.componentDefaultSettingDataService.getDefaultDateFormat(); + } + getDateFormatList(): string[][] { + return this.componentDefaultSettingDataService.getDateFormatList(); + } + setListHandlePosition(value: number[]): void { + this.localStorageService.set(WebAppSettingDataService.KEYS.LIST_HANDLE_POSITION, value); + } + getListHandlePosition(): number[] { + return this.localStorageService.get(WebAppSettingDataService.KEYS.LIST_HANDLE_POSITION) || [30, 70]; + } + setLayerHeight(value: number): void { + this.localStorageService.set(WebAppSettingDataService.KEYS.LAYER_HEIGHT, value); + } + getLayerHeight(): number { + return Number.parseInt(this.localStorageService.get(WebAppSettingDataService.KEYS.LAYER_HEIGHT), 10); + } + setUserDefaultInbound(value: string): void { + this.localStorageService.set(WebAppSettingDataService.KEYS.USER_DEFAULT_INBOUND, value); + } + getUserDefaultInbound(): string { + return this.localStorageService.get(WebAppSettingDataService.KEYS.USER_DEFAULT_INBOUND) || this.getSystemDefaultInbound(); + } + setUserDefaultOutbound(value: string): void { + this.localStorageService.set(WebAppSettingDataService.KEYS.USER_DEFAULT_OUTBOUND, value); + } + getUserDefaultOutbound(): string { + return this.localStorageService.get(WebAppSettingDataService.KEYS.USER_DEFAULT_OUTBOUND) || this.getSystemDefaultOutbound(); + } + setUserDefaultPeriod(value: Period): void { + this.localStorageService.set(WebAppSettingDataService.KEYS.USER_DEFAULT_PERIOD, value.getValue()); + } + getUserDefaultPeriod(): Period { + const userDefaultPeriodInMinute = this.localStorageService.get(WebAppSettingDataService.KEYS.USER_DEFAULT_PERIOD); + + return userDefaultPeriodInMinute ? new Period(userDefaultPeriodInMinute) : this.getSystemDefaultPeriod(); + } + getServerMapIconPathMakeFunc(): Function { + return (name: string) => { + return this.IMAGE_PATH + this.SERVER_MAP_PATH + name + this.IMAGE_EXT; + }; + } + getIconPathMakeFunc(): Function { + return (name: string) => { + return this.IMAGE_PATH + this.ICON_PATH + name + this.IMAGE_EXT; + }; + } + getImagePathMakeFunc(): Function { + return (name: string) => { + return this.IMAGE_PATH + name + this.IMAGE_EXT; + }; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/services/window-ref.service.ts b/web/src/main/webapp/v2/src/app/shared/services/window-ref.service.ts new file mode 100644 index 000000000000..1548c2ec742e --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/services/window-ref.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; + +function getWindow(): any { + return window; +} + +@Injectable() +export class WindowRefService { + get nativeWindow(): any { + return getWindow(); + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/admin.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/admin.reducer.ts new file mode 100644 index 000000000000..c451bc9bd5f4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/admin.reducer.ts @@ -0,0 +1,16 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_ADMIN_AGENT_LIST = 'UPDATE_ADMIN_AGENT_LIST'; +export class UpdateAdminAgentList implements Action { + readonly type = UPDATE_ADMIN_AGENT_LIST; + constructor(public payload: IAgentList) {} +} + +export function Reducer(state: IAgentList, action: UpdateAdminAgentList): IAgentList { + switch (action.type) { + case UPDATE_ADMIN_AGENT_LIST: + return action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/agent-info.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/agent-info.reducer.ts new file mode 100644 index 000000000000..2faf50d4b203 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/agent-info.reducer.ts @@ -0,0 +1,17 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_AGENT_INFO = 'UPDATE_AGENT_INFO'; + +export class UpdateAgentInfo implements Action { + readonly type = UPDATE_AGENT_INFO; + constructor(public payload: IServerAndAgentData) {} +} + +export function Reducer(state: IServerAndAgentData, action: UpdateAgentInfo): IServerAndAgentData { + switch (action.type) { + case UPDATE_AGENT_INFO: + return action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/agent-selection-for-info-per-server.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/agent-selection-for-info-per-server.reducer.ts new file mode 100644 index 000000000000..059ef31ccce0 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/agent-selection-for-info-per-server.reducer.ts @@ -0,0 +1,17 @@ +import { Action } from '@ngrx/store'; + +const CHANGE_AGENT_FOR_SERVER_LIST = 'CHANGE_AGENT_FOR_SERVER_LIST'; + +export class ChangeAgentForServerList implements Action { + readonly type = CHANGE_AGENT_FOR_SERVER_LIST; + constructor(public payload: IAgentSelection) {} +} + +export function Reducer(state: IAgentSelection, action: ChangeAgentForServerList): IAgentSelection { + switch ( action.type ) { + case CHANGE_AGENT_FOR_SERVER_LIST: + return (state && state.agent === action.payload.agent) ? state : action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/agent-selection-for-side-bar.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/agent-selection-for-side-bar.reducer.ts new file mode 100644 index 000000000000..d0f2a7353c31 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/agent-selection-for-side-bar.reducer.ts @@ -0,0 +1,17 @@ +import { Action } from '@ngrx/store'; + +const CHANGE_AGENT = 'CHANGE_AGENT'; + +export class ChangeAgent implements Action { + readonly type = CHANGE_AGENT; + constructor(public payload: string) {} +} + +export function Reducer(state = '', action: ChangeAgent): string { + switch ( action.type ) { + case CHANGE_AGENT: + return (state === action.payload) ? state : action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/application-list.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/application-list.reducer.ts new file mode 100644 index 000000000000..4cfcdb198931 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/application-list.reducer.ts @@ -0,0 +1,17 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_APPLICATION_LIST = 'UPDATE_APPLICATION_LIST'; + +export class UpdateApplicationList implements Action { + readonly type = UPDATE_APPLICATION_LIST; + constructor(public payload: IApplication[]) {} +} + +export function Reducer(state: IApplication[] = [], action: UpdateApplicationList): IApplication[] { + switch (action.type) { + case UPDATE_APPLICATION_LIST: + return action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/date-format.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/date-format.reducer.ts new file mode 100644 index 000000000000..de12929dbdb6 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/date-format.reducer.ts @@ -0,0 +1,16 @@ +import { Action } from '@ngrx/store'; + +const CHANGE_DATE_FORMAT = 'CHANGE_DATE_FORMAT'; +export class ChangeDateFormat implements Action { + readonly type = CHANGE_DATE_FORMAT; + constructor(public payload: number) {} +} + +export function Reducer(state = 0, action: ChangeDateFormat) { + switch (action.type) { + case CHANGE_DATE_FORMAT: + return (state === action.payload) ? state : action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/favorite-application-list.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/favorite-application-list.reducer.ts new file mode 100644 index 000000000000..4aaac3d76f15 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/favorite-application-list.reducer.ts @@ -0,0 +1,31 @@ +import { Action } from '@ngrx/store'; + +const ADD_FAVORITE_APPLICATION = 'ADD_FAVORITE_APPLICATION'; +const REMOVE_FAVORITE_APPLICATION = 'REMOVE_FAVORITE_APPLICATION'; + +export class AddFavoriteApplication implements Action { + readonly type = ADD_FAVORITE_APPLICATION; + constructor(public payload: IApplication[]) {} +} +export class RemoveFavoriteApplication implements Action { + readonly type = REMOVE_FAVORITE_APPLICATION; + constructor(public payload: IApplication[]) {} +} + +export function Reducer(state: IApplication[] = [], action: AddFavoriteApplication | RemoveFavoriteApplication): IApplication[] { + switch (action.type) { + case ADD_FAVORITE_APPLICATION: + return [...state, ...action.payload]; + case REMOVE_FAVORITE_APPLICATION: + return state.filter((application: IApplication) => { + for (let i = 0 ; i < action.payload.length ; i++) { + if (application.equals(action.payload[i])) { + return false; + } + } + return true; + }); + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/index.ts b/web/src/main/webapp/v2/src/app/shared/store/index.ts new file mode 100644 index 000000000000..c6be81fbd092 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/index.ts @@ -0,0 +1,163 @@ +import { ActionReducerMap, createSelector, createFeatureSelector } from '@ngrx/store'; + +import * as admin from './admin.reducer'; +import * as agentInfo from './agent-info.reducer'; +import * as agentSelectionForInfoPerServer from './agent-selection-for-info-per-server.reducer'; +import * as agentSelectionForSideBar from './agent-selection-for-side-bar.reducer'; +import * as applicationList from './application-list.reducer'; +import * as dateFormat from './date-format.reducer'; +import * as favoriteApplicationList from './favorite-application-list.reducer'; +import * as inspectorChartHover from './inspector-chart-hover.reducer'; +import * as loadChart from './load-chart.reducer'; +import * as responseSummaryChart from './response-summary-chart.reducer'; +import * as scatterChartRealTime from './scatter-chart-real-time.reducer'; +import * as scatterChart from './scatter-chart.reducer'; +import * as serverAndAgent from './server-and-agent.reducer'; +import * as serverList from './server-list.reducer'; +import * as timeline from './timeline.reducer'; +import * as timezone from './timezone.reducer'; +import * as transactionDetailData from './transaction-detail-data.reducer'; +import * as transactionData from './transaction-info.reducer'; +import * as targetList from './target-list.reducer'; +import * as serverMap from './server-map.reducer'; +import * as serverMapSelectedTarget from './server-map-selected-target.reducer'; +import * as serverMapLoadingState from './server-map-loading-state.reducer'; +import * as uiState from './ui-state.reducer'; + + +export interface AppState { + timeline: ITimelineInfo; + timezone: string; + dateFormat: number; + loadChartYMax: number; + responseSummaryChartYMax: number; + agentSelection: string; + agentSelectionForServerList: IAgentSelection; + transactionData: ITransactionMetaData; + transactionDetailData: ITransactionDetailData; + hoverOnInspectorCharts: number; + agentInfo: IServerAndAgentData; + applicationList: IApplication[]; + favoriteApplicationList: IApplication[]; + serverList: any; + scatterChart: IScatterData; + realTimeScatterChart: IScatterXRange; + serverMapData: any; + serverMapLoadingState: string; + serverMapTargetSelected: ISelectedTarget; + serverMapTargetSelectByList: any; + updateFilterOfServerAndAgentList: string; + adminAgentList: { [key: string]: IAgent[] }; + uiState: IUIState; +} + +export const STORE_KEY = { + TIMELINE: 'timeline', + TIMEZONE: 'timezone', + DATE_FORMAT: 'dateFormat', + LOAD_CHART_Y_MAX: 'loadChartYMax', + RESPONSE_SUMMARY_CHART_Y_MAX: 'responseSummaryChartYMax', + AGENT_SELECTION: 'agentSelection', + AGENT_SELECTION_FOR_SERVER_LIST: 'agentSelectionForServerList', + TIMELINE_SELECTION_RANGE: 'timelineSelectionRange', + TRANSACTION_DATA: 'transactionData', + TRANSACTION_DETAIL_DATA: 'transactionDetailData', + HOVER_ON_INSPECTOR_CHARTS: 'hoverOnInspectorCharts', + AGENT_INFO: 'agentInfo', + APPLICATION_LIST: 'applicationList', + FAVORITE_APPLICATION_LIST: 'favoriteApplicationList', + SERVER_LIST: 'serverList', + SCATTER_CHART: 'scatterChart', + REAL_TIME_SCATTER_CHART: 'realTimeScatterChart', + SERVER_MAP_DATA: 'serverMapData', + SERVER_MAP_LOADING_STATE: 'serverMapLoadingState', + SERVER_MAP_TARGET_SELECTED: 'serverMapTargetSelected', + SERVER_MAP_TARGET_SELECTED_BY_LIST: 'serverMapTargetSelectByList', + ADMIN_AGENT_LIST: 'adminAgentList', + SERVER_AND_AGENT: 'serverAndAgent', + UI_STATE: 'uiState' +}; + + +export const reducers: ActionReducerMap = { + // [STORE_KEY.AGENT_INFo]: agentInfoReducer 방식은 빌드시 에러가 발생 함. + agentInfo: agentInfo.Reducer, + agentSelection: agentSelectionForSideBar.Reducer, + agentSelectionForServerList: agentSelectionForInfoPerServer.Reducer, + applicationList: applicationList.Reducer, + favoriteApplicationList: favoriteApplicationList.Reducer, + dateFormat: dateFormat.Reducer, + hoverOnInspectorCharts: inspectorChartHover.Reducer, + loadChartYMax: loadChart.Reducer, + responseSummaryChartYMax: responseSummaryChart.Reducer, + realTimeScatterChart: scatterChartRealTime.Reducer, + scatterChart: scatterChart.Reducer, + serverList: serverList.Reducer, + serverMapLoadingState: serverMapLoadingState.Reducer, + serverMapData: serverMap.Reducer, + serverMapTargetSelected: serverMapSelectedTarget.Reducer, + serverMapTargetSelectByList: targetList.Reducer, + timezone: timezone.Reducer, + transactionData: transactionData.Reducer, + transactionDetailData: transactionDetailData.Reducer, + adminAgentList: admin.Reducer, + serverAndAgent: serverAndAgent.Reducer, + uiState: uiState.Reducer, + timeline: timeline.Reducer +}; + +export const Actions = { + 'ChangeTimezone': timezone.ChangeTimezone, + 'ChangeDateFormat': dateFormat.ChangeDateFormat, + 'ChangeResponseSummaryChartYMax': responseSummaryChart.ChangeResponseSummaryChartYMax, + 'ChangeLoadChartYMax': loadChart.ChangeLoadChartYMax, + 'ChangeAgent': agentSelectionForSideBar.ChangeAgent, + 'ChangeAgentForServerList': agentSelectionForInfoPerServer.ChangeAgentForServerList, + 'UpdateTransactionData': transactionData.UpdateTransactionData, + 'UpdateTransactionDetailData': transactionDetailData.UpdateTransactionDetailData, + 'ChangeHoverOnInspectorCharts': inspectorChartHover.ChangeHoverOnInspectorCharts, + 'UpdateApplicationList': applicationList.UpdateApplicationList, + 'AddFavoriteApplication': favoriteApplicationList.AddFavoriteApplication, + 'RemoveFavoriteApplication': favoriteApplicationList.RemoveFavoriteApplication, + 'UpdateServerList': serverList.UpdateServerList, + 'AddScatterChartData': scatterChart.AddScatterChartData, + 'UpdateRealTimeScatterChartXRange': scatterChartRealTime.UpdateRealTimeScatterChartXRange, + 'UpdateServerMapData': serverMap.UpdateServerMapData, + 'UpdateServerMapLoadingState': serverMapLoadingState.UpdateServerMapLoadingState, + 'UpdateServerMapTargetSelected': serverMapSelectedTarget.UpdateServerMapTargetSelected, + 'UpdateServerMapSelectedTargetByList': targetList.UpdateServerMapSelectedTargetByList, + 'UpdateFilterOfServerAndAgentList': serverAndAgent.UpdateFilterOfServerAndAgentList, + 'UpdateAgentInfo': agentInfo.UpdateAgentInfo, + 'UpdateAdminAgentList': admin.UpdateAdminAgentList, + 'ChangeServerMapDisableState': uiState.ChangeServerMapDisableState, + 'ChangeInfoPerServerVisibleState': uiState.ChangeInfoPerServerVisibleState, + 'UpdateTimelineSelectionRange': timeline.UpdateTimelineSelectionRange, + 'UpdateTimelineRange': timeline.UpdateTimelineRange, + 'UpdateTimelineSelectedTime': timeline.UpdateTimelineSelectedTime, + 'UpdateTimelineData': timeline.UpdateTimelineData +}; + +const getUI = createFeatureSelector('uiState'); +export const selectServerMapDisableState = createSelector( + getUI, + (state: IUIState) => state['serverMap'] +); +export const selectInfoPerServerVisibleState = createSelector( + getUI, + (state: IUIState) => state['infoPerServer'] +); + +const getTimeline = createFeatureSelector('timeline'); +export const selectTimelineRange = createSelector( + getTimeline, + (state: ITimelineInfo) => state['range'] +); +export const selectTimelineSelectionRange = createSelector( + getTimeline, + (state: ITimelineInfo) => state['selectionRange'] +); +export const selectTimelineSelectedTime = createSelector( + getTimeline, + (state: ITimelineInfo) => state['selectedTime'] +); + diff --git a/web/src/main/webapp/v2/src/app/shared/store/inspector-chart-hover.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/inspector-chart-hover.reducer.ts new file mode 100644 index 000000000000..985372c10031 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/inspector-chart-hover.reducer.ts @@ -0,0 +1,16 @@ +import { Action } from '@ngrx/store'; + +const SYNC_HOVER_ON_INSPECTOR_CHARTS = 'SYNC_HOVER_ON_INSPECTOR_CHARTS'; +export class ChangeHoverOnInspectorCharts implements Action { + readonly type = SYNC_HOVER_ON_INSPECTOR_CHARTS; + constructor(public payload: IHoveredInfo) {} +} + +export function Reducer(state: IHoveredInfo, action: ChangeHoverOnInspectorCharts): IHoveredInfo { + switch ( action.type ) { + case SYNC_HOVER_ON_INSPECTOR_CHARTS: + return action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/load-chart.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/load-chart.reducer.ts new file mode 100644 index 000000000000..a9354ebc7349 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/load-chart.reducer.ts @@ -0,0 +1,18 @@ +import { Action } from '@ngrx/store'; + +const CHANGE_LOAD_CHART_Y_MAX = 'CHANGE_LOAD_Y_MAX'; + +export class ChangeLoadChartYMax implements Action { + readonly type = CHANGE_LOAD_CHART_Y_MAX; + constructor(public payload: number) {} +} + +export function Reducer(state = 0, action: ChangeLoadChartYMax) { + switch (action.type) { + case CHANGE_LOAD_CHART_Y_MAX: + return action.payload; + default: + return state; + } +} + diff --git a/web/src/main/webapp/v2/src/app/shared/store/response-summary-chart.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/response-summary-chart.reducer.ts new file mode 100644 index 000000000000..b26f2d16186c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/response-summary-chart.reducer.ts @@ -0,0 +1,18 @@ +import { Action } from '@ngrx/store'; + +const CHANGE_RESPONSE_SUMMARY_CHART_Y_MAX = 'CHANGE_RESPONSE_SUMMARY_Y_MAX'; + +export class ChangeResponseSummaryChartYMax implements Action { + readonly type = CHANGE_RESPONSE_SUMMARY_CHART_Y_MAX; + constructor(public payload: number) {} +} + +export function Reducer(state = 0, action: ChangeResponseSummaryChartYMax) { + switch (action.type) { + case CHANGE_RESPONSE_SUMMARY_CHART_Y_MAX: + return action.payload; + default: + return state; + } +} + diff --git a/web/src/main/webapp/v2/src/app/shared/store/scatter-chart-real-time.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/scatter-chart-real-time.reducer.ts new file mode 100644 index 000000000000..3542197ef038 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/scatter-chart-real-time.reducer.ts @@ -0,0 +1,20 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_REAL_TIME_SCATTER_CHART_X_RANGE = 'UPDATE_REAL_TIME_SCATTER_CHART_X_RANGE'; + +export class UpdateRealTimeScatterChartXRange implements Action { + readonly type = UPDATE_REAL_TIME_SCATTER_CHART_X_RANGE; + constructor(public payload: IScatterXRange) {} +} + +export function Reducer(state: IScatterXRange = {from: -1, to: -1}, action: UpdateRealTimeScatterChartXRange): IScatterXRange { + switch (action.type) { + case UPDATE_REAL_TIME_SCATTER_CHART_X_RANGE: + if (state.from === action.payload.from && state.to === action.payload.to) { + return state; + } + return action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/scatter-chart.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/scatter-chart.reducer.ts new file mode 100644 index 000000000000..e3d26d31682c --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/scatter-chart.reducer.ts @@ -0,0 +1,17 @@ +import { Action } from '@ngrx/store'; + +const ADD_SCATTER_CHART_DATA = 'UPDATE_SCATTER_CHART_DATA'; + +export class AddScatterChartData implements Action { + readonly type = ADD_SCATTER_CHART_DATA; + constructor(public payload: IScatterData) {} +} + +export function Reducer(state: IScatterData[] = [], action: AddScatterChartData): IScatterData[] { + switch (action.type) { + case ADD_SCATTER_CHART_DATA: + return state.concat(action.payload); + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/server-and-agent.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/server-and-agent.reducer.ts new file mode 100644 index 000000000000..554730a98789 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/server-and-agent.reducer.ts @@ -0,0 +1,16 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_FILTER_OF_SERVER_AND_AGENT_LIST = 'UPDATE_FILTER_OF_SERVER_AND_AGENT_LIST'; +export class UpdateFilterOfServerAndAgentList implements Action { + readonly type = UPDATE_FILTER_OF_SERVER_AND_AGENT_LIST; + constructor(public payload: string) {} +} + +export function Reducer(state = '', action: UpdateFilterOfServerAndAgentList): string { + switch (action.type) { + case UPDATE_FILTER_OF_SERVER_AND_AGENT_LIST: + return state === action.payload ? state : action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/server-list.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/server-list.reducer.ts new file mode 100644 index 000000000000..aa6ae2ea8dc4 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/server-list.reducer.ts @@ -0,0 +1,17 @@ +import { Action } from '@ngrx/store'; + +export const UPDATE_SERVER_LIST = 'UPDATE_SERVER_LIST'; + +export class UpdateServerList implements Action { + readonly type = UPDATE_SERVER_LIST; + constructor(public payload: any) {} +} + +export function Reducer(state = {}, action: UpdateServerList): any { + switch (action.type) { + case UPDATE_SERVER_LIST: + return action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/server-map-loading-state.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/server-map-loading-state.reducer.ts new file mode 100644 index 000000000000..dda4707d6999 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/server-map-loading-state.reducer.ts @@ -0,0 +1,16 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_SERVER_MAP_LOADING_STATE = 'UPDATE_LOADING_STATE'; + +export class UpdateServerMapLoadingState implements Action { + readonly type = UPDATE_SERVER_MAP_LOADING_STATE; + constructor(public payload: string) {} +} +export function Reducer(state = 'LOADING', action: UpdateServerMapLoadingState): string { + switch (action.type) { + case UPDATE_SERVER_MAP_LOADING_STATE: + return action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/server-map-selected-target.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/server-map-selected-target.reducer.ts new file mode 100644 index 000000000000..11d1db7e0826 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/server-map-selected-target.reducer.ts @@ -0,0 +1,32 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_SERVER_MAP_TARGET_SELECTED = 'UPDATE_SERVER_MAP_TARGET_SELECTED'; + +export class UpdateServerMapTargetSelected implements Action { + readonly type = UPDATE_SERVER_MAP_TARGET_SELECTED; + constructor(public payload: ISelectedTarget) {} +} + +export function Reducer(state: ISelectedTarget, action: UpdateServerMapTargetSelected): ISelectedTarget { + switch (action.type) { + case UPDATE_SERVER_MAP_TARGET_SELECTED: + if (action.payload === null) { + return {} as ISelectedTarget; + } else if ( + state && + state.endTime === action.payload.endTime && + state.period === action.payload.period && + state.isWAS === action.payload.isWAS && + (state.isNode === action.payload.isNode + ? (state.isNode ? state.node[0] === action.payload.node[0] : state.link[0] === action.payload.link[0]) + : false + ) + ) { + return state; + } else { + return action.payload; + } + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/server-map.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/server-map.reducer.ts new file mode 100644 index 000000000000..7f8464858118 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/server-map.reducer.ts @@ -0,0 +1,17 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_SERVER_MAP_DATA = 'UPDATE_SERVER_MAP_DATA'; + +export class UpdateServerMapData implements Action { + readonly type = UPDATE_SERVER_MAP_DATA; + constructor(public payload: any) {} +} + +export function Reducer(state = {}, action: UpdateServerMapData): any { + switch (action.type) { + case UPDATE_SERVER_MAP_DATA: + return action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/target-list.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/target-list.reducer.ts new file mode 100644 index 000000000000..2d0ed134fc8b --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/target-list.reducer.ts @@ -0,0 +1,20 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_SERVER_MAP_SELECTED_TARGET_BY_LIST = 'UPDATE_SERVER_MAP_SELECTED_TARGET_BY_LIST'; + +export class UpdateServerMapSelectedTargetByList implements Action { + readonly type = UPDATE_SERVER_MAP_SELECTED_TARGET_BY_LIST; + constructor(public payload: any) {} +} +export function Reducer(state: any, action: UpdateServerMapSelectedTargetByList): any { + switch (action.type) { + case UPDATE_SERVER_MAP_SELECTED_TARGET_BY_LIST: + if (state && state.key === action.payload.key) { + return state; + } else { + return action.payload; + } + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/timeline.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/timeline.reducer.ts new file mode 100644 index 000000000000..7beb90856ee1 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/timeline.reducer.ts @@ -0,0 +1,74 @@ +import { Action } from '@ngrx/store'; + +const initState: ITimelineInfo = { + range: [0, 0], + selectedTime: 0, + selectionRange: [0, 0], +}; + +const UPDATE_TIMELINE_INFO = 'UPDATE_TIMELINE_INFO'; +const UPDATE_TIMELINE_SELECTED_TIME = 'UPDATE_TIMELINE_SELECTED_TIME'; +const UPDATE_TIMELINE_SELECTION_RANGE = 'UPDATE_TIMELINE_SELECTION_RANGE'; +const UPDATE_TIMELINE_RANGE = 'UPDATE_TIMELINE_RANGE'; + +export class UpdateTimelineData implements Action { + readonly type = UPDATE_TIMELINE_INFO; + constructor(public payload: ITimelineInfo) {} +} +export class UpdateTimelineSelectedTime implements Action { + readonly type = UPDATE_TIMELINE_SELECTED_TIME; + constructor(public payload: number) {} +} +export class UpdateTimelineSelectionRange implements Action { + readonly type = UPDATE_TIMELINE_SELECTION_RANGE; + constructor(public payload: number[]) {} +} +export class UpdateTimelineRange implements Action { + readonly type = UPDATE_TIMELINE_RANGE; + constructor(public payload: number[]) {} +} +export function Reducer(state = initState, action: UpdateTimelineData | UpdateTimelineSelectedTime | UpdateTimelineSelectionRange | UpdateTimelineRange): ITimelineInfo { + switch (action.type) { + case UPDATE_TIMELINE_INFO: + if ( + state.range[0] !== action.payload.range[0] || + state.range[1] !== action.payload.range[1] || + state.selectedTime !== action.payload.selectedTime || + state.selectionRange[0] !== action.payload.selectionRange[0] || + state.selectionRange[1] !== action.payload.selectionRange[1] + ) { + return action.payload; + } else { + return state; + } + case UPDATE_TIMELINE_SELECTED_TIME: + if (state.selectedTime === action.payload) { + return state; + } else { + return { + ...state, + selectedTime : action.payload + }; + } + case UPDATE_TIMELINE_SELECTION_RANGE: + if (state.selectionRange[0] === action.payload[0] && state.selectionRange[1] === action.payload[1]) { + return state; + } else { + return { + ...state, + selectionRange: action.payload + }; + } + case UPDATE_TIMELINE_RANGE: + if (state.range[0] === action.payload[0] && state.range[1] === action.payload[1]) { + return state; + } else { + return { + ...state, + range: action.payload + }; + } + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/timezone.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/timezone.reducer.ts new file mode 100644 index 000000000000..d49657b361b5 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/timezone.reducer.ts @@ -0,0 +1,16 @@ +import { Action } from '@ngrx/store'; + +const CHANGE_TIMEZONE = 'CHANGE_TIMEZONE'; +export class ChangeTimezone implements Action { + readonly type = CHANGE_TIMEZONE; + constructor(public payload: string) {} +} + +export function Reducer(state = 'Asia/Seoul', action: ChangeTimezone): string { + switch (action.type) { + case CHANGE_TIMEZONE: + return (state === action.payload) ? state : action.payload; + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/transaction-detail-data.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/transaction-detail-data.reducer.ts new file mode 100644 index 000000000000..43e0dcd18a82 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/transaction-detail-data.reducer.ts @@ -0,0 +1,21 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_TRANSACTION_DETAIL_DATA = 'UPDATE_TRANSACTION_DETAIL_DATA'; + +export class UpdateTransactionDetailData implements Action { + readonly type = UPDATE_TRANSACTION_DETAIL_DATA; + constructor(public payload: ITransactionDetailData) {} +} + +export function Reducer(state: ITransactionDetailData, action: UpdateTransactionDetailData): ITransactionDetailData { + switch ( action.type ) { + case UPDATE_TRANSACTION_DETAIL_DATA: + if (state && (state.agentId === action.payload.agentId && state.applicationId === action.payload['applicationId'] && state.transactionId === action.payload.transactionId)) { + return state; + } else { + return action.payload; + } + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/app/shared/store/transaction-info.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/transaction-info.reducer.ts new file mode 100644 index 000000000000..446b94ffac84 --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/transaction-info.reducer.ts @@ -0,0 +1,22 @@ +import { Action } from '@ngrx/store'; + +const UPDATE_TRANSACTION_DATA = 'UPDATE_TRANSACTION_DATA'; + +export class UpdateTransactionData implements Action { + readonly type = UPDATE_TRANSACTION_DATA; + constructor(public payload: ITransactionMetaData) {} +} + +export function Reducer(state: ITransactionMetaData, action: UpdateTransactionData): ITransactionMetaData { + switch ( action.type ) { + case UPDATE_TRANSACTION_DATA: + if (state && (state.traceId === action.payload.traceId && state.collectorAcceptTime === action.payload.collectorAcceptTime && state.elapsed === action.payload.elapsed)) { + return state; + } else { + return action.payload; + } + default: + return state; + } +} + diff --git a/web/src/main/webapp/v2/src/app/shared/store/ui-state.reducer.ts b/web/src/main/webapp/v2/src/app/shared/store/ui-state.reducer.ts new file mode 100644 index 000000000000..333e63d97e5a --- /dev/null +++ b/web/src/main/webapp/v2/src/app/shared/store/ui-state.reducer.ts @@ -0,0 +1,43 @@ +import { Action } from '@ngrx/store'; + +const CHANGE_INFO_PER_SERVER_VISIBLE_STATE = 'CHANGE_INFO_PER_SERVER_VISIBLE_STATE'; +const CHANGE_SERVER_MAP_DISABLE_STATE = 'CHANGE_SERVER_MAP_DISABLE_STATE'; + +const initState = { + infoPerServer: false, + serverMap: false +}; + +export class ChangeInfoPerServerVisibleState implements Action { + readonly type = CHANGE_INFO_PER_SERVER_VISIBLE_STATE; + constructor(public payload: boolean) {} +} +export class ChangeServerMapDisableState implements Action { + readonly type = CHANGE_SERVER_MAP_DISABLE_STATE; + constructor(public payload: boolean) {} +} + +export function Reducer(state: IUIState = initState, action: ChangeInfoPerServerVisibleState | ChangeServerMapDisableState): IUIState { + switch (action.type) { + case CHANGE_INFO_PER_SERVER_VISIBLE_STATE: + if (action.payload === state['infoPerServer']) { + return state; + } else { + return { + ...state, + 'infoPerServer': action.payload + }; + } + case CHANGE_SERVER_MAP_DISABLE_STATE: + if (action.payload === state['serverMap']) { + return state; + } else { + return { + ...state, + 'serverMap': action.payload + }; + } + default: + return state; + } +} diff --git a/web/src/main/webapp/v2/src/assets/.gitkeep b/web/src/main/webapp/v2/src/assets/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/css/fontawesome-all.css b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/css/fontawesome-all.css new file mode 100644 index 000000000000..ec052ff5bd51 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/css/fontawesome-all.css @@ -0,0 +1,2861 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa, +.fas, +.far, +.fal, +.fab { + -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: 0.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: solid 0.08em #eee; + border-radius: .1em; + padding: .2em .25em .15em; } + +.fa-pull-left { + float: left; } + +.fa-pull-right { + float: right; } + +.fa.fa-pull-left, +.fas.fa-pull-left, +.far.fa-pull-left, +.fal.fa-pull-left, +.fab.fa-pull-left { + margin-right: .3em; } + +.fa.fa-pull-right, +.fas.fa-pull-right, +.far.fa-pull-right, +.fal.fa-pull-right, +.fab.fa-pull-right { + margin-left: .3em; } + +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; } + +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); } + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } + +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + transform: rotate(180deg); } + +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + transform: scale(-1, 1); } + +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + transform: scale(1, -1); } + +.fa-flip-horizontal.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(-1, -1); + transform: scale(-1, -1); } + +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + -webkit-filter: none; + filter: none; } + +.fa-stack { + display: inline-block; + height: 2em; + line-height: 2em; + position: relative; + vertical-align: middle; + width: 2em; } + +.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; } + +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen +readers do not read off random characters that represent icons */ +.fa-500px:before { + content: "\f26e"; } + +.fa-accessible-icon:before { + content: "\f368"; } + +.fa-accusoft:before { + content: "\f369"; } + +.fa-address-book:before { + content: "\f2b9"; } + +.fa-address-card:before { + content: "\f2bb"; } + +.fa-adjust:before { + content: "\f042"; } + +.fa-adn:before { + content: "\f170"; } + +.fa-adversal:before { + content: "\f36a"; } + +.fa-affiliatetheme:before { + content: "\f36b"; } + +.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-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-angrycreative:before { + content: "\f36e"; } + +.fa-angular:before { + content: "\f420"; } + +.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-pay:before { + content: "\f415"; } + +.fa-archive:before { + content: "\f187"; } + +.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-assistive-listening-systems:before { + content: "\f2a2"; } + +.fa-asterisk:before { + content: "\f069"; } + +.fa-asymmetrik:before { + content: "\f372"; } + +.fa-at:before { + content: "\f1fa"; } + +.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-aws:before { + content: "\f375"; } + +.fa-backward:before { + content: "\f04a"; } + +.fa-balance-scale:before { + content: "\f24e"; } + +.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-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-bicycle:before { + content: "\f206"; } + +.fa-bimobject:before { + content: "\f378"; } + +.fa-binoculars:before { + content: "\f1e5"; } + +.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-blind:before { + content: "\f29d"; } + +.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-book:before { + content: "\f02d"; } + +.fa-bookmark:before { + content: "\f02e"; } + +.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-briefcase:before { + content: "\f0b1"; } + +.fa-briefcase-medical:before { + content: "\f469"; } + +.fa-btc:before { + content: "\f15a"; } + +.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-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-minus:before { + content: "\f272"; } + +.fa-calendar-plus:before { + content: "\f271"; } + +.fa-calendar-times:before { + content: "\f273"; } + +.fa-camera:before { + content: "\f030"; } + +.fa-camera-retro:before { + content: "\f083"; } + +.fa-capsules:before { + content: "\f46b"; } + +.fa-car:before { + content: "\f1b9"; } + +.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-cart-arrow-down:before { + content: "\f218"; } + +.fa-cart-plus:before { + content: "\f217"; } + +.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-certificate:before { + content: "\f0a3"; } + +.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-square:before { + content: "\f14a"; } + +.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-circle:before { + content: "\f111"; } + +.fa-circle-notch:before { + content: "\f1ce"; } + +.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-upload-alt:before { + content: "\f382"; } + +.fa-cloudscale:before { + content: "\f383"; } + +.fa-cloudsmith:before { + content: "\f384"; } + +.fa-cloudversify:before { + content: "\f385"; } + +.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-columns:before { + content: "\f0db"; } + +.fa-comment:before { + content: "\f075"; } + +.fa-comment-alt:before { + content: "\f27a"; } + +.fa-comment-dots:before { + content: "\f4ad"; } + +.fa-comment-slash:before { + content: "\f4b3"; } + +.fa-comments:before { + content: "\f086"; } + +.fa-compass:before { + content: "\f14e"; } + +.fa-compress:before { + content: "\f066"; } + +.fa-connectdevelop:before { + content: "\f20e"; } + +.fa-contao:before { + content: "\f26d"; } + +.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-credit-card:before { + content: "\f09d"; } + +.fa-crop:before { + content: "\f125"; } + +.fa-crosshairs:before { + content: "\f05b"; } + +.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-dashcube:before { + content: "\f210"; } + +.fa-database:before { + content: "\f1c0"; } + +.fa-deaf:before { + content: "\f2a4"; } + +.fa-delicious:before { + content: "\f1a5"; } + +.fa-deploydog:before { + content: "\f38e"; } + +.fa-deskpro:before { + content: "\f38f"; } + +.fa-desktop:before { + content: "\f108"; } + +.fa-deviantart:before { + content: "\f1bd"; } + +.fa-diagnoses:before { + content: "\f470"; } + +.fa-digg:before { + content: "\f1a6"; } + +.fa-digital-ocean:before { + content: "\f391"; } + +.fa-discord:before { + content: "\f392"; } + +.fa-discourse:before { + content: "\f393"; } + +.fa-dna:before { + content: "\f471"; } + +.fa-dochub:before { + content: "\f394"; } + +.fa-docker:before { + content: "\f395"; } + +.fa-dollar-sign:before { + content: "\f155"; } + +.fa-dolly:before { + content: "\f472"; } + +.fa-dolly-flatbed:before { + content: "\f474"; } + +.fa-donate:before { + content: "\f4b9"; } + +.fa-dot-circle:before { + content: "\f192"; } + +.fa-dove:before { + content: "\f4ba"; } + +.fa-download:before { + content: "\f019"; } + +.fa-draft2digital:before { + content: "\f396"; } + +.fa-dribbble:before { + content: "\f17d"; } + +.fa-dribbble-square:before { + content: "\f397"; } + +.fa-dropbox:before { + content: "\f16b"; } + +.fa-drupal:before { + content: "\f1a9"; } + +.fa-dyalog:before { + content: "\f399"; } + +.fa-earlybirds:before { + content: "\f39a"; } + +.fa-edge:before { + content: "\f282"; } + +.fa-edit:before { + content: "\f044"; } + +.fa-eject:before { + content: "\f052"; } + +.fa-elementor:before { + content: "\f430"; } + +.fa-ellipsis-h:before { + content: "\f141"; } + +.fa-ellipsis-v:before { + content: "\f142"; } + +.fa-ember:before { + content: "\f423"; } + +.fa-empire:before { + content: "\f1d1"; } + +.fa-envelope:before { + content: "\f0e0"; } + +.fa-envelope-open:before { + content: "\f2b6"; } + +.fa-envelope-square:before { + content: "\f199"; } + +.fa-envira:before { + content: "\f299"; } + +.fa-eraser:before { + content: "\f12d"; } + +.fa-erlang:before { + content: "\f39d"; } + +.fa-ethereum:before { + content: "\f42e"; } + +.fa-etsy:before { + content: "\f2d7"; } + +.fa-euro-sign:before { + content: "\f153"; } + +.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-fast-backward:before { + content: "\f049"; } + +.fa-fast-forward:before { + content: "\f050"; } + +.fa-fax:before { + content: "\f1ac"; } + +.fa-female:before { + content: "\f182"; } + +.fa-fighter-jet:before { + content: "\f0fb"; } + +.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-excel:before { + content: "\f1c3"; } + +.fa-file-image:before { + content: "\f1c5"; } + +.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-video:before { + content: "\f1c8"; } + +.fa-file-word:before { + content: "\f1c2"; } + +.fa-film:before { + content: "\f008"; } + +.fa-filter:before { + content: "\f0b0"; } + +.fa-fire:before { + content: "\f06d"; } + +.fa-fire-extinguisher:before { + content: "\f134"; } + +.fa-firefox:before { + content: "\f269"; } + +.fa-first-aid:before { + content: "\f479"; } + +.fa-first-order:before { + content: "\f2b0"; } + +.fa-firstdraft:before { + content: "\f3a1"; } + +.fa-flag:before { + content: "\f024"; } + +.fa-flag-checkered:before { + content: "\f11e"; } + +.fa-flask:before { + content: "\f0c3"; } + +.fa-flickr:before { + content: "\f16e"; } + +.fa-flipboard:before { + content: "\f44d"; } + +.fa-fly:before { + content: "\f417"; } + +.fa-folder:before { + content: "\f07b"; } + +.fa-folder-open:before { + content: "\f07c"; } + +.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-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-frown:before { + content: "\f119"; } + +.fa-futbol:before { + content: "\f1e3"; } + +.fa-gamepad:before { + content: "\f11b"; } + +.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-gift:before { + content: "\f06b"; } + +.fa-git:before { + content: "\f1d3"; } + +.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-martini:before { + content: "\f000"; } + +.fa-glide:before { + content: "\f2a5"; } + +.fa-glide-g:before { + content: "\f2a6"; } + +.fa-globe:before { + content: "\f0ac"; } + +.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-graduation-cap:before { + content: "\f19d"; } + +.fa-gratipay:before { + content: "\f184"; } + +.fa-grav:before { + content: "\f2d6"; } + +.fa-gripfire:before { + content: "\f3ac"; } + +.fa-grunt:before { + content: "\f3ad"; } + +.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-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-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-hashtag:before { + content: "\f292"; } + +.fa-hdd:before { + content: "\f0a0"; } + +.fa-heading:before { + content: "\f1dc"; } + +.fa-headphones:before { + content: "\f025"; } + +.fa-heart:before { + content: "\f004"; } + +.fa-heartbeat:before { + content: "\f21e"; } + +.fa-hips:before { + content: "\f452"; } + +.fa-hire-a-helper:before { + content: "\f3b0"; } + +.fa-history:before { + content: "\f1da"; } + +.fa-hockey-puck:before { + content: "\f453"; } + +.fa-home:before { + content: "\f015"; } + +.fa-hooli:before { + content: "\f427"; } + +.fa-hospital:before { + content: "\f0f8"; } + +.fa-hospital-alt:before { + content: "\f47d"; } + +.fa-hospital-symbol:before { + content: "\f47e"; } + +.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-houzz:before { + content: "\f27c"; } + +.fa-html5:before { + content: "\f13b"; } + +.fa-hubspot:before { + content: "\f3b2"; } + +.fa-i-cursor:before { + content: "\f246"; } + +.fa-id-badge:before { + content: "\f2c1"; } + +.fa-id-card:before { + content: "\f2c2"; } + +.fa-id-card-alt:before { + content: "\f47f"; } + +.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-info:before { + content: "\f129"; } + +.fa-info-circle:before { + content: "\f05a"; } + +.fa-instagram:before { + content: "\f16d"; } + +.fa-internet-explorer:before { + content: "\f26b"; } + +.fa-ioxhost:before { + content: "\f208"; } + +.fa-italic:before { + content: "\f033"; } + +.fa-itunes:before { + content: "\f3b4"; } + +.fa-itunes-note:before { + content: "\f3b5"; } + +.fa-java:before { + content: "\f4e4"; } + +.fa-jenkins:before { + content: "\f3b6"; } + +.fa-joget:before { + content: "\f3b7"; } + +.fa-joomla:before { + content: "\f1aa"; } + +.fa-js:before { + content: "\f3b8"; } + +.fa-js-square:before { + content: "\f3b9"; } + +.fa-jsfiddle:before { + content: "\f1cc"; } + +.fa-key:before { + content: "\f084"; } + +.fa-keyboard:before { + content: "\f11c"; } + +.fa-keycdn:before { + content: "\f3ba"; } + +.fa-kickstarter:before { + content: "\f3bb"; } + +.fa-kickstarter-k:before { + content: "\f3bc"; } + +.fa-korvue:before { + content: "\f42f"; } + +.fa-language:before { + content: "\f1ab"; } + +.fa-laptop:before { + content: "\f109"; } + +.fa-laravel:before { + content: "\f3bd"; } + +.fa-lastfm:before { + content: "\f202"; } + +.fa-lastfm-square:before { + content: "\f203"; } + +.fa-leaf:before { + content: "\f06c"; } + +.fa-leanpub:before { + content: "\f212"; } + +.fa-lemon:before { + content: "\f094"; } + +.fa-less:before { + content: "\f41d"; } + +.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-lyft:before { + content: "\f3c3"; } + +.fa-magento:before { + content: "\f3c4"; } + +.fa-magic:before { + content: "\f0d0"; } + +.fa-magnet:before { + content: "\f076"; } + +.fa-male:before { + content: "\f183"; } + +.fa-map:before { + content: "\f279"; } + +.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-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-maxcdn:before { + content: "\f136"; } + +.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-meh:before { + content: "\f11a"; } + +.fa-mercury:before { + content: "\f223"; } + +.fa-microchip:before { + content: "\f2db"; } + +.fa-microphone:before { + content: "\f130"; } + +.fa-microphone-slash:before { + content: "\f131"; } + +.fa-microsoft:before { + content: "\f3ca"; } + +.fa-minus:before { + content: "\f068"; } + +.fa-minus-circle:before { + content: "\f056"; } + +.fa-minus-square:before { + content: "\f146"; } + +.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-alt:before { + content: "\f3d1"; } + +.fa-moon:before { + content: "\f186"; } + +.fa-motorcycle:before { + content: "\f21c"; } + +.fa-mouse-pointer:before { + content: "\f245"; } + +.fa-music:before { + content: "\f001"; } + +.fa-napster:before { + content: "\f3d2"; } + +.fa-neuter:before { + content: "\f22c"; } + +.fa-newspaper:before { + content: "\f1ea"; } + +.fa-nintendo-switch:before { + content: "\f418"; } + +.fa-node:before { + content: "\f419"; } + +.fa-node-js:before { + content: "\f3d3"; } + +.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-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-outdent:before { + content: "\f03b"; } + +.fa-page4:before { + content: "\f3d7"; } + +.fa-pagelines:before { + content: "\f18c"; } + +.fa-paint-brush:before { + content: "\f1fc"; } + +.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-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-pen-square:before { + content: "\f14b"; } + +.fa-pencil-alt:before { + content: "\f303"; } + +.fa-people-carry:before { + content: "\f4ce"; } + +.fa-percent:before { + content: "\f295"; } + +.fa-periscope:before { + content: "\f3da"; } + +.fa-phabricator:before { + content: "\f3db"; } + +.fa-phoenix-framework:before { + content: "\f3dc"; } + +.fa-phone:before { + content: "\f095"; } + +.fa-phone-slash:before { + content: "\f3dd"; } + +.fa-phone-square:before { + content: "\f098"; } + +.fa-phone-volume:before { + content: "\f2a0"; } + +.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-plane:before { + content: "\f072"; } + +.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-poo:before { + content: "\f2fe"; } + +.fa-pound-sign:before { + content: "\f154"; } + +.fa-power-off:before { + content: "\f011"; } + +.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-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-random:before { + content: "\f074"; } + +.fa-ravelry:before { + content: "\f2d9"; } + +.fa-react:before { + content: "\f41b"; } + +.fa-readme:before { + content: "\f4d5"; } + +.fa-rebel:before { + content: "\f1d0"; } + +.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-redo:before { + content: "\f01e"; } + +.fa-redo-alt:before { + content: "\f2f9"; } + +.fa-registered:before { + content: "\f25d"; } + +.fa-rendact:before { + content: "\f3e4"; } + +.fa-renren:before { + content: "\f18b"; } + +.fa-reply:before { + content: "\f3e5"; } + +.fa-reply-all:before { + content: "\f122"; } + +.fa-replyd:before { + content: "\f3e6"; } + +.fa-resolving:before { + content: "\f3e7"; } + +.fa-retweet:before { + content: "\f079"; } + +.fa-ribbon:before { + content: "\f4d6"; } + +.fa-road:before { + content: "\f018"; } + +.fa-rocket:before { + content: "\f135"; } + +.fa-rocketchat:before { + content: "\f3e8"; } + +.fa-rockrms:before { + content: "\f3e9"; } + +.fa-rss:before { + content: "\f09e"; } + +.fa-rss-square:before { + content: "\f143"; } + +.fa-ruble-sign:before { + content: "\f158"; } + +.fa-rupee-sign:before { + content: "\f156"; } + +.fa-safari:before { + content: "\f267"; } + +.fa-sass:before { + content: "\f41e"; } + +.fa-save:before { + content: "\f0c7"; } + +.fa-schlix:before { + content: "\f3ea"; } + +.fa-scribd:before { + content: "\f28a"; } + +.fa-search:before { + content: "\f002"; } + +.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-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-shopping-bag:before { + content: "\f290"; } + +.fa-shopping-basket:before { + content: "\f291"; } + +.fa-shopping-cart:before { + content: "\f07a"; } + +.fa-shower:before { + content: "\f2cc"; } + +.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-simplybuilt:before { + content: "\f215"; } + +.fa-sistrix:before { + content: "\f3ee"; } + +.fa-sitemap:before { + content: "\f0e8"; } + +.fa-skyatlas:before { + content: "\f216"; } + +.fa-skype:before { + content: "\f17e"; } + +.fa-slack:before { + content: "\f198"; } + +.fa-slack-hash:before { + content: "\f3ef"; } + +.fa-sliders-h:before { + content: "\f1de"; } + +.fa-slideshare:before { + content: "\f1e7"; } + +.fa-smile:before { + content: "\f118"; } + +.fa-smoking:before { + content: "\f48d"; } + +.fa-snapchat:before { + content: "\f2ab"; } + +.fa-snapchat-ghost:before { + content: "\f2ac"; } + +.fa-snapchat-square:before { + content: "\f2ad"; } + +.fa-snowflake:before { + content: "\f2dc"; } + +.fa-sort:before { + content: "\f0dc"; } + +.fa-sort-alpha-down:before { + content: "\f15d"; } + +.fa-sort-alpha-up:before { + content: "\f15e"; } + +.fa-sort-amount-down:before { + content: "\f160"; } + +.fa-sort-amount-up:before { + content: "\f161"; } + +.fa-sort-down:before { + content: "\f0dd"; } + +.fa-sort-numeric-down:before { + content: "\f162"; } + +.fa-sort-numeric-up:before { + content: "\f163"; } + +.fa-sort-up:before { + content: "\f0de"; } + +.fa-soundcloud:before { + content: "\f1be"; } + +.fa-space-shuttle:before { + content: "\f197"; } + +.fa-speakap:before { + content: "\f3f3"; } + +.fa-spinner:before { + content: "\f110"; } + +.fa-spotify:before { + content: "\f1bc"; } + +.fa-square:before { + content: "\f0c8"; } + +.fa-square-full:before { + content: "\f45c"; } + +.fa-stack-exchange:before { + content: "\f18d"; } + +.fa-stack-overflow:before { + content: "\f16c"; } + +.fa-star:before { + content: "\f005"; } + +.fa-star-half:before { + content: "\f089"; } + +.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-strava:before { + content: "\f428"; } + +.fa-street-view:before { + content: "\f21d"; } + +.fa-strikethrough:before { + content: "\f0cc"; } + +.fa-stripe:before { + content: "\f429"; } + +.fa-stripe-s:before { + content: "\f42a"; } + +.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-sun:before { + content: "\f185"; } + +.fa-superpowers:before { + content: "\f2dd"; } + +.fa-superscript:before { + content: "\f12b"; } + +.fa-supple:before { + content: "\f3f9"; } + +.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-telegram:before { + content: "\f2c6"; } + +.fa-telegram-plane:before { + content: "\f3fe"; } + +.fa-tencent-weibo:before { + content: "\f1d5"; } + +.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-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-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-toggle-off:before { + content: "\f204"; } + +.fa-toggle-on:before { + content: "\f205"; } + +.fa-trademark:before { + content: "\f25c"; } + +.fa-train:before { + content: "\f238"; } + +.fa-transgender:before { + content: "\f224"; } + +.fa-transgender-alt:before { + content: "\f225"; } + +.fa-trash:before { + content: "\f1f8"; } + +.fa-trash-alt:before { + content: "\f2ed"; } + +.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-moving:before { + content: "\f4df"; } + +.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-uikit:before { + content: "\f403"; } + +.fa-umbrella:before { + content: "\f0e9"; } + +.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-usb:before { + content: "\f287"; } + +.fa-user:before { + content: "\f007"; } + +.fa-user-circle:before { + content: "\f2bd"; } + +.fa-user-md:before { + content: "\f0f0"; } + +.fa-user-plus:before { + content: "\f234"; } + +.fa-user-secret:before { + content: "\f21b"; } + +.fa-user-times:before { + content: "\f235"; } + +.fa-users:before { + content: "\f0c0"; } + +.fa-ussunnah:before { + content: "\f407"; } + +.fa-utensil-spoon:before { + content: "\f2e5"; } + +.fa-utensils:before { + content: "\f2e7"; } + +.fa-vaadin:before { + content: "\f408"; } + +.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-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-volleyball-ball:before { + content: "\f45f"; } + +.fa-volume-down:before { + content: "\f027"; } + +.fa-volume-off:before { + content: "\f026"; } + +.fa-volume-up:before { + content: "\f028"; } + +.fa-vuejs:before { + content: "\f41f"; } + +.fa-warehouse:before { + content: "\f494"; } + +.fa-weibo:before { + content: "\f18a"; } + +.fa-weight:before { + content: "\f496"; } + +.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-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-glass:before { + content: "\f4e3"; } + +.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-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-yandex:before { + content: "\f413"; } + +.fa-yandex-international:before { + content: "\f414"; } + +.fa-yelp:before { + content: "\f1e9"; } + +.fa-yen-sign:before { + content: "\f157"; } + +.fa-yoast:before { + content: "\f2b1"; } + +.fa-youtube:before { + content: "\f167"; } + +.fa-youtube-square:before { + content: "\f431"; } + +.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; + 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; + 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-family: 'Font Awesome 5 Free'; + font-weight: 400; } +@font-face { + font-family: 'Font Awesome 5 Free'; + font-style: normal; + font-weight: 900; + 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, +.fas { + font-family: 'Font Awesome 5 Free'; + font-weight: 900; } diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/css/fontawesome-all.min.css b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/css/fontawesome-all.min.css new file mode 100644 index 000000000000..0c0e4e00d5dc --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/css/fontawesome-all.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (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:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{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-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.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-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.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-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-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.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-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.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-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.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-aws:before{content:"\f375"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.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-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-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.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-blind:before{content:"\f29d"}.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-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.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-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-btc:before{content:"\f15a"}.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-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-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.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-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.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-certificate:before{content:"\f0a3"}.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-square:before{content:"\f14a"}.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-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.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-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.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-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.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-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crosshairs:before{content:"\f05b"}.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-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-diagnoses:before{content:"\f470"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drupal:before{content:"\f1a9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.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-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.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-excel:before{content:"\f1c3"}.fa-file-image:before{content:"\f1c5"}.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-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-firstdraft:before{content:"\f3a1"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.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-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-frown:before{content:"\f119"}.fa-futbol:before{content:"\f1e3"}.fa-gamepad:before{content:"\f11b"}.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-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.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-martini:before{content:"\f000"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.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-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.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-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-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-hashtag:before{content:"\f292"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.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-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.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-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-key:before{content:"\f084"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-korvue:before{content:"\f42f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.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-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-male:before{content:"\f183"}.fa-map:before{content:"\f279"}.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-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-maxcdn:before{content:"\f136"}.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-meh:before{content:"\f11a"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.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-alt:before{content:"\f3d1"}.fa-moon:before{content:"\f186"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.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-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-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.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-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-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.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-plane:before{content:"\f072"}.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-poo:before{content:"\f2fe"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.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-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-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.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-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-ribbon:before{content:"\f4d6"}.fa-road:before{content:"\f018"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-rupee-sign:before{content:"\f156"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.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-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-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shower:before{content:"\f2cc"}.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-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smoking:before{content:"\f48d"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-spotify:before{content:"\f1bc"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-star:before{content:"\f005"}.fa-star-half:before{content:"\f089"}.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-strava:before{content:"\f428"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.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-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.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-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.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-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-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-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-trademark:before{content:"\f25c"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.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-moving:before{content:"\f4df"}.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-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.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-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-circle:before{content:"\f2bd"}.fa-user-md:before{content:"\f0f0"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.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-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-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-warehouse:before{content:"\f494"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.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-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-glass:before{content:"\f4e3"}.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-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-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.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:400;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;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;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/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_animated.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_animated.less new file mode 100644 index 000000000000..704ec9510374 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_animated.less @@ -0,0 +1,19 @@ +// Animated Icons +// -------------------------- + +.@{fa-css-prefix}-spin { + animation: fa-spin 2s infinite linear; +} + +.@{fa-css-prefix}-pulse { + animation: fa-spin 1s infinite steps(8); +} + +@keyframes fa-spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_bordered-pulled.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_bordered-pulled.less new file mode 100644 index 000000000000..29a356b423de --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_bordered-pulled.less @@ -0,0 +1,16 @@ +// Bordered & Pulled +// ------------------------- + +.@{fa-css-prefix}-border { + border-radius: .1em; + border: solid .08em @fa-border-color; + padding: .2em .25em .15em; +} + +.@{fa-css-prefix}-pull-left { float: left; } +.@{fa-css-prefix}-pull-right { float: right; } + +.@{fa-css-prefix}, .fas, .far, .fal, .fab { + &.@{fa-css-prefix}-pull-left { margin-right: .3em; } + &.@{fa-css-prefix}-pull-right { margin-left: .3em; } +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_core.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_core.less new file mode 100644 index 000000000000..82031d65235d --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_core.less @@ -0,0 +1,12 @@ +// Base Class Definition +// ------------------------- + +.@{fa-css-prefix}, .fas, .far, .fal, .fab { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_fixed-width.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_fixed-width.less new file mode 100644 index 000000000000..be817c637538 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_fixed-width.less @@ -0,0 +1,6 @@ +// Fixed Width Icons +// ------------------------- +.@{fa-css-prefix}-fw { + text-align: center; + width: (20em / 16); +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_icons.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_icons.less new file mode 100644 index 000000000000..ad23e33180a1 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_icons.less @@ -0,0 +1,878 @@ +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ + +.@{fa-css-prefix}-500px:before { content: @fa-var-500px; } +.@{fa-css-prefix}-accessible-icon:before { content: @fa-var-accessible-icon; } +.@{fa-css-prefix}-accusoft:before { content: @fa-var-accusoft; } +.@{fa-css-prefix}-address-book:before { content: @fa-var-address-book; } +.@{fa-css-prefix}-address-card:before { content: @fa-var-address-card; } +.@{fa-css-prefix}-adjust:before { content: @fa-var-adjust; } +.@{fa-css-prefix}-adn:before { content: @fa-var-adn; } +.@{fa-css-prefix}-adversal:before { content: @fa-var-adversal; } +.@{fa-css-prefix}-affiliatetheme:before { content: @fa-var-affiliatetheme; } +.@{fa-css-prefix}-algolia:before { content: @fa-var-algolia; } +.@{fa-css-prefix}-align-center:before { content: @fa-var-align-center; } +.@{fa-css-prefix}-align-justify:before { content: @fa-var-align-justify; } +.@{fa-css-prefix}-align-left:before { content: @fa-var-align-left; } +.@{fa-css-prefix}-align-right:before { content: @fa-var-align-right; } +.@{fa-css-prefix}-allergies:before { content: @fa-var-allergies; } +.@{fa-css-prefix}-amazon:before { content: @fa-var-amazon; } +.@{fa-css-prefix}-amazon-pay:before { content: @fa-var-amazon-pay; } +.@{fa-css-prefix}-ambulance:before { content: @fa-var-ambulance; } +.@{fa-css-prefix}-american-sign-language-interpreting:before { content: @fa-var-american-sign-language-interpreting; } +.@{fa-css-prefix}-amilia:before { content: @fa-var-amilia; } +.@{fa-css-prefix}-anchor:before { content: @fa-var-anchor; } +.@{fa-css-prefix}-android:before { content: @fa-var-android; } +.@{fa-css-prefix}-angellist:before { content: @fa-var-angellist; } +.@{fa-css-prefix}-angle-double-down:before { content: @fa-var-angle-double-down; } +.@{fa-css-prefix}-angle-double-left:before { content: @fa-var-angle-double-left; } +.@{fa-css-prefix}-angle-double-right:before { content: @fa-var-angle-double-right; } +.@{fa-css-prefix}-angle-double-up:before { content: @fa-var-angle-double-up; } +.@{fa-css-prefix}-angle-down:before { content: @fa-var-angle-down; } +.@{fa-css-prefix}-angle-left:before { content: @fa-var-angle-left; } +.@{fa-css-prefix}-angle-right:before { content: @fa-var-angle-right; } +.@{fa-css-prefix}-angle-up:before { content: @fa-var-angle-up; } +.@{fa-css-prefix}-angrycreative:before { content: @fa-var-angrycreative; } +.@{fa-css-prefix}-angular:before { content: @fa-var-angular; } +.@{fa-css-prefix}-app-store:before { content: @fa-var-app-store; } +.@{fa-css-prefix}-app-store-ios:before { content: @fa-var-app-store-ios; } +.@{fa-css-prefix}-apper:before { content: @fa-var-apper; } +.@{fa-css-prefix}-apple:before { content: @fa-var-apple; } +.@{fa-css-prefix}-apple-pay:before { content: @fa-var-apple-pay; } +.@{fa-css-prefix}-archive:before { content: @fa-var-archive; } +.@{fa-css-prefix}-arrow-alt-circle-down:before { content: @fa-var-arrow-alt-circle-down; } +.@{fa-css-prefix}-arrow-alt-circle-left:before { content: @fa-var-arrow-alt-circle-left; } +.@{fa-css-prefix}-arrow-alt-circle-right:before { content: @fa-var-arrow-alt-circle-right; } +.@{fa-css-prefix}-arrow-alt-circle-up:before { content: @fa-var-arrow-alt-circle-up; } +.@{fa-css-prefix}-arrow-circle-down:before { content: @fa-var-arrow-circle-down; } +.@{fa-css-prefix}-arrow-circle-left:before { content: @fa-var-arrow-circle-left; } +.@{fa-css-prefix}-arrow-circle-right:before { content: @fa-var-arrow-circle-right; } +.@{fa-css-prefix}-arrow-circle-up:before { content: @fa-var-arrow-circle-up; } +.@{fa-css-prefix}-arrow-down:before { content: @fa-var-arrow-down; } +.@{fa-css-prefix}-arrow-left:before { content: @fa-var-arrow-left; } +.@{fa-css-prefix}-arrow-right:before { content: @fa-var-arrow-right; } +.@{fa-css-prefix}-arrow-up:before { content: @fa-var-arrow-up; } +.@{fa-css-prefix}-arrows-alt:before { content: @fa-var-arrows-alt; } +.@{fa-css-prefix}-arrows-alt-h:before { content: @fa-var-arrows-alt-h; } +.@{fa-css-prefix}-arrows-alt-v:before { content: @fa-var-arrows-alt-v; } +.@{fa-css-prefix}-assistive-listening-systems:before { content: @fa-var-assistive-listening-systems; } +.@{fa-css-prefix}-asterisk:before { content: @fa-var-asterisk; } +.@{fa-css-prefix}-asymmetrik:before { content: @fa-var-asymmetrik; } +.@{fa-css-prefix}-at:before { content: @fa-var-at; } +.@{fa-css-prefix}-audible:before { content: @fa-var-audible; } +.@{fa-css-prefix}-audio-description:before { content: @fa-var-audio-description; } +.@{fa-css-prefix}-autoprefixer:before { content: @fa-var-autoprefixer; } +.@{fa-css-prefix}-avianex:before { content: @fa-var-avianex; } +.@{fa-css-prefix}-aviato:before { content: @fa-var-aviato; } +.@{fa-css-prefix}-aws:before { content: @fa-var-aws; } +.@{fa-css-prefix}-backward:before { content: @fa-var-backward; } +.@{fa-css-prefix}-balance-scale:before { content: @fa-var-balance-scale; } +.@{fa-css-prefix}-ban:before { content: @fa-var-ban; } +.@{fa-css-prefix}-band-aid:before { content: @fa-var-band-aid; } +.@{fa-css-prefix}-bandcamp:before { content: @fa-var-bandcamp; } +.@{fa-css-prefix}-barcode:before { content: @fa-var-barcode; } +.@{fa-css-prefix}-bars:before { content: @fa-var-bars; } +.@{fa-css-prefix}-baseball-ball:before { content: @fa-var-baseball-ball; } +.@{fa-css-prefix}-basketball-ball:before { content: @fa-var-basketball-ball; } +.@{fa-css-prefix}-bath:before { content: @fa-var-bath; } +.@{fa-css-prefix}-battery-empty:before { content: @fa-var-battery-empty; } +.@{fa-css-prefix}-battery-full:before { content: @fa-var-battery-full; } +.@{fa-css-prefix}-battery-half:before { content: @fa-var-battery-half; } +.@{fa-css-prefix}-battery-quarter:before { content: @fa-var-battery-quarter; } +.@{fa-css-prefix}-battery-three-quarters:before { content: @fa-var-battery-three-quarters; } +.@{fa-css-prefix}-bed:before { content: @fa-var-bed; } +.@{fa-css-prefix}-beer:before { content: @fa-var-beer; } +.@{fa-css-prefix}-behance:before { content: @fa-var-behance; } +.@{fa-css-prefix}-behance-square:before { content: @fa-var-behance-square; } +.@{fa-css-prefix}-bell:before { content: @fa-var-bell; } +.@{fa-css-prefix}-bell-slash:before { content: @fa-var-bell-slash; } +.@{fa-css-prefix}-bicycle:before { content: @fa-var-bicycle; } +.@{fa-css-prefix}-bimobject:before { content: @fa-var-bimobject; } +.@{fa-css-prefix}-binoculars:before { content: @fa-var-binoculars; } +.@{fa-css-prefix}-birthday-cake:before { content: @fa-var-birthday-cake; } +.@{fa-css-prefix}-bitbucket:before { content: @fa-var-bitbucket; } +.@{fa-css-prefix}-bitcoin:before { content: @fa-var-bitcoin; } +.@{fa-css-prefix}-bity:before { content: @fa-var-bity; } +.@{fa-css-prefix}-black-tie:before { content: @fa-var-black-tie; } +.@{fa-css-prefix}-blackberry:before { content: @fa-var-blackberry; } +.@{fa-css-prefix}-blind:before { content: @fa-var-blind; } +.@{fa-css-prefix}-blogger:before { content: @fa-var-blogger; } +.@{fa-css-prefix}-blogger-b:before { content: @fa-var-blogger-b; } +.@{fa-css-prefix}-bluetooth:before { content: @fa-var-bluetooth; } +.@{fa-css-prefix}-bluetooth-b:before { content: @fa-var-bluetooth-b; } +.@{fa-css-prefix}-bold:before { content: @fa-var-bold; } +.@{fa-css-prefix}-bolt:before { content: @fa-var-bolt; } +.@{fa-css-prefix}-bomb:before { content: @fa-var-bomb; } +.@{fa-css-prefix}-book:before { content: @fa-var-book; } +.@{fa-css-prefix}-bookmark:before { content: @fa-var-bookmark; } +.@{fa-css-prefix}-bowling-ball:before { content: @fa-var-bowling-ball; } +.@{fa-css-prefix}-box:before { content: @fa-var-box; } +.@{fa-css-prefix}-box-open:before { content: @fa-var-box-open; } +.@{fa-css-prefix}-boxes:before { content: @fa-var-boxes; } +.@{fa-css-prefix}-braille:before { content: @fa-var-braille; } +.@{fa-css-prefix}-briefcase:before { content: @fa-var-briefcase; } +.@{fa-css-prefix}-briefcase-medical:before { content: @fa-var-briefcase-medical; } +.@{fa-css-prefix}-btc:before { content: @fa-var-btc; } +.@{fa-css-prefix}-bug:before { content: @fa-var-bug; } +.@{fa-css-prefix}-building:before { content: @fa-var-building; } +.@{fa-css-prefix}-bullhorn:before { content: @fa-var-bullhorn; } +.@{fa-css-prefix}-bullseye:before { content: @fa-var-bullseye; } +.@{fa-css-prefix}-burn:before { content: @fa-var-burn; } +.@{fa-css-prefix}-buromobelexperte:before { content: @fa-var-buromobelexperte; } +.@{fa-css-prefix}-bus:before { content: @fa-var-bus; } +.@{fa-css-prefix}-buysellads:before { content: @fa-var-buysellads; } +.@{fa-css-prefix}-calculator:before { content: @fa-var-calculator; } +.@{fa-css-prefix}-calendar:before { content: @fa-var-calendar; } +.@{fa-css-prefix}-calendar-alt:before { content: @fa-var-calendar-alt; } +.@{fa-css-prefix}-calendar-check:before { content: @fa-var-calendar-check; } +.@{fa-css-prefix}-calendar-minus:before { content: @fa-var-calendar-minus; } +.@{fa-css-prefix}-calendar-plus:before { content: @fa-var-calendar-plus; } +.@{fa-css-prefix}-calendar-times:before { content: @fa-var-calendar-times; } +.@{fa-css-prefix}-camera:before { content: @fa-var-camera; } +.@{fa-css-prefix}-camera-retro:before { content: @fa-var-camera-retro; } +.@{fa-css-prefix}-capsules:before { content: @fa-var-capsules; } +.@{fa-css-prefix}-car:before { content: @fa-var-car; } +.@{fa-css-prefix}-caret-down:before { content: @fa-var-caret-down; } +.@{fa-css-prefix}-caret-left:before { content: @fa-var-caret-left; } +.@{fa-css-prefix}-caret-right:before { content: @fa-var-caret-right; } +.@{fa-css-prefix}-caret-square-down:before { content: @fa-var-caret-square-down; } +.@{fa-css-prefix}-caret-square-left:before { content: @fa-var-caret-square-left; } +.@{fa-css-prefix}-caret-square-right:before { content: @fa-var-caret-square-right; } +.@{fa-css-prefix}-caret-square-up:before { content: @fa-var-caret-square-up; } +.@{fa-css-prefix}-caret-up:before { content: @fa-var-caret-up; } +.@{fa-css-prefix}-cart-arrow-down:before { content: @fa-var-cart-arrow-down; } +.@{fa-css-prefix}-cart-plus:before { content: @fa-var-cart-plus; } +.@{fa-css-prefix}-cc-amazon-pay:before { content: @fa-var-cc-amazon-pay; } +.@{fa-css-prefix}-cc-amex:before { content: @fa-var-cc-amex; } +.@{fa-css-prefix}-cc-apple-pay:before { content: @fa-var-cc-apple-pay; } +.@{fa-css-prefix}-cc-diners-club:before { content: @fa-var-cc-diners-club; } +.@{fa-css-prefix}-cc-discover:before { content: @fa-var-cc-discover; } +.@{fa-css-prefix}-cc-jcb:before { content: @fa-var-cc-jcb; } +.@{fa-css-prefix}-cc-mastercard:before { content: @fa-var-cc-mastercard; } +.@{fa-css-prefix}-cc-paypal:before { content: @fa-var-cc-paypal; } +.@{fa-css-prefix}-cc-stripe:before { content: @fa-var-cc-stripe; } +.@{fa-css-prefix}-cc-visa:before { content: @fa-var-cc-visa; } +.@{fa-css-prefix}-centercode:before { content: @fa-var-centercode; } +.@{fa-css-prefix}-certificate:before { content: @fa-var-certificate; } +.@{fa-css-prefix}-chart-area:before { content: @fa-var-chart-area; } +.@{fa-css-prefix}-chart-bar:before { content: @fa-var-chart-bar; } +.@{fa-css-prefix}-chart-line:before { content: @fa-var-chart-line; } +.@{fa-css-prefix}-chart-pie:before { content: @fa-var-chart-pie; } +.@{fa-css-prefix}-check:before { content: @fa-var-check; } +.@{fa-css-prefix}-check-circle:before { content: @fa-var-check-circle; } +.@{fa-css-prefix}-check-square:before { content: @fa-var-check-square; } +.@{fa-css-prefix}-chess:before { content: @fa-var-chess; } +.@{fa-css-prefix}-chess-bishop:before { content: @fa-var-chess-bishop; } +.@{fa-css-prefix}-chess-board:before { content: @fa-var-chess-board; } +.@{fa-css-prefix}-chess-king:before { content: @fa-var-chess-king; } +.@{fa-css-prefix}-chess-knight:before { content: @fa-var-chess-knight; } +.@{fa-css-prefix}-chess-pawn:before { content: @fa-var-chess-pawn; } +.@{fa-css-prefix}-chess-queen:before { content: @fa-var-chess-queen; } +.@{fa-css-prefix}-chess-rook:before { content: @fa-var-chess-rook; } +.@{fa-css-prefix}-chevron-circle-down:before { content: @fa-var-chevron-circle-down; } +.@{fa-css-prefix}-chevron-circle-left:before { content: @fa-var-chevron-circle-left; } +.@{fa-css-prefix}-chevron-circle-right:before { content: @fa-var-chevron-circle-right; } +.@{fa-css-prefix}-chevron-circle-up:before { content: @fa-var-chevron-circle-up; } +.@{fa-css-prefix}-chevron-down:before { content: @fa-var-chevron-down; } +.@{fa-css-prefix}-chevron-left:before { content: @fa-var-chevron-left; } +.@{fa-css-prefix}-chevron-right:before { content: @fa-var-chevron-right; } +.@{fa-css-prefix}-chevron-up:before { content: @fa-var-chevron-up; } +.@{fa-css-prefix}-child:before { content: @fa-var-child; } +.@{fa-css-prefix}-chrome:before { content: @fa-var-chrome; } +.@{fa-css-prefix}-circle:before { content: @fa-var-circle; } +.@{fa-css-prefix}-circle-notch:before { content: @fa-var-circle-notch; } +.@{fa-css-prefix}-clipboard:before { content: @fa-var-clipboard; } +.@{fa-css-prefix}-clipboard-check:before { content: @fa-var-clipboard-check; } +.@{fa-css-prefix}-clipboard-list:before { content: @fa-var-clipboard-list; } +.@{fa-css-prefix}-clock:before { content: @fa-var-clock; } +.@{fa-css-prefix}-clone:before { content: @fa-var-clone; } +.@{fa-css-prefix}-closed-captioning:before { content: @fa-var-closed-captioning; } +.@{fa-css-prefix}-cloud:before { content: @fa-var-cloud; } +.@{fa-css-prefix}-cloud-download-alt:before { content: @fa-var-cloud-download-alt; } +.@{fa-css-prefix}-cloud-upload-alt:before { content: @fa-var-cloud-upload-alt; } +.@{fa-css-prefix}-cloudscale:before { content: @fa-var-cloudscale; } +.@{fa-css-prefix}-cloudsmith:before { content: @fa-var-cloudsmith; } +.@{fa-css-prefix}-cloudversify:before { content: @fa-var-cloudversify; } +.@{fa-css-prefix}-code:before { content: @fa-var-code; } +.@{fa-css-prefix}-code-branch:before { content: @fa-var-code-branch; } +.@{fa-css-prefix}-codepen:before { content: @fa-var-codepen; } +.@{fa-css-prefix}-codiepie:before { content: @fa-var-codiepie; } +.@{fa-css-prefix}-coffee:before { content: @fa-var-coffee; } +.@{fa-css-prefix}-cog:before { content: @fa-var-cog; } +.@{fa-css-prefix}-cogs:before { content: @fa-var-cogs; } +.@{fa-css-prefix}-columns:before { content: @fa-var-columns; } +.@{fa-css-prefix}-comment:before { content: @fa-var-comment; } +.@{fa-css-prefix}-comment-alt:before { content: @fa-var-comment-alt; } +.@{fa-css-prefix}-comment-dots:before { content: @fa-var-comment-dots; } +.@{fa-css-prefix}-comment-slash:before { content: @fa-var-comment-slash; } +.@{fa-css-prefix}-comments:before { content: @fa-var-comments; } +.@{fa-css-prefix}-compass:before { content: @fa-var-compass; } +.@{fa-css-prefix}-compress:before { content: @fa-var-compress; } +.@{fa-css-prefix}-connectdevelop:before { content: @fa-var-connectdevelop; } +.@{fa-css-prefix}-contao:before { content: @fa-var-contao; } +.@{fa-css-prefix}-copy:before { content: @fa-var-copy; } +.@{fa-css-prefix}-copyright:before { content: @fa-var-copyright; } +.@{fa-css-prefix}-couch:before { content: @fa-var-couch; } +.@{fa-css-prefix}-cpanel:before { content: @fa-var-cpanel; } +.@{fa-css-prefix}-creative-commons:before { content: @fa-var-creative-commons; } +.@{fa-css-prefix}-credit-card:before { content: @fa-var-credit-card; } +.@{fa-css-prefix}-crop:before { content: @fa-var-crop; } +.@{fa-css-prefix}-crosshairs:before { content: @fa-var-crosshairs; } +.@{fa-css-prefix}-css3:before { content: @fa-var-css3; } +.@{fa-css-prefix}-css3-alt:before { content: @fa-var-css3-alt; } +.@{fa-css-prefix}-cube:before { content: @fa-var-cube; } +.@{fa-css-prefix}-cubes:before { content: @fa-var-cubes; } +.@{fa-css-prefix}-cut:before { content: @fa-var-cut; } +.@{fa-css-prefix}-cuttlefish:before { content: @fa-var-cuttlefish; } +.@{fa-css-prefix}-d-and-d:before { content: @fa-var-d-and-d; } +.@{fa-css-prefix}-dashcube:before { content: @fa-var-dashcube; } +.@{fa-css-prefix}-database:before { content: @fa-var-database; } +.@{fa-css-prefix}-deaf:before { content: @fa-var-deaf; } +.@{fa-css-prefix}-delicious:before { content: @fa-var-delicious; } +.@{fa-css-prefix}-deploydog:before { content: @fa-var-deploydog; } +.@{fa-css-prefix}-deskpro:before { content: @fa-var-deskpro; } +.@{fa-css-prefix}-desktop:before { content: @fa-var-desktop; } +.@{fa-css-prefix}-deviantart:before { content: @fa-var-deviantart; } +.@{fa-css-prefix}-diagnoses:before { content: @fa-var-diagnoses; } +.@{fa-css-prefix}-digg:before { content: @fa-var-digg; } +.@{fa-css-prefix}-digital-ocean:before { content: @fa-var-digital-ocean; } +.@{fa-css-prefix}-discord:before { content: @fa-var-discord; } +.@{fa-css-prefix}-discourse:before { content: @fa-var-discourse; } +.@{fa-css-prefix}-dna:before { content: @fa-var-dna; } +.@{fa-css-prefix}-dochub:before { content: @fa-var-dochub; } +.@{fa-css-prefix}-docker:before { content: @fa-var-docker; } +.@{fa-css-prefix}-dollar-sign:before { content: @fa-var-dollar-sign; } +.@{fa-css-prefix}-dolly:before { content: @fa-var-dolly; } +.@{fa-css-prefix}-dolly-flatbed:before { content: @fa-var-dolly-flatbed; } +.@{fa-css-prefix}-donate:before { content: @fa-var-donate; } +.@{fa-css-prefix}-dot-circle:before { content: @fa-var-dot-circle; } +.@{fa-css-prefix}-dove:before { content: @fa-var-dove; } +.@{fa-css-prefix}-download:before { content: @fa-var-download; } +.@{fa-css-prefix}-draft2digital:before { content: @fa-var-draft2digital; } +.@{fa-css-prefix}-dribbble:before { content: @fa-var-dribbble; } +.@{fa-css-prefix}-dribbble-square:before { content: @fa-var-dribbble-square; } +.@{fa-css-prefix}-dropbox:before { content: @fa-var-dropbox; } +.@{fa-css-prefix}-drupal:before { content: @fa-var-drupal; } +.@{fa-css-prefix}-dyalog:before { content: @fa-var-dyalog; } +.@{fa-css-prefix}-earlybirds:before { content: @fa-var-earlybirds; } +.@{fa-css-prefix}-edge:before { content: @fa-var-edge; } +.@{fa-css-prefix}-edit:before { content: @fa-var-edit; } +.@{fa-css-prefix}-eject:before { content: @fa-var-eject; } +.@{fa-css-prefix}-elementor:before { content: @fa-var-elementor; } +.@{fa-css-prefix}-ellipsis-h:before { content: @fa-var-ellipsis-h; } +.@{fa-css-prefix}-ellipsis-v:before { content: @fa-var-ellipsis-v; } +.@{fa-css-prefix}-ember:before { content: @fa-var-ember; } +.@{fa-css-prefix}-empire:before { content: @fa-var-empire; } +.@{fa-css-prefix}-envelope:before { content: @fa-var-envelope; } +.@{fa-css-prefix}-envelope-open:before { content: @fa-var-envelope-open; } +.@{fa-css-prefix}-envelope-square:before { content: @fa-var-envelope-square; } +.@{fa-css-prefix}-envira:before { content: @fa-var-envira; } +.@{fa-css-prefix}-eraser:before { content: @fa-var-eraser; } +.@{fa-css-prefix}-erlang:before { content: @fa-var-erlang; } +.@{fa-css-prefix}-ethereum:before { content: @fa-var-ethereum; } +.@{fa-css-prefix}-etsy:before { content: @fa-var-etsy; } +.@{fa-css-prefix}-euro-sign:before { content: @fa-var-euro-sign; } +.@{fa-css-prefix}-exchange-alt:before { content: @fa-var-exchange-alt; } +.@{fa-css-prefix}-exclamation:before { content: @fa-var-exclamation; } +.@{fa-css-prefix}-exclamation-circle:before { content: @fa-var-exclamation-circle; } +.@{fa-css-prefix}-exclamation-triangle:before { content: @fa-var-exclamation-triangle; } +.@{fa-css-prefix}-expand:before { content: @fa-var-expand; } +.@{fa-css-prefix}-expand-arrows-alt:before { content: @fa-var-expand-arrows-alt; } +.@{fa-css-prefix}-expeditedssl:before { content: @fa-var-expeditedssl; } +.@{fa-css-prefix}-external-link-alt:before { content: @fa-var-external-link-alt; } +.@{fa-css-prefix}-external-link-square-alt:before { content: @fa-var-external-link-square-alt; } +.@{fa-css-prefix}-eye:before { content: @fa-var-eye; } +.@{fa-css-prefix}-eye-dropper:before { content: @fa-var-eye-dropper; } +.@{fa-css-prefix}-eye-slash:before { content: @fa-var-eye-slash; } +.@{fa-css-prefix}-facebook:before { content: @fa-var-facebook; } +.@{fa-css-prefix}-facebook-f:before { content: @fa-var-facebook-f; } +.@{fa-css-prefix}-facebook-messenger:before { content: @fa-var-facebook-messenger; } +.@{fa-css-prefix}-facebook-square:before { content: @fa-var-facebook-square; } +.@{fa-css-prefix}-fast-backward:before { content: @fa-var-fast-backward; } +.@{fa-css-prefix}-fast-forward:before { content: @fa-var-fast-forward; } +.@{fa-css-prefix}-fax:before { content: @fa-var-fax; } +.@{fa-css-prefix}-female:before { content: @fa-var-female; } +.@{fa-css-prefix}-fighter-jet:before { content: @fa-var-fighter-jet; } +.@{fa-css-prefix}-file:before { content: @fa-var-file; } +.@{fa-css-prefix}-file-alt:before { content: @fa-var-file-alt; } +.@{fa-css-prefix}-file-archive:before { content: @fa-var-file-archive; } +.@{fa-css-prefix}-file-audio:before { content: @fa-var-file-audio; } +.@{fa-css-prefix}-file-code:before { content: @fa-var-file-code; } +.@{fa-css-prefix}-file-excel:before { content: @fa-var-file-excel; } +.@{fa-css-prefix}-file-image:before { content: @fa-var-file-image; } +.@{fa-css-prefix}-file-medical:before { content: @fa-var-file-medical; } +.@{fa-css-prefix}-file-medical-alt:before { content: @fa-var-file-medical-alt; } +.@{fa-css-prefix}-file-pdf:before { content: @fa-var-file-pdf; } +.@{fa-css-prefix}-file-powerpoint:before { content: @fa-var-file-powerpoint; } +.@{fa-css-prefix}-file-video:before { content: @fa-var-file-video; } +.@{fa-css-prefix}-file-word:before { content: @fa-var-file-word; } +.@{fa-css-prefix}-film:before { content: @fa-var-film; } +.@{fa-css-prefix}-filter:before { content: @fa-var-filter; } +.@{fa-css-prefix}-fire:before { content: @fa-var-fire; } +.@{fa-css-prefix}-fire-extinguisher:before { content: @fa-var-fire-extinguisher; } +.@{fa-css-prefix}-firefox:before { content: @fa-var-firefox; } +.@{fa-css-prefix}-first-aid:before { content: @fa-var-first-aid; } +.@{fa-css-prefix}-first-order:before { content: @fa-var-first-order; } +.@{fa-css-prefix}-firstdraft:before { content: @fa-var-firstdraft; } +.@{fa-css-prefix}-flag:before { content: @fa-var-flag; } +.@{fa-css-prefix}-flag-checkered:before { content: @fa-var-flag-checkered; } +.@{fa-css-prefix}-flask:before { content: @fa-var-flask; } +.@{fa-css-prefix}-flickr:before { content: @fa-var-flickr; } +.@{fa-css-prefix}-flipboard:before { content: @fa-var-flipboard; } +.@{fa-css-prefix}-fly:before { content: @fa-var-fly; } +.@{fa-css-prefix}-folder:before { content: @fa-var-folder; } +.@{fa-css-prefix}-folder-open:before { content: @fa-var-folder-open; } +.@{fa-css-prefix}-font:before { content: @fa-var-font; } +.@{fa-css-prefix}-font-awesome:before { content: @fa-var-font-awesome; } +.@{fa-css-prefix}-font-awesome-alt:before { content: @fa-var-font-awesome-alt; } +.@{fa-css-prefix}-font-awesome-flag:before { content: @fa-var-font-awesome-flag; } +.@{fa-css-prefix}-fonticons:before { content: @fa-var-fonticons; } +.@{fa-css-prefix}-fonticons-fi:before { content: @fa-var-fonticons-fi; } +.@{fa-css-prefix}-football-ball:before { content: @fa-var-football-ball; } +.@{fa-css-prefix}-fort-awesome:before { content: @fa-var-fort-awesome; } +.@{fa-css-prefix}-fort-awesome-alt:before { content: @fa-var-fort-awesome-alt; } +.@{fa-css-prefix}-forumbee:before { content: @fa-var-forumbee; } +.@{fa-css-prefix}-forward:before { content: @fa-var-forward; } +.@{fa-css-prefix}-foursquare:before { content: @fa-var-foursquare; } +.@{fa-css-prefix}-free-code-camp:before { content: @fa-var-free-code-camp; } +.@{fa-css-prefix}-freebsd:before { content: @fa-var-freebsd; } +.@{fa-css-prefix}-frown:before { content: @fa-var-frown; } +.@{fa-css-prefix}-futbol:before { content: @fa-var-futbol; } +.@{fa-css-prefix}-gamepad:before { content: @fa-var-gamepad; } +.@{fa-css-prefix}-gavel:before { content: @fa-var-gavel; } +.@{fa-css-prefix}-gem:before { content: @fa-var-gem; } +.@{fa-css-prefix}-genderless:before { content: @fa-var-genderless; } +.@{fa-css-prefix}-get-pocket:before { content: @fa-var-get-pocket; } +.@{fa-css-prefix}-gg:before { content: @fa-var-gg; } +.@{fa-css-prefix}-gg-circle:before { content: @fa-var-gg-circle; } +.@{fa-css-prefix}-gift:before { content: @fa-var-gift; } +.@{fa-css-prefix}-git:before { content: @fa-var-git; } +.@{fa-css-prefix}-git-square:before { content: @fa-var-git-square; } +.@{fa-css-prefix}-github:before { content: @fa-var-github; } +.@{fa-css-prefix}-github-alt:before { content: @fa-var-github-alt; } +.@{fa-css-prefix}-github-square:before { content: @fa-var-github-square; } +.@{fa-css-prefix}-gitkraken:before { content: @fa-var-gitkraken; } +.@{fa-css-prefix}-gitlab:before { content: @fa-var-gitlab; } +.@{fa-css-prefix}-gitter:before { content: @fa-var-gitter; } +.@{fa-css-prefix}-glass-martini:before { content: @fa-var-glass-martini; } +.@{fa-css-prefix}-glide:before { content: @fa-var-glide; } +.@{fa-css-prefix}-glide-g:before { content: @fa-var-glide-g; } +.@{fa-css-prefix}-globe:before { content: @fa-var-globe; } +.@{fa-css-prefix}-gofore:before { content: @fa-var-gofore; } +.@{fa-css-prefix}-golf-ball:before { content: @fa-var-golf-ball; } +.@{fa-css-prefix}-goodreads:before { content: @fa-var-goodreads; } +.@{fa-css-prefix}-goodreads-g:before { content: @fa-var-goodreads-g; } +.@{fa-css-prefix}-google:before { content: @fa-var-google; } +.@{fa-css-prefix}-google-drive:before { content: @fa-var-google-drive; } +.@{fa-css-prefix}-google-play:before { content: @fa-var-google-play; } +.@{fa-css-prefix}-google-plus:before { content: @fa-var-google-plus; } +.@{fa-css-prefix}-google-plus-g:before { content: @fa-var-google-plus-g; } +.@{fa-css-prefix}-google-plus-square:before { content: @fa-var-google-plus-square; } +.@{fa-css-prefix}-google-wallet:before { content: @fa-var-google-wallet; } +.@{fa-css-prefix}-graduation-cap:before { content: @fa-var-graduation-cap; } +.@{fa-css-prefix}-gratipay:before { content: @fa-var-gratipay; } +.@{fa-css-prefix}-grav:before { content: @fa-var-grav; } +.@{fa-css-prefix}-gripfire:before { content: @fa-var-gripfire; } +.@{fa-css-prefix}-grunt:before { content: @fa-var-grunt; } +.@{fa-css-prefix}-gulp:before { content: @fa-var-gulp; } +.@{fa-css-prefix}-h-square:before { content: @fa-var-h-square; } +.@{fa-css-prefix}-hacker-news:before { content: @fa-var-hacker-news; } +.@{fa-css-prefix}-hacker-news-square:before { content: @fa-var-hacker-news-square; } +.@{fa-css-prefix}-hand-holding:before { content: @fa-var-hand-holding; } +.@{fa-css-prefix}-hand-holding-heart:before { content: @fa-var-hand-holding-heart; } +.@{fa-css-prefix}-hand-holding-usd:before { content: @fa-var-hand-holding-usd; } +.@{fa-css-prefix}-hand-lizard:before { content: @fa-var-hand-lizard; } +.@{fa-css-prefix}-hand-paper:before { content: @fa-var-hand-paper; } +.@{fa-css-prefix}-hand-peace:before { content: @fa-var-hand-peace; } +.@{fa-css-prefix}-hand-point-down:before { content: @fa-var-hand-point-down; } +.@{fa-css-prefix}-hand-point-left:before { content: @fa-var-hand-point-left; } +.@{fa-css-prefix}-hand-point-right:before { content: @fa-var-hand-point-right; } +.@{fa-css-prefix}-hand-point-up:before { content: @fa-var-hand-point-up; } +.@{fa-css-prefix}-hand-pointer:before { content: @fa-var-hand-pointer; } +.@{fa-css-prefix}-hand-rock:before { content: @fa-var-hand-rock; } +.@{fa-css-prefix}-hand-scissors:before { content: @fa-var-hand-scissors; } +.@{fa-css-prefix}-hand-spock:before { content: @fa-var-hand-spock; } +.@{fa-css-prefix}-hands:before { content: @fa-var-hands; } +.@{fa-css-prefix}-hands-helping:before { content: @fa-var-hands-helping; } +.@{fa-css-prefix}-handshake:before { content: @fa-var-handshake; } +.@{fa-css-prefix}-hashtag:before { content: @fa-var-hashtag; } +.@{fa-css-prefix}-hdd:before { content: @fa-var-hdd; } +.@{fa-css-prefix}-heading:before { content: @fa-var-heading; } +.@{fa-css-prefix}-headphones:before { content: @fa-var-headphones; } +.@{fa-css-prefix}-heart:before { content: @fa-var-heart; } +.@{fa-css-prefix}-heartbeat:before { content: @fa-var-heartbeat; } +.@{fa-css-prefix}-hips:before { content: @fa-var-hips; } +.@{fa-css-prefix}-hire-a-helper:before { content: @fa-var-hire-a-helper; } +.@{fa-css-prefix}-history:before { content: @fa-var-history; } +.@{fa-css-prefix}-hockey-puck:before { content: @fa-var-hockey-puck; } +.@{fa-css-prefix}-home:before { content: @fa-var-home; } +.@{fa-css-prefix}-hooli:before { content: @fa-var-hooli; } +.@{fa-css-prefix}-hospital:before { content: @fa-var-hospital; } +.@{fa-css-prefix}-hospital-alt:before { content: @fa-var-hospital-alt; } +.@{fa-css-prefix}-hospital-symbol:before { content: @fa-var-hospital-symbol; } +.@{fa-css-prefix}-hotjar:before { content: @fa-var-hotjar; } +.@{fa-css-prefix}-hourglass:before { content: @fa-var-hourglass; } +.@{fa-css-prefix}-hourglass-end:before { content: @fa-var-hourglass-end; } +.@{fa-css-prefix}-hourglass-half:before { content: @fa-var-hourglass-half; } +.@{fa-css-prefix}-hourglass-start:before { content: @fa-var-hourglass-start; } +.@{fa-css-prefix}-houzz:before { content: @fa-var-houzz; } +.@{fa-css-prefix}-html5:before { content: @fa-var-html5; } +.@{fa-css-prefix}-hubspot:before { content: @fa-var-hubspot; } +.@{fa-css-prefix}-i-cursor:before { content: @fa-var-i-cursor; } +.@{fa-css-prefix}-id-badge:before { content: @fa-var-id-badge; } +.@{fa-css-prefix}-id-card:before { content: @fa-var-id-card; } +.@{fa-css-prefix}-id-card-alt:before { content: @fa-var-id-card-alt; } +.@{fa-css-prefix}-image:before { content: @fa-var-image; } +.@{fa-css-prefix}-images:before { content: @fa-var-images; } +.@{fa-css-prefix}-imdb:before { content: @fa-var-imdb; } +.@{fa-css-prefix}-inbox:before { content: @fa-var-inbox; } +.@{fa-css-prefix}-indent:before { content: @fa-var-indent; } +.@{fa-css-prefix}-industry:before { content: @fa-var-industry; } +.@{fa-css-prefix}-info:before { content: @fa-var-info; } +.@{fa-css-prefix}-info-circle:before { content: @fa-var-info-circle; } +.@{fa-css-prefix}-instagram:before { content: @fa-var-instagram; } +.@{fa-css-prefix}-internet-explorer:before { content: @fa-var-internet-explorer; } +.@{fa-css-prefix}-ioxhost:before { content: @fa-var-ioxhost; } +.@{fa-css-prefix}-italic:before { content: @fa-var-italic; } +.@{fa-css-prefix}-itunes:before { content: @fa-var-itunes; } +.@{fa-css-prefix}-itunes-note:before { content: @fa-var-itunes-note; } +.@{fa-css-prefix}-java:before { content: @fa-var-java; } +.@{fa-css-prefix}-jenkins:before { content: @fa-var-jenkins; } +.@{fa-css-prefix}-joget:before { content: @fa-var-joget; } +.@{fa-css-prefix}-joomla:before { content: @fa-var-joomla; } +.@{fa-css-prefix}-js:before { content: @fa-var-js; } +.@{fa-css-prefix}-js-square:before { content: @fa-var-js-square; } +.@{fa-css-prefix}-jsfiddle:before { content: @fa-var-jsfiddle; } +.@{fa-css-prefix}-key:before { content: @fa-var-key; } +.@{fa-css-prefix}-keyboard:before { content: @fa-var-keyboard; } +.@{fa-css-prefix}-keycdn:before { content: @fa-var-keycdn; } +.@{fa-css-prefix}-kickstarter:before { content: @fa-var-kickstarter; } +.@{fa-css-prefix}-kickstarter-k:before { content: @fa-var-kickstarter-k; } +.@{fa-css-prefix}-korvue:before { content: @fa-var-korvue; } +.@{fa-css-prefix}-language:before { content: @fa-var-language; } +.@{fa-css-prefix}-laptop:before { content: @fa-var-laptop; } +.@{fa-css-prefix}-laravel:before { content: @fa-var-laravel; } +.@{fa-css-prefix}-lastfm:before { content: @fa-var-lastfm; } +.@{fa-css-prefix}-lastfm-square:before { content: @fa-var-lastfm-square; } +.@{fa-css-prefix}-leaf:before { content: @fa-var-leaf; } +.@{fa-css-prefix}-leanpub:before { content: @fa-var-leanpub; } +.@{fa-css-prefix}-lemon:before { content: @fa-var-lemon; } +.@{fa-css-prefix}-less:before { content: @fa-var-less; } +.@{fa-css-prefix}-level-down-alt:before { content: @fa-var-level-down-alt; } +.@{fa-css-prefix}-level-up-alt:before { content: @fa-var-level-up-alt; } +.@{fa-css-prefix}-life-ring:before { content: @fa-var-life-ring; } +.@{fa-css-prefix}-lightbulb:before { content: @fa-var-lightbulb; } +.@{fa-css-prefix}-line:before { content: @fa-var-line; } +.@{fa-css-prefix}-link:before { content: @fa-var-link; } +.@{fa-css-prefix}-linkedin:before { content: @fa-var-linkedin; } +.@{fa-css-prefix}-linkedin-in:before { content: @fa-var-linkedin-in; } +.@{fa-css-prefix}-linode:before { content: @fa-var-linode; } +.@{fa-css-prefix}-linux:before { content: @fa-var-linux; } +.@{fa-css-prefix}-lira-sign:before { content: @fa-var-lira-sign; } +.@{fa-css-prefix}-list:before { content: @fa-var-list; } +.@{fa-css-prefix}-list-alt:before { content: @fa-var-list-alt; } +.@{fa-css-prefix}-list-ol:before { content: @fa-var-list-ol; } +.@{fa-css-prefix}-list-ul:before { content: @fa-var-list-ul; } +.@{fa-css-prefix}-location-arrow:before { content: @fa-var-location-arrow; } +.@{fa-css-prefix}-lock:before { content: @fa-var-lock; } +.@{fa-css-prefix}-lock-open:before { content: @fa-var-lock-open; } +.@{fa-css-prefix}-long-arrow-alt-down:before { content: @fa-var-long-arrow-alt-down; } +.@{fa-css-prefix}-long-arrow-alt-left:before { content: @fa-var-long-arrow-alt-left; } +.@{fa-css-prefix}-long-arrow-alt-right:before { content: @fa-var-long-arrow-alt-right; } +.@{fa-css-prefix}-long-arrow-alt-up:before { content: @fa-var-long-arrow-alt-up; } +.@{fa-css-prefix}-low-vision:before { content: @fa-var-low-vision; } +.@{fa-css-prefix}-lyft:before { content: @fa-var-lyft; } +.@{fa-css-prefix}-magento:before { content: @fa-var-magento; } +.@{fa-css-prefix}-magic:before { content: @fa-var-magic; } +.@{fa-css-prefix}-magnet:before { content: @fa-var-magnet; } +.@{fa-css-prefix}-male:before { content: @fa-var-male; } +.@{fa-css-prefix}-map:before { content: @fa-var-map; } +.@{fa-css-prefix}-map-marker:before { content: @fa-var-map-marker; } +.@{fa-css-prefix}-map-marker-alt:before { content: @fa-var-map-marker-alt; } +.@{fa-css-prefix}-map-pin:before { content: @fa-var-map-pin; } +.@{fa-css-prefix}-map-signs:before { content: @fa-var-map-signs; } +.@{fa-css-prefix}-mars:before { content: @fa-var-mars; } +.@{fa-css-prefix}-mars-double:before { content: @fa-var-mars-double; } +.@{fa-css-prefix}-mars-stroke:before { content: @fa-var-mars-stroke; } +.@{fa-css-prefix}-mars-stroke-h:before { content: @fa-var-mars-stroke-h; } +.@{fa-css-prefix}-mars-stroke-v:before { content: @fa-var-mars-stroke-v; } +.@{fa-css-prefix}-maxcdn:before { content: @fa-var-maxcdn; } +.@{fa-css-prefix}-medapps:before { content: @fa-var-medapps; } +.@{fa-css-prefix}-medium:before { content: @fa-var-medium; } +.@{fa-css-prefix}-medium-m:before { content: @fa-var-medium-m; } +.@{fa-css-prefix}-medkit:before { content: @fa-var-medkit; } +.@{fa-css-prefix}-medrt:before { content: @fa-var-medrt; } +.@{fa-css-prefix}-meetup:before { content: @fa-var-meetup; } +.@{fa-css-prefix}-meh:before { content: @fa-var-meh; } +.@{fa-css-prefix}-mercury:before { content: @fa-var-mercury; } +.@{fa-css-prefix}-microchip:before { content: @fa-var-microchip; } +.@{fa-css-prefix}-microphone:before { content: @fa-var-microphone; } +.@{fa-css-prefix}-microphone-slash:before { content: @fa-var-microphone-slash; } +.@{fa-css-prefix}-microsoft:before { content: @fa-var-microsoft; } +.@{fa-css-prefix}-minus:before { content: @fa-var-minus; } +.@{fa-css-prefix}-minus-circle:before { content: @fa-var-minus-circle; } +.@{fa-css-prefix}-minus-square:before { content: @fa-var-minus-square; } +.@{fa-css-prefix}-mix:before { content: @fa-var-mix; } +.@{fa-css-prefix}-mixcloud:before { content: @fa-var-mixcloud; } +.@{fa-css-prefix}-mizuni:before { content: @fa-var-mizuni; } +.@{fa-css-prefix}-mobile:before { content: @fa-var-mobile; } +.@{fa-css-prefix}-mobile-alt:before { content: @fa-var-mobile-alt; } +.@{fa-css-prefix}-modx:before { content: @fa-var-modx; } +.@{fa-css-prefix}-monero:before { content: @fa-var-monero; } +.@{fa-css-prefix}-money-bill-alt:before { content: @fa-var-money-bill-alt; } +.@{fa-css-prefix}-moon:before { content: @fa-var-moon; } +.@{fa-css-prefix}-motorcycle:before { content: @fa-var-motorcycle; } +.@{fa-css-prefix}-mouse-pointer:before { content: @fa-var-mouse-pointer; } +.@{fa-css-prefix}-music:before { content: @fa-var-music; } +.@{fa-css-prefix}-napster:before { content: @fa-var-napster; } +.@{fa-css-prefix}-neuter:before { content: @fa-var-neuter; } +.@{fa-css-prefix}-newspaper:before { content: @fa-var-newspaper; } +.@{fa-css-prefix}-nintendo-switch:before { content: @fa-var-nintendo-switch; } +.@{fa-css-prefix}-node:before { content: @fa-var-node; } +.@{fa-css-prefix}-node-js:before { content: @fa-var-node-js; } +.@{fa-css-prefix}-notes-medical:before { content: @fa-var-notes-medical; } +.@{fa-css-prefix}-npm:before { content: @fa-var-npm; } +.@{fa-css-prefix}-ns8:before { content: @fa-var-ns8; } +.@{fa-css-prefix}-nutritionix:before { content: @fa-var-nutritionix; } +.@{fa-css-prefix}-object-group:before { content: @fa-var-object-group; } +.@{fa-css-prefix}-object-ungroup:before { content: @fa-var-object-ungroup; } +.@{fa-css-prefix}-odnoklassniki:before { content: @fa-var-odnoklassniki; } +.@{fa-css-prefix}-odnoklassniki-square:before { content: @fa-var-odnoklassniki-square; } +.@{fa-css-prefix}-opencart:before { content: @fa-var-opencart; } +.@{fa-css-prefix}-openid:before { content: @fa-var-openid; } +.@{fa-css-prefix}-opera:before { content: @fa-var-opera; } +.@{fa-css-prefix}-optin-monster:before { content: @fa-var-optin-monster; } +.@{fa-css-prefix}-osi:before { content: @fa-var-osi; } +.@{fa-css-prefix}-outdent:before { content: @fa-var-outdent; } +.@{fa-css-prefix}-page4:before { content: @fa-var-page4; } +.@{fa-css-prefix}-pagelines:before { content: @fa-var-pagelines; } +.@{fa-css-prefix}-paint-brush:before { content: @fa-var-paint-brush; } +.@{fa-css-prefix}-palfed:before { content: @fa-var-palfed; } +.@{fa-css-prefix}-pallet:before { content: @fa-var-pallet; } +.@{fa-css-prefix}-paper-plane:before { content: @fa-var-paper-plane; } +.@{fa-css-prefix}-paperclip:before { content: @fa-var-paperclip; } +.@{fa-css-prefix}-parachute-box:before { content: @fa-var-parachute-box; } +.@{fa-css-prefix}-paragraph:before { content: @fa-var-paragraph; } +.@{fa-css-prefix}-paste:before { content: @fa-var-paste; } +.@{fa-css-prefix}-patreon:before { content: @fa-var-patreon; } +.@{fa-css-prefix}-pause:before { content: @fa-var-pause; } +.@{fa-css-prefix}-pause-circle:before { content: @fa-var-pause-circle; } +.@{fa-css-prefix}-paw:before { content: @fa-var-paw; } +.@{fa-css-prefix}-paypal:before { content: @fa-var-paypal; } +.@{fa-css-prefix}-pen-square:before { content: @fa-var-pen-square; } +.@{fa-css-prefix}-pencil-alt:before { content: @fa-var-pencil-alt; } +.@{fa-css-prefix}-people-carry:before { content: @fa-var-people-carry; } +.@{fa-css-prefix}-percent:before { content: @fa-var-percent; } +.@{fa-css-prefix}-periscope:before { content: @fa-var-periscope; } +.@{fa-css-prefix}-phabricator:before { content: @fa-var-phabricator; } +.@{fa-css-prefix}-phoenix-framework:before { content: @fa-var-phoenix-framework; } +.@{fa-css-prefix}-phone:before { content: @fa-var-phone; } +.@{fa-css-prefix}-phone-slash:before { content: @fa-var-phone-slash; } +.@{fa-css-prefix}-phone-square:before { content: @fa-var-phone-square; } +.@{fa-css-prefix}-phone-volume:before { content: @fa-var-phone-volume; } +.@{fa-css-prefix}-php:before { content: @fa-var-php; } +.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; } +.@{fa-css-prefix}-pied-piper-alt:before { content: @fa-var-pied-piper-alt; } +.@{fa-css-prefix}-pied-piper-hat:before { content: @fa-var-pied-piper-hat; } +.@{fa-css-prefix}-pied-piper-pp:before { content: @fa-var-pied-piper-pp; } +.@{fa-css-prefix}-piggy-bank:before { content: @fa-var-piggy-bank; } +.@{fa-css-prefix}-pills:before { content: @fa-var-pills; } +.@{fa-css-prefix}-pinterest:before { content: @fa-var-pinterest; } +.@{fa-css-prefix}-pinterest-p:before { content: @fa-var-pinterest-p; } +.@{fa-css-prefix}-pinterest-square:before { content: @fa-var-pinterest-square; } +.@{fa-css-prefix}-plane:before { content: @fa-var-plane; } +.@{fa-css-prefix}-play:before { content: @fa-var-play; } +.@{fa-css-prefix}-play-circle:before { content: @fa-var-play-circle; } +.@{fa-css-prefix}-playstation:before { content: @fa-var-playstation; } +.@{fa-css-prefix}-plug:before { content: @fa-var-plug; } +.@{fa-css-prefix}-plus:before { content: @fa-var-plus; } +.@{fa-css-prefix}-plus-circle:before { content: @fa-var-plus-circle; } +.@{fa-css-prefix}-plus-square:before { content: @fa-var-plus-square; } +.@{fa-css-prefix}-podcast:before { content: @fa-var-podcast; } +.@{fa-css-prefix}-poo:before { content: @fa-var-poo; } +.@{fa-css-prefix}-pound-sign:before { content: @fa-var-pound-sign; } +.@{fa-css-prefix}-power-off:before { content: @fa-var-power-off; } +.@{fa-css-prefix}-prescription-bottle:before { content: @fa-var-prescription-bottle; } +.@{fa-css-prefix}-prescription-bottle-alt:before { content: @fa-var-prescription-bottle-alt; } +.@{fa-css-prefix}-print:before { content: @fa-var-print; } +.@{fa-css-prefix}-procedures:before { content: @fa-var-procedures; } +.@{fa-css-prefix}-product-hunt:before { content: @fa-var-product-hunt; } +.@{fa-css-prefix}-pushed:before { content: @fa-var-pushed; } +.@{fa-css-prefix}-puzzle-piece:before { content: @fa-var-puzzle-piece; } +.@{fa-css-prefix}-python:before { content: @fa-var-python; } +.@{fa-css-prefix}-qq:before { content: @fa-var-qq; } +.@{fa-css-prefix}-qrcode:before { content: @fa-var-qrcode; } +.@{fa-css-prefix}-question:before { content: @fa-var-question; } +.@{fa-css-prefix}-question-circle:before { content: @fa-var-question-circle; } +.@{fa-css-prefix}-quidditch:before { content: @fa-var-quidditch; } +.@{fa-css-prefix}-quinscape:before { content: @fa-var-quinscape; } +.@{fa-css-prefix}-quora:before { content: @fa-var-quora; } +.@{fa-css-prefix}-quote-left:before { content: @fa-var-quote-left; } +.@{fa-css-prefix}-quote-right:before { content: @fa-var-quote-right; } +.@{fa-css-prefix}-random:before { content: @fa-var-random; } +.@{fa-css-prefix}-ravelry:before { content: @fa-var-ravelry; } +.@{fa-css-prefix}-react:before { content: @fa-var-react; } +.@{fa-css-prefix}-readme:before { content: @fa-var-readme; } +.@{fa-css-prefix}-rebel:before { content: @fa-var-rebel; } +.@{fa-css-prefix}-recycle:before { content: @fa-var-recycle; } +.@{fa-css-prefix}-red-river:before { content: @fa-var-red-river; } +.@{fa-css-prefix}-reddit:before { content: @fa-var-reddit; } +.@{fa-css-prefix}-reddit-alien:before { content: @fa-var-reddit-alien; } +.@{fa-css-prefix}-reddit-square:before { content: @fa-var-reddit-square; } +.@{fa-css-prefix}-redo:before { content: @fa-var-redo; } +.@{fa-css-prefix}-redo-alt:before { content: @fa-var-redo-alt; } +.@{fa-css-prefix}-registered:before { content: @fa-var-registered; } +.@{fa-css-prefix}-rendact:before { content: @fa-var-rendact; } +.@{fa-css-prefix}-renren:before { content: @fa-var-renren; } +.@{fa-css-prefix}-reply:before { content: @fa-var-reply; } +.@{fa-css-prefix}-reply-all:before { content: @fa-var-reply-all; } +.@{fa-css-prefix}-replyd:before { content: @fa-var-replyd; } +.@{fa-css-prefix}-resolving:before { content: @fa-var-resolving; } +.@{fa-css-prefix}-retweet:before { content: @fa-var-retweet; } +.@{fa-css-prefix}-ribbon:before { content: @fa-var-ribbon; } +.@{fa-css-prefix}-road:before { content: @fa-var-road; } +.@{fa-css-prefix}-rocket:before { content: @fa-var-rocket; } +.@{fa-css-prefix}-rocketchat:before { content: @fa-var-rocketchat; } +.@{fa-css-prefix}-rockrms:before { content: @fa-var-rockrms; } +.@{fa-css-prefix}-rss:before { content: @fa-var-rss; } +.@{fa-css-prefix}-rss-square:before { content: @fa-var-rss-square; } +.@{fa-css-prefix}-ruble-sign:before { content: @fa-var-ruble-sign; } +.@{fa-css-prefix}-rupee-sign:before { content: @fa-var-rupee-sign; } +.@{fa-css-prefix}-safari:before { content: @fa-var-safari; } +.@{fa-css-prefix}-sass:before { content: @fa-var-sass; } +.@{fa-css-prefix}-save:before { content: @fa-var-save; } +.@{fa-css-prefix}-schlix:before { content: @fa-var-schlix; } +.@{fa-css-prefix}-scribd:before { content: @fa-var-scribd; } +.@{fa-css-prefix}-search:before { content: @fa-var-search; } +.@{fa-css-prefix}-search-minus:before { content: @fa-var-search-minus; } +.@{fa-css-prefix}-search-plus:before { content: @fa-var-search-plus; } +.@{fa-css-prefix}-searchengin:before { content: @fa-var-searchengin; } +.@{fa-css-prefix}-seedling:before { content: @fa-var-seedling; } +.@{fa-css-prefix}-sellcast:before { content: @fa-var-sellcast; } +.@{fa-css-prefix}-sellsy:before { content: @fa-var-sellsy; } +.@{fa-css-prefix}-server:before { content: @fa-var-server; } +.@{fa-css-prefix}-servicestack:before { content: @fa-var-servicestack; } +.@{fa-css-prefix}-share:before { content: @fa-var-share; } +.@{fa-css-prefix}-share-alt:before { content: @fa-var-share-alt; } +.@{fa-css-prefix}-share-alt-square:before { content: @fa-var-share-alt-square; } +.@{fa-css-prefix}-share-square:before { content: @fa-var-share-square; } +.@{fa-css-prefix}-shekel-sign:before { content: @fa-var-shekel-sign; } +.@{fa-css-prefix}-shield-alt:before { content: @fa-var-shield-alt; } +.@{fa-css-prefix}-ship:before { content: @fa-var-ship; } +.@{fa-css-prefix}-shipping-fast:before { content: @fa-var-shipping-fast; } +.@{fa-css-prefix}-shirtsinbulk:before { content: @fa-var-shirtsinbulk; } +.@{fa-css-prefix}-shopping-bag:before { content: @fa-var-shopping-bag; } +.@{fa-css-prefix}-shopping-basket:before { content: @fa-var-shopping-basket; } +.@{fa-css-prefix}-shopping-cart:before { content: @fa-var-shopping-cart; } +.@{fa-css-prefix}-shower:before { content: @fa-var-shower; } +.@{fa-css-prefix}-sign:before { content: @fa-var-sign; } +.@{fa-css-prefix}-sign-in-alt:before { content: @fa-var-sign-in-alt; } +.@{fa-css-prefix}-sign-language:before { content: @fa-var-sign-language; } +.@{fa-css-prefix}-sign-out-alt:before { content: @fa-var-sign-out-alt; } +.@{fa-css-prefix}-signal:before { content: @fa-var-signal; } +.@{fa-css-prefix}-simplybuilt:before { content: @fa-var-simplybuilt; } +.@{fa-css-prefix}-sistrix:before { content: @fa-var-sistrix; } +.@{fa-css-prefix}-sitemap:before { content: @fa-var-sitemap; } +.@{fa-css-prefix}-skyatlas:before { content: @fa-var-skyatlas; } +.@{fa-css-prefix}-skype:before { content: @fa-var-skype; } +.@{fa-css-prefix}-slack:before { content: @fa-var-slack; } +.@{fa-css-prefix}-slack-hash:before { content: @fa-var-slack-hash; } +.@{fa-css-prefix}-sliders-h:before { content: @fa-var-sliders-h; } +.@{fa-css-prefix}-slideshare:before { content: @fa-var-slideshare; } +.@{fa-css-prefix}-smile:before { content: @fa-var-smile; } +.@{fa-css-prefix}-smoking:before { content: @fa-var-smoking; } +.@{fa-css-prefix}-snapchat:before { content: @fa-var-snapchat; } +.@{fa-css-prefix}-snapchat-ghost:before { content: @fa-var-snapchat-ghost; } +.@{fa-css-prefix}-snapchat-square:before { content: @fa-var-snapchat-square; } +.@{fa-css-prefix}-snowflake:before { content: @fa-var-snowflake; } +.@{fa-css-prefix}-sort:before { content: @fa-var-sort; } +.@{fa-css-prefix}-sort-alpha-down:before { content: @fa-var-sort-alpha-down; } +.@{fa-css-prefix}-sort-alpha-up:before { content: @fa-var-sort-alpha-up; } +.@{fa-css-prefix}-sort-amount-down:before { content: @fa-var-sort-amount-down; } +.@{fa-css-prefix}-sort-amount-up:before { content: @fa-var-sort-amount-up; } +.@{fa-css-prefix}-sort-down:before { content: @fa-var-sort-down; } +.@{fa-css-prefix}-sort-numeric-down:before { content: @fa-var-sort-numeric-down; } +.@{fa-css-prefix}-sort-numeric-up:before { content: @fa-var-sort-numeric-up; } +.@{fa-css-prefix}-sort-up:before { content: @fa-var-sort-up; } +.@{fa-css-prefix}-soundcloud:before { content: @fa-var-soundcloud; } +.@{fa-css-prefix}-space-shuttle:before { content: @fa-var-space-shuttle; } +.@{fa-css-prefix}-speakap:before { content: @fa-var-speakap; } +.@{fa-css-prefix}-spinner:before { content: @fa-var-spinner; } +.@{fa-css-prefix}-spotify:before { content: @fa-var-spotify; } +.@{fa-css-prefix}-square:before { content: @fa-var-square; } +.@{fa-css-prefix}-square-full:before { content: @fa-var-square-full; } +.@{fa-css-prefix}-stack-exchange:before { content: @fa-var-stack-exchange; } +.@{fa-css-prefix}-stack-overflow:before { content: @fa-var-stack-overflow; } +.@{fa-css-prefix}-star:before { content: @fa-var-star; } +.@{fa-css-prefix}-star-half:before { content: @fa-var-star-half; } +.@{fa-css-prefix}-staylinked:before { content: @fa-var-staylinked; } +.@{fa-css-prefix}-steam:before { content: @fa-var-steam; } +.@{fa-css-prefix}-steam-square:before { content: @fa-var-steam-square; } +.@{fa-css-prefix}-steam-symbol:before { content: @fa-var-steam-symbol; } +.@{fa-css-prefix}-step-backward:before { content: @fa-var-step-backward; } +.@{fa-css-prefix}-step-forward:before { content: @fa-var-step-forward; } +.@{fa-css-prefix}-stethoscope:before { content: @fa-var-stethoscope; } +.@{fa-css-prefix}-sticker-mule:before { content: @fa-var-sticker-mule; } +.@{fa-css-prefix}-sticky-note:before { content: @fa-var-sticky-note; } +.@{fa-css-prefix}-stop:before { content: @fa-var-stop; } +.@{fa-css-prefix}-stop-circle:before { content: @fa-var-stop-circle; } +.@{fa-css-prefix}-stopwatch:before { content: @fa-var-stopwatch; } +.@{fa-css-prefix}-strava:before { content: @fa-var-strava; } +.@{fa-css-prefix}-street-view:before { content: @fa-var-street-view; } +.@{fa-css-prefix}-strikethrough:before { content: @fa-var-strikethrough; } +.@{fa-css-prefix}-stripe:before { content: @fa-var-stripe; } +.@{fa-css-prefix}-stripe-s:before { content: @fa-var-stripe-s; } +.@{fa-css-prefix}-studiovinari:before { content: @fa-var-studiovinari; } +.@{fa-css-prefix}-stumbleupon:before { content: @fa-var-stumbleupon; } +.@{fa-css-prefix}-stumbleupon-circle:before { content: @fa-var-stumbleupon-circle; } +.@{fa-css-prefix}-subscript:before { content: @fa-var-subscript; } +.@{fa-css-prefix}-subway:before { content: @fa-var-subway; } +.@{fa-css-prefix}-suitcase:before { content: @fa-var-suitcase; } +.@{fa-css-prefix}-sun:before { content: @fa-var-sun; } +.@{fa-css-prefix}-superpowers:before { content: @fa-var-superpowers; } +.@{fa-css-prefix}-superscript:before { content: @fa-var-superscript; } +.@{fa-css-prefix}-supple:before { content: @fa-var-supple; } +.@{fa-css-prefix}-sync:before { content: @fa-var-sync; } +.@{fa-css-prefix}-sync-alt:before { content: @fa-var-sync-alt; } +.@{fa-css-prefix}-syringe:before { content: @fa-var-syringe; } +.@{fa-css-prefix}-table:before { content: @fa-var-table; } +.@{fa-css-prefix}-table-tennis:before { content: @fa-var-table-tennis; } +.@{fa-css-prefix}-tablet:before { content: @fa-var-tablet; } +.@{fa-css-prefix}-tablet-alt:before { content: @fa-var-tablet-alt; } +.@{fa-css-prefix}-tablets:before { content: @fa-var-tablets; } +.@{fa-css-prefix}-tachometer-alt:before { content: @fa-var-tachometer-alt; } +.@{fa-css-prefix}-tag:before { content: @fa-var-tag; } +.@{fa-css-prefix}-tags:before { content: @fa-var-tags; } +.@{fa-css-prefix}-tape:before { content: @fa-var-tape; } +.@{fa-css-prefix}-tasks:before { content: @fa-var-tasks; } +.@{fa-css-prefix}-taxi:before { content: @fa-var-taxi; } +.@{fa-css-prefix}-telegram:before { content: @fa-var-telegram; } +.@{fa-css-prefix}-telegram-plane:before { content: @fa-var-telegram-plane; } +.@{fa-css-prefix}-tencent-weibo:before { content: @fa-var-tencent-weibo; } +.@{fa-css-prefix}-terminal:before { content: @fa-var-terminal; } +.@{fa-css-prefix}-text-height:before { content: @fa-var-text-height; } +.@{fa-css-prefix}-text-width:before { content: @fa-var-text-width; } +.@{fa-css-prefix}-th:before { content: @fa-var-th; } +.@{fa-css-prefix}-th-large:before { content: @fa-var-th-large; } +.@{fa-css-prefix}-th-list:before { content: @fa-var-th-list; } +.@{fa-css-prefix}-themeisle:before { content: @fa-var-themeisle; } +.@{fa-css-prefix}-thermometer:before { content: @fa-var-thermometer; } +.@{fa-css-prefix}-thermometer-empty:before { content: @fa-var-thermometer-empty; } +.@{fa-css-prefix}-thermometer-full:before { content: @fa-var-thermometer-full; } +.@{fa-css-prefix}-thermometer-half:before { content: @fa-var-thermometer-half; } +.@{fa-css-prefix}-thermometer-quarter:before { content: @fa-var-thermometer-quarter; } +.@{fa-css-prefix}-thermometer-three-quarters:before { content: @fa-var-thermometer-three-quarters; } +.@{fa-css-prefix}-thumbs-down:before { content: @fa-var-thumbs-down; } +.@{fa-css-prefix}-thumbs-up:before { content: @fa-var-thumbs-up; } +.@{fa-css-prefix}-thumbtack:before { content: @fa-var-thumbtack; } +.@{fa-css-prefix}-ticket-alt:before { content: @fa-var-ticket-alt; } +.@{fa-css-prefix}-times:before { content: @fa-var-times; } +.@{fa-css-prefix}-times-circle:before { content: @fa-var-times-circle; } +.@{fa-css-prefix}-tint:before { content: @fa-var-tint; } +.@{fa-css-prefix}-toggle-off:before { content: @fa-var-toggle-off; } +.@{fa-css-prefix}-toggle-on:before { content: @fa-var-toggle-on; } +.@{fa-css-prefix}-trademark:before { content: @fa-var-trademark; } +.@{fa-css-prefix}-train:before { content: @fa-var-train; } +.@{fa-css-prefix}-transgender:before { content: @fa-var-transgender; } +.@{fa-css-prefix}-transgender-alt:before { content: @fa-var-transgender-alt; } +.@{fa-css-prefix}-trash:before { content: @fa-var-trash; } +.@{fa-css-prefix}-trash-alt:before { content: @fa-var-trash-alt; } +.@{fa-css-prefix}-tree:before { content: @fa-var-tree; } +.@{fa-css-prefix}-trello:before { content: @fa-var-trello; } +.@{fa-css-prefix}-tripadvisor:before { content: @fa-var-tripadvisor; } +.@{fa-css-prefix}-trophy:before { content: @fa-var-trophy; } +.@{fa-css-prefix}-truck:before { content: @fa-var-truck; } +.@{fa-css-prefix}-truck-loading:before { content: @fa-var-truck-loading; } +.@{fa-css-prefix}-truck-moving:before { content: @fa-var-truck-moving; } +.@{fa-css-prefix}-tty:before { content: @fa-var-tty; } +.@{fa-css-prefix}-tumblr:before { content: @fa-var-tumblr; } +.@{fa-css-prefix}-tumblr-square:before { content: @fa-var-tumblr-square; } +.@{fa-css-prefix}-tv:before { content: @fa-var-tv; } +.@{fa-css-prefix}-twitch:before { content: @fa-var-twitch; } +.@{fa-css-prefix}-twitter:before { content: @fa-var-twitter; } +.@{fa-css-prefix}-twitter-square:before { content: @fa-var-twitter-square; } +.@{fa-css-prefix}-typo3:before { content: @fa-var-typo3; } +.@{fa-css-prefix}-uber:before { content: @fa-var-uber; } +.@{fa-css-prefix}-uikit:before { content: @fa-var-uikit; } +.@{fa-css-prefix}-umbrella:before { content: @fa-var-umbrella; } +.@{fa-css-prefix}-underline:before { content: @fa-var-underline; } +.@{fa-css-prefix}-undo:before { content: @fa-var-undo; } +.@{fa-css-prefix}-undo-alt:before { content: @fa-var-undo-alt; } +.@{fa-css-prefix}-uniregistry:before { content: @fa-var-uniregistry; } +.@{fa-css-prefix}-universal-access:before { content: @fa-var-universal-access; } +.@{fa-css-prefix}-university:before { content: @fa-var-university; } +.@{fa-css-prefix}-unlink:before { content: @fa-var-unlink; } +.@{fa-css-prefix}-unlock:before { content: @fa-var-unlock; } +.@{fa-css-prefix}-unlock-alt:before { content: @fa-var-unlock-alt; } +.@{fa-css-prefix}-untappd:before { content: @fa-var-untappd; } +.@{fa-css-prefix}-upload:before { content: @fa-var-upload; } +.@{fa-css-prefix}-usb:before { content: @fa-var-usb; } +.@{fa-css-prefix}-user:before { content: @fa-var-user; } +.@{fa-css-prefix}-user-circle:before { content: @fa-var-user-circle; } +.@{fa-css-prefix}-user-md:before { content: @fa-var-user-md; } +.@{fa-css-prefix}-user-plus:before { content: @fa-var-user-plus; } +.@{fa-css-prefix}-user-secret:before { content: @fa-var-user-secret; } +.@{fa-css-prefix}-user-times:before { content: @fa-var-user-times; } +.@{fa-css-prefix}-users:before { content: @fa-var-users; } +.@{fa-css-prefix}-ussunnah:before { content: @fa-var-ussunnah; } +.@{fa-css-prefix}-utensil-spoon:before { content: @fa-var-utensil-spoon; } +.@{fa-css-prefix}-utensils:before { content: @fa-var-utensils; } +.@{fa-css-prefix}-vaadin:before { content: @fa-var-vaadin; } +.@{fa-css-prefix}-venus:before { content: @fa-var-venus; } +.@{fa-css-prefix}-venus-double:before { content: @fa-var-venus-double; } +.@{fa-css-prefix}-venus-mars:before { content: @fa-var-venus-mars; } +.@{fa-css-prefix}-viacoin:before { content: @fa-var-viacoin; } +.@{fa-css-prefix}-viadeo:before { content: @fa-var-viadeo; } +.@{fa-css-prefix}-viadeo-square:before { content: @fa-var-viadeo-square; } +.@{fa-css-prefix}-vial:before { content: @fa-var-vial; } +.@{fa-css-prefix}-vials:before { content: @fa-var-vials; } +.@{fa-css-prefix}-viber:before { content: @fa-var-viber; } +.@{fa-css-prefix}-video:before { content: @fa-var-video; } +.@{fa-css-prefix}-video-slash:before { content: @fa-var-video-slash; } +.@{fa-css-prefix}-vimeo:before { content: @fa-var-vimeo; } +.@{fa-css-prefix}-vimeo-square:before { content: @fa-var-vimeo-square; } +.@{fa-css-prefix}-vimeo-v:before { content: @fa-var-vimeo-v; } +.@{fa-css-prefix}-vine:before { content: @fa-var-vine; } +.@{fa-css-prefix}-vk:before { content: @fa-var-vk; } +.@{fa-css-prefix}-vnv:before { content: @fa-var-vnv; } +.@{fa-css-prefix}-volleyball-ball:before { content: @fa-var-volleyball-ball; } +.@{fa-css-prefix}-volume-down:before { content: @fa-var-volume-down; } +.@{fa-css-prefix}-volume-off:before { content: @fa-var-volume-off; } +.@{fa-css-prefix}-volume-up:before { content: @fa-var-volume-up; } +.@{fa-css-prefix}-vuejs:before { content: @fa-var-vuejs; } +.@{fa-css-prefix}-warehouse:before { content: @fa-var-warehouse; } +.@{fa-css-prefix}-weibo:before { content: @fa-var-weibo; } +.@{fa-css-prefix}-weight:before { content: @fa-var-weight; } +.@{fa-css-prefix}-weixin:before { content: @fa-var-weixin; } +.@{fa-css-prefix}-whatsapp:before { content: @fa-var-whatsapp; } +.@{fa-css-prefix}-whatsapp-square:before { content: @fa-var-whatsapp-square; } +.@{fa-css-prefix}-wheelchair:before { content: @fa-var-wheelchair; } +.@{fa-css-prefix}-whmcs:before { content: @fa-var-whmcs; } +.@{fa-css-prefix}-wifi:before { content: @fa-var-wifi; } +.@{fa-css-prefix}-wikipedia-w:before { content: @fa-var-wikipedia-w; } +.@{fa-css-prefix}-window-close:before { content: @fa-var-window-close; } +.@{fa-css-prefix}-window-maximize:before { content: @fa-var-window-maximize; } +.@{fa-css-prefix}-window-minimize:before { content: @fa-var-window-minimize; } +.@{fa-css-prefix}-window-restore:before { content: @fa-var-window-restore; } +.@{fa-css-prefix}-windows:before { content: @fa-var-windows; } +.@{fa-css-prefix}-wine-glass:before { content: @fa-var-wine-glass; } +.@{fa-css-prefix}-won-sign:before { content: @fa-var-won-sign; } +.@{fa-css-prefix}-wordpress:before { content: @fa-var-wordpress; } +.@{fa-css-prefix}-wordpress-simple:before { content: @fa-var-wordpress-simple; } +.@{fa-css-prefix}-wpbeginner:before { content: @fa-var-wpbeginner; } +.@{fa-css-prefix}-wpexplorer:before { content: @fa-var-wpexplorer; } +.@{fa-css-prefix}-wpforms:before { content: @fa-var-wpforms; } +.@{fa-css-prefix}-wrench:before { content: @fa-var-wrench; } +.@{fa-css-prefix}-x-ray:before { content: @fa-var-x-ray; } +.@{fa-css-prefix}-xbox:before { content: @fa-var-xbox; } +.@{fa-css-prefix}-xing:before { content: @fa-var-xing; } +.@{fa-css-prefix}-xing-square:before { content: @fa-var-xing-square; } +.@{fa-css-prefix}-y-combinator:before { content: @fa-var-y-combinator; } +.@{fa-css-prefix}-yahoo:before { content: @fa-var-yahoo; } +.@{fa-css-prefix}-yandex:before { content: @fa-var-yandex; } +.@{fa-css-prefix}-yandex-international:before { content: @fa-var-yandex-international; } +.@{fa-css-prefix}-yelp:before { content: @fa-var-yelp; } +.@{fa-css-prefix}-yen-sign:before { content: @fa-var-yen-sign; } +.@{fa-css-prefix}-yoast:before { content: @fa-var-yoast; } +.@{fa-css-prefix}-youtube:before { content: @fa-var-youtube; } +.@{fa-css-prefix}-youtube-square:before { content: @fa-var-youtube-square; } diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_larger.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_larger.less new file mode 100644 index 000000000000..6cbb1ec6ec5b --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_larger.less @@ -0,0 +1,27 @@ +// Icon Sizes +// ------------------------- + +.larger(@factor) when (@factor > 0) { + .larger((@factor - 1)); + + .@{fa-css-prefix}-@{factor}x { + font-size: (@factor * 1em); + } +} + +/* makes the font 33% larger relative to the icon container */ +.@{fa-css-prefix}-lg { + font-size: (4em / 3); + line-height: (3em / 4); + vertical-align: -.0667em; +} + +.@{fa-css-prefix}-xs { + font-size: .75em; +} + +.@{fa-css-prefix}-sm { + font-size: .875em; +} + +.larger(10); diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_list.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_list.less new file mode 100644 index 000000000000..1ff7ca7f5998 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_list.less @@ -0,0 +1,18 @@ +// List Icons +// ------------------------- + +.@{fa-css-prefix}-ul { + list-style-type: none; + margin-left: @fa-li-width * 5/4; + padding-left: 0; + + > li { position: relative; } +} + +.@{fa-css-prefix}-li { + left: -@fa-li-width; + position: absolute; + text-align: center; + width: @fa-li-width; + line-height: inherit; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_mixins.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_mixins.less new file mode 100644 index 000000000000..a4e93f90a39d --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_mixins.less @@ -0,0 +1,57 @@ +// Mixins +// -------------------------- + +.fa-icon() { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + font-weight: normal; + line-height: 1; + vertical-align: -.125em; +} + +.fa-icon-rotate(@degrees, @rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; + transform: rotate(@degrees); +} + +.fa-icon-flip(@horiz, @vert, @rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; + transform: scale(@horiz, @vert); +} + + +// Only display content to screen readers. A la Bootstrap 4. +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +.sr-only() { + border: 0; + clip: rect(0,0,0,0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +.sr-only-focusable() { + &:active, + &:focus { + clip: auto; + height: auto; + margin: 0; + overflow: visible; + position: static; + width: auto; + } +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_rotated-flipped.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_rotated-flipped.less new file mode 100644 index 000000000000..1ee31db19f44 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_rotated-flipped.less @@ -0,0 +1,23 @@ +// Rotated & Flipped Icons +// ------------------------- + +.@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } +.@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } +.@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } + +.@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } +.@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } +.@{fa-css-prefix}-flip-horizontal.@{fa-css-prefix}-flip-vertical { .fa-icon-flip(-1, -1, 2); } + +// Hook for IE8-9 +// ------------------------- + +:root { + .@{fa-css-prefix}-rotate-90, + .@{fa-css-prefix}-rotate-180, + .@{fa-css-prefix}-rotate-270, + .@{fa-css-prefix}-flip-horizontal, + .@{fa-css-prefix}-flip-vertical { + filter: none; + } +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_screen-reader.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_screen-reader.less new file mode 100644 index 000000000000..11c188196d5a --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_screen-reader.less @@ -0,0 +1,5 @@ +// Screen Readers +// ------------------------- + +.sr-only { .sr-only(); } +.sr-only-focusable { .sr-only-focusable(); } diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_stacked.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_stacked.less new file mode 100644 index 000000000000..263b5c44fceb --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_stacked.less @@ -0,0 +1,22 @@ +// Stacked Icons +// ------------------------- + +.@{fa-css-prefix}-stack { + display: inline-block; + height: 2em; + line-height: 2em; + position: relative; + vertical-align: middle; + width: 2em; +} + +.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { + left: 0; + position: absolute; + text-align: center; + width: 100%; +} + +.@{fa-css-prefix}-stack-1x { line-height: inherit; } +.@{fa-css-prefix}-stack-2x { font-size: 2em; } +.@{fa-css-prefix}-inverse { color: @fa-inverse; } diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_variables.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_variables.less new file mode 100644 index 000000000000..19d5a9ee61a5 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/_variables.less @@ -0,0 +1,887 @@ +// Variables +// -------------------------- + +@fa-font-path: "../webfonts"; +@fa-font-size-base: 16px; +@fa-line-height-base: 1; +@fa-css-prefix: fa; +@fa-version: "5.0.10"; +@fa-border-color: #eee; +@fa-inverse: #fff; +@fa-li-width: 2em; + +@fa-var-500px: "\f26e"; +@fa-var-accessible-icon: "\f368"; +@fa-var-accusoft: "\f369"; +@fa-var-address-book: "\f2b9"; +@fa-var-address-card: "\f2bb"; +@fa-var-adjust: "\f042"; +@fa-var-adn: "\f170"; +@fa-var-adversal: "\f36a"; +@fa-var-affiliatetheme: "\f36b"; +@fa-var-algolia: "\f36c"; +@fa-var-align-center: "\f037"; +@fa-var-align-justify: "\f039"; +@fa-var-align-left: "\f036"; +@fa-var-align-right: "\f038"; +@fa-var-allergies: "\f461"; +@fa-var-amazon: "\f270"; +@fa-var-amazon-pay: "\f42c"; +@fa-var-ambulance: "\f0f9"; +@fa-var-american-sign-language-interpreting: "\f2a3"; +@fa-var-amilia: "\f36d"; +@fa-var-anchor: "\f13d"; +@fa-var-android: "\f17b"; +@fa-var-angellist: "\f209"; +@fa-var-angle-double-down: "\f103"; +@fa-var-angle-double-left: "\f100"; +@fa-var-angle-double-right: "\f101"; +@fa-var-angle-double-up: "\f102"; +@fa-var-angle-down: "\f107"; +@fa-var-angle-left: "\f104"; +@fa-var-angle-right: "\f105"; +@fa-var-angle-up: "\f106"; +@fa-var-angrycreative: "\f36e"; +@fa-var-angular: "\f420"; +@fa-var-app-store: "\f36f"; +@fa-var-app-store-ios: "\f370"; +@fa-var-apper: "\f371"; +@fa-var-apple: "\f179"; +@fa-var-apple-pay: "\f415"; +@fa-var-archive: "\f187"; +@fa-var-arrow-alt-circle-down: "\f358"; +@fa-var-arrow-alt-circle-left: "\f359"; +@fa-var-arrow-alt-circle-right: "\f35a"; +@fa-var-arrow-alt-circle-up: "\f35b"; +@fa-var-arrow-circle-down: "\f0ab"; +@fa-var-arrow-circle-left: "\f0a8"; +@fa-var-arrow-circle-right: "\f0a9"; +@fa-var-arrow-circle-up: "\f0aa"; +@fa-var-arrow-down: "\f063"; +@fa-var-arrow-left: "\f060"; +@fa-var-arrow-right: "\f061"; +@fa-var-arrow-up: "\f062"; +@fa-var-arrows-alt: "\f0b2"; +@fa-var-arrows-alt-h: "\f337"; +@fa-var-arrows-alt-v: "\f338"; +@fa-var-assistive-listening-systems: "\f2a2"; +@fa-var-asterisk: "\f069"; +@fa-var-asymmetrik: "\f372"; +@fa-var-at: "\f1fa"; +@fa-var-audible: "\f373"; +@fa-var-audio-description: "\f29e"; +@fa-var-autoprefixer: "\f41c"; +@fa-var-avianex: "\f374"; +@fa-var-aviato: "\f421"; +@fa-var-aws: "\f375"; +@fa-var-backward: "\f04a"; +@fa-var-balance-scale: "\f24e"; +@fa-var-ban: "\f05e"; +@fa-var-band-aid: "\f462"; +@fa-var-bandcamp: "\f2d5"; +@fa-var-barcode: "\f02a"; +@fa-var-bars: "\f0c9"; +@fa-var-baseball-ball: "\f433"; +@fa-var-basketball-ball: "\f434"; +@fa-var-bath: "\f2cd"; +@fa-var-battery-empty: "\f244"; +@fa-var-battery-full: "\f240"; +@fa-var-battery-half: "\f242"; +@fa-var-battery-quarter: "\f243"; +@fa-var-battery-three-quarters: "\f241"; +@fa-var-bed: "\f236"; +@fa-var-beer: "\f0fc"; +@fa-var-behance: "\f1b4"; +@fa-var-behance-square: "\f1b5"; +@fa-var-bell: "\f0f3"; +@fa-var-bell-slash: "\f1f6"; +@fa-var-bicycle: "\f206"; +@fa-var-bimobject: "\f378"; +@fa-var-binoculars: "\f1e5"; +@fa-var-birthday-cake: "\f1fd"; +@fa-var-bitbucket: "\f171"; +@fa-var-bitcoin: "\f379"; +@fa-var-bity: "\f37a"; +@fa-var-black-tie: "\f27e"; +@fa-var-blackberry: "\f37b"; +@fa-var-blind: "\f29d"; +@fa-var-blogger: "\f37c"; +@fa-var-blogger-b: "\f37d"; +@fa-var-bluetooth: "\f293"; +@fa-var-bluetooth-b: "\f294"; +@fa-var-bold: "\f032"; +@fa-var-bolt: "\f0e7"; +@fa-var-bomb: "\f1e2"; +@fa-var-book: "\f02d"; +@fa-var-bookmark: "\f02e"; +@fa-var-bowling-ball: "\f436"; +@fa-var-box: "\f466"; +@fa-var-box-open: "\f49e"; +@fa-var-boxes: "\f468"; +@fa-var-braille: "\f2a1"; +@fa-var-briefcase: "\f0b1"; +@fa-var-briefcase-medical: "\f469"; +@fa-var-btc: "\f15a"; +@fa-var-bug: "\f188"; +@fa-var-building: "\f1ad"; +@fa-var-bullhorn: "\f0a1"; +@fa-var-bullseye: "\f140"; +@fa-var-burn: "\f46a"; +@fa-var-buromobelexperte: "\f37f"; +@fa-var-bus: "\f207"; +@fa-var-buysellads: "\f20d"; +@fa-var-calculator: "\f1ec"; +@fa-var-calendar: "\f133"; +@fa-var-calendar-alt: "\f073"; +@fa-var-calendar-check: "\f274"; +@fa-var-calendar-minus: "\f272"; +@fa-var-calendar-plus: "\f271"; +@fa-var-calendar-times: "\f273"; +@fa-var-camera: "\f030"; +@fa-var-camera-retro: "\f083"; +@fa-var-capsules: "\f46b"; +@fa-var-car: "\f1b9"; +@fa-var-caret-down: "\f0d7"; +@fa-var-caret-left: "\f0d9"; +@fa-var-caret-right: "\f0da"; +@fa-var-caret-square-down: "\f150"; +@fa-var-caret-square-left: "\f191"; +@fa-var-caret-square-right: "\f152"; +@fa-var-caret-square-up: "\f151"; +@fa-var-caret-up: "\f0d8"; +@fa-var-cart-arrow-down: "\f218"; +@fa-var-cart-plus: "\f217"; +@fa-var-cc-amazon-pay: "\f42d"; +@fa-var-cc-amex: "\f1f3"; +@fa-var-cc-apple-pay: "\f416"; +@fa-var-cc-diners-club: "\f24c"; +@fa-var-cc-discover: "\f1f2"; +@fa-var-cc-jcb: "\f24b"; +@fa-var-cc-mastercard: "\f1f1"; +@fa-var-cc-paypal: "\f1f4"; +@fa-var-cc-stripe: "\f1f5"; +@fa-var-cc-visa: "\f1f0"; +@fa-var-centercode: "\f380"; +@fa-var-certificate: "\f0a3"; +@fa-var-chart-area: "\f1fe"; +@fa-var-chart-bar: "\f080"; +@fa-var-chart-line: "\f201"; +@fa-var-chart-pie: "\f200"; +@fa-var-check: "\f00c"; +@fa-var-check-circle: "\f058"; +@fa-var-check-square: "\f14a"; +@fa-var-chess: "\f439"; +@fa-var-chess-bishop: "\f43a"; +@fa-var-chess-board: "\f43c"; +@fa-var-chess-king: "\f43f"; +@fa-var-chess-knight: "\f441"; +@fa-var-chess-pawn: "\f443"; +@fa-var-chess-queen: "\f445"; +@fa-var-chess-rook: "\f447"; +@fa-var-chevron-circle-down: "\f13a"; +@fa-var-chevron-circle-left: "\f137"; +@fa-var-chevron-circle-right: "\f138"; +@fa-var-chevron-circle-up: "\f139"; +@fa-var-chevron-down: "\f078"; +@fa-var-chevron-left: "\f053"; +@fa-var-chevron-right: "\f054"; +@fa-var-chevron-up: "\f077"; +@fa-var-child: "\f1ae"; +@fa-var-chrome: "\f268"; +@fa-var-circle: "\f111"; +@fa-var-circle-notch: "\f1ce"; +@fa-var-clipboard: "\f328"; +@fa-var-clipboard-check: "\f46c"; +@fa-var-clipboard-list: "\f46d"; +@fa-var-clock: "\f017"; +@fa-var-clone: "\f24d"; +@fa-var-closed-captioning: "\f20a"; +@fa-var-cloud: "\f0c2"; +@fa-var-cloud-download-alt: "\f381"; +@fa-var-cloud-upload-alt: "\f382"; +@fa-var-cloudscale: "\f383"; +@fa-var-cloudsmith: "\f384"; +@fa-var-cloudversify: "\f385"; +@fa-var-code: "\f121"; +@fa-var-code-branch: "\f126"; +@fa-var-codepen: "\f1cb"; +@fa-var-codiepie: "\f284"; +@fa-var-coffee: "\f0f4"; +@fa-var-cog: "\f013"; +@fa-var-cogs: "\f085"; +@fa-var-columns: "\f0db"; +@fa-var-comment: "\f075"; +@fa-var-comment-alt: "\f27a"; +@fa-var-comment-dots: "\f4ad"; +@fa-var-comment-slash: "\f4b3"; +@fa-var-comments: "\f086"; +@fa-var-compass: "\f14e"; +@fa-var-compress: "\f066"; +@fa-var-connectdevelop: "\f20e"; +@fa-var-contao: "\f26d"; +@fa-var-copy: "\f0c5"; +@fa-var-copyright: "\f1f9"; +@fa-var-couch: "\f4b8"; +@fa-var-cpanel: "\f388"; +@fa-var-creative-commons: "\f25e"; +@fa-var-credit-card: "\f09d"; +@fa-var-crop: "\f125"; +@fa-var-crosshairs: "\f05b"; +@fa-var-css3: "\f13c"; +@fa-var-css3-alt: "\f38b"; +@fa-var-cube: "\f1b2"; +@fa-var-cubes: "\f1b3"; +@fa-var-cut: "\f0c4"; +@fa-var-cuttlefish: "\f38c"; +@fa-var-d-and-d: "\f38d"; +@fa-var-dashcube: "\f210"; +@fa-var-database: "\f1c0"; +@fa-var-deaf: "\f2a4"; +@fa-var-delicious: "\f1a5"; +@fa-var-deploydog: "\f38e"; +@fa-var-deskpro: "\f38f"; +@fa-var-desktop: "\f108"; +@fa-var-deviantart: "\f1bd"; +@fa-var-diagnoses: "\f470"; +@fa-var-digg: "\f1a6"; +@fa-var-digital-ocean: "\f391"; +@fa-var-discord: "\f392"; +@fa-var-discourse: "\f393"; +@fa-var-dna: "\f471"; +@fa-var-dochub: "\f394"; +@fa-var-docker: "\f395"; +@fa-var-dollar-sign: "\f155"; +@fa-var-dolly: "\f472"; +@fa-var-dolly-flatbed: "\f474"; +@fa-var-donate: "\f4b9"; +@fa-var-dot-circle: "\f192"; +@fa-var-dove: "\f4ba"; +@fa-var-download: "\f019"; +@fa-var-draft2digital: "\f396"; +@fa-var-dribbble: "\f17d"; +@fa-var-dribbble-square: "\f397"; +@fa-var-dropbox: "\f16b"; +@fa-var-drupal: "\f1a9"; +@fa-var-dyalog: "\f399"; +@fa-var-earlybirds: "\f39a"; +@fa-var-edge: "\f282"; +@fa-var-edit: "\f044"; +@fa-var-eject: "\f052"; +@fa-var-elementor: "\f430"; +@fa-var-ellipsis-h: "\f141"; +@fa-var-ellipsis-v: "\f142"; +@fa-var-ember: "\f423"; +@fa-var-empire: "\f1d1"; +@fa-var-envelope: "\f0e0"; +@fa-var-envelope-open: "\f2b6"; +@fa-var-envelope-square: "\f199"; +@fa-var-envira: "\f299"; +@fa-var-eraser: "\f12d"; +@fa-var-erlang: "\f39d"; +@fa-var-ethereum: "\f42e"; +@fa-var-etsy: "\f2d7"; +@fa-var-euro-sign: "\f153"; +@fa-var-exchange-alt: "\f362"; +@fa-var-exclamation: "\f12a"; +@fa-var-exclamation-circle: "\f06a"; +@fa-var-exclamation-triangle: "\f071"; +@fa-var-expand: "\f065"; +@fa-var-expand-arrows-alt: "\f31e"; +@fa-var-expeditedssl: "\f23e"; +@fa-var-external-link-alt: "\f35d"; +@fa-var-external-link-square-alt: "\f360"; +@fa-var-eye: "\f06e"; +@fa-var-eye-dropper: "\f1fb"; +@fa-var-eye-slash: "\f070"; +@fa-var-facebook: "\f09a"; +@fa-var-facebook-f: "\f39e"; +@fa-var-facebook-messenger: "\f39f"; +@fa-var-facebook-square: "\f082"; +@fa-var-fast-backward: "\f049"; +@fa-var-fast-forward: "\f050"; +@fa-var-fax: "\f1ac"; +@fa-var-female: "\f182"; +@fa-var-fighter-jet: "\f0fb"; +@fa-var-file: "\f15b"; +@fa-var-file-alt: "\f15c"; +@fa-var-file-archive: "\f1c6"; +@fa-var-file-audio: "\f1c7"; +@fa-var-file-code: "\f1c9"; +@fa-var-file-excel: "\f1c3"; +@fa-var-file-image: "\f1c5"; +@fa-var-file-medical: "\f477"; +@fa-var-file-medical-alt: "\f478"; +@fa-var-file-pdf: "\f1c1"; +@fa-var-file-powerpoint: "\f1c4"; +@fa-var-file-video: "\f1c8"; +@fa-var-file-word: "\f1c2"; +@fa-var-film: "\f008"; +@fa-var-filter: "\f0b0"; +@fa-var-fire: "\f06d"; +@fa-var-fire-extinguisher: "\f134"; +@fa-var-firefox: "\f269"; +@fa-var-first-aid: "\f479"; +@fa-var-first-order: "\f2b0"; +@fa-var-firstdraft: "\f3a1"; +@fa-var-flag: "\f024"; +@fa-var-flag-checkered: "\f11e"; +@fa-var-flask: "\f0c3"; +@fa-var-flickr: "\f16e"; +@fa-var-flipboard: "\f44d"; +@fa-var-fly: "\f417"; +@fa-var-folder: "\f07b"; +@fa-var-folder-open: "\f07c"; +@fa-var-font: "\f031"; +@fa-var-font-awesome: "\f2b4"; +@fa-var-font-awesome-alt: "\f35c"; +@fa-var-font-awesome-flag: "\f425"; +@fa-var-fonticons: "\f280"; +@fa-var-fonticons-fi: "\f3a2"; +@fa-var-football-ball: "\f44e"; +@fa-var-fort-awesome: "\f286"; +@fa-var-fort-awesome-alt: "\f3a3"; +@fa-var-forumbee: "\f211"; +@fa-var-forward: "\f04e"; +@fa-var-foursquare: "\f180"; +@fa-var-free-code-camp: "\f2c5"; +@fa-var-freebsd: "\f3a4"; +@fa-var-frown: "\f119"; +@fa-var-futbol: "\f1e3"; +@fa-var-gamepad: "\f11b"; +@fa-var-gavel: "\f0e3"; +@fa-var-gem: "\f3a5"; +@fa-var-genderless: "\f22d"; +@fa-var-get-pocket: "\f265"; +@fa-var-gg: "\f260"; +@fa-var-gg-circle: "\f261"; +@fa-var-gift: "\f06b"; +@fa-var-git: "\f1d3"; +@fa-var-git-square: "\f1d2"; +@fa-var-github: "\f09b"; +@fa-var-github-alt: "\f113"; +@fa-var-github-square: "\f092"; +@fa-var-gitkraken: "\f3a6"; +@fa-var-gitlab: "\f296"; +@fa-var-gitter: "\f426"; +@fa-var-glass-martini: "\f000"; +@fa-var-glide: "\f2a5"; +@fa-var-glide-g: "\f2a6"; +@fa-var-globe: "\f0ac"; +@fa-var-gofore: "\f3a7"; +@fa-var-golf-ball: "\f450"; +@fa-var-goodreads: "\f3a8"; +@fa-var-goodreads-g: "\f3a9"; +@fa-var-google: "\f1a0"; +@fa-var-google-drive: "\f3aa"; +@fa-var-google-play: "\f3ab"; +@fa-var-google-plus: "\f2b3"; +@fa-var-google-plus-g: "\f0d5"; +@fa-var-google-plus-square: "\f0d4"; +@fa-var-google-wallet: "\f1ee"; +@fa-var-graduation-cap: "\f19d"; +@fa-var-gratipay: "\f184"; +@fa-var-grav: "\f2d6"; +@fa-var-gripfire: "\f3ac"; +@fa-var-grunt: "\f3ad"; +@fa-var-gulp: "\f3ae"; +@fa-var-h-square: "\f0fd"; +@fa-var-hacker-news: "\f1d4"; +@fa-var-hacker-news-square: "\f3af"; +@fa-var-hand-holding: "\f4bd"; +@fa-var-hand-holding-heart: "\f4be"; +@fa-var-hand-holding-usd: "\f4c0"; +@fa-var-hand-lizard: "\f258"; +@fa-var-hand-paper: "\f256"; +@fa-var-hand-peace: "\f25b"; +@fa-var-hand-point-down: "\f0a7"; +@fa-var-hand-point-left: "\f0a5"; +@fa-var-hand-point-right: "\f0a4"; +@fa-var-hand-point-up: "\f0a6"; +@fa-var-hand-pointer: "\f25a"; +@fa-var-hand-rock: "\f255"; +@fa-var-hand-scissors: "\f257"; +@fa-var-hand-spock: "\f259"; +@fa-var-hands: "\f4c2"; +@fa-var-hands-helping: "\f4c4"; +@fa-var-handshake: "\f2b5"; +@fa-var-hashtag: "\f292"; +@fa-var-hdd: "\f0a0"; +@fa-var-heading: "\f1dc"; +@fa-var-headphones: "\f025"; +@fa-var-heart: "\f004"; +@fa-var-heartbeat: "\f21e"; +@fa-var-hips: "\f452"; +@fa-var-hire-a-helper: "\f3b0"; +@fa-var-history: "\f1da"; +@fa-var-hockey-puck: "\f453"; +@fa-var-home: "\f015"; +@fa-var-hooli: "\f427"; +@fa-var-hospital: "\f0f8"; +@fa-var-hospital-alt: "\f47d"; +@fa-var-hospital-symbol: "\f47e"; +@fa-var-hotjar: "\f3b1"; +@fa-var-hourglass: "\f254"; +@fa-var-hourglass-end: "\f253"; +@fa-var-hourglass-half: "\f252"; +@fa-var-hourglass-start: "\f251"; +@fa-var-houzz: "\f27c"; +@fa-var-html5: "\f13b"; +@fa-var-hubspot: "\f3b2"; +@fa-var-i-cursor: "\f246"; +@fa-var-id-badge: "\f2c1"; +@fa-var-id-card: "\f2c2"; +@fa-var-id-card-alt: "\f47f"; +@fa-var-image: "\f03e"; +@fa-var-images: "\f302"; +@fa-var-imdb: "\f2d8"; +@fa-var-inbox: "\f01c"; +@fa-var-indent: "\f03c"; +@fa-var-industry: "\f275"; +@fa-var-info: "\f129"; +@fa-var-info-circle: "\f05a"; +@fa-var-instagram: "\f16d"; +@fa-var-internet-explorer: "\f26b"; +@fa-var-ioxhost: "\f208"; +@fa-var-italic: "\f033"; +@fa-var-itunes: "\f3b4"; +@fa-var-itunes-note: "\f3b5"; +@fa-var-java: "\f4e4"; +@fa-var-jenkins: "\f3b6"; +@fa-var-joget: "\f3b7"; +@fa-var-joomla: "\f1aa"; +@fa-var-js: "\f3b8"; +@fa-var-js-square: "\f3b9"; +@fa-var-jsfiddle: "\f1cc"; +@fa-var-key: "\f084"; +@fa-var-keyboard: "\f11c"; +@fa-var-keycdn: "\f3ba"; +@fa-var-kickstarter: "\f3bb"; +@fa-var-kickstarter-k: "\f3bc"; +@fa-var-korvue: "\f42f"; +@fa-var-language: "\f1ab"; +@fa-var-laptop: "\f109"; +@fa-var-laravel: "\f3bd"; +@fa-var-lastfm: "\f202"; +@fa-var-lastfm-square: "\f203"; +@fa-var-leaf: "\f06c"; +@fa-var-leanpub: "\f212"; +@fa-var-lemon: "\f094"; +@fa-var-less: "\f41d"; +@fa-var-level-down-alt: "\f3be"; +@fa-var-level-up-alt: "\f3bf"; +@fa-var-life-ring: "\f1cd"; +@fa-var-lightbulb: "\f0eb"; +@fa-var-line: "\f3c0"; +@fa-var-link: "\f0c1"; +@fa-var-linkedin: "\f08c"; +@fa-var-linkedin-in: "\f0e1"; +@fa-var-linode: "\f2b8"; +@fa-var-linux: "\f17c"; +@fa-var-lira-sign: "\f195"; +@fa-var-list: "\f03a"; +@fa-var-list-alt: "\f022"; +@fa-var-list-ol: "\f0cb"; +@fa-var-list-ul: "\f0ca"; +@fa-var-location-arrow: "\f124"; +@fa-var-lock: "\f023"; +@fa-var-lock-open: "\f3c1"; +@fa-var-long-arrow-alt-down: "\f309"; +@fa-var-long-arrow-alt-left: "\f30a"; +@fa-var-long-arrow-alt-right: "\f30b"; +@fa-var-long-arrow-alt-up: "\f30c"; +@fa-var-low-vision: "\f2a8"; +@fa-var-lyft: "\f3c3"; +@fa-var-magento: "\f3c4"; +@fa-var-magic: "\f0d0"; +@fa-var-magnet: "\f076"; +@fa-var-male: "\f183"; +@fa-var-map: "\f279"; +@fa-var-map-marker: "\f041"; +@fa-var-map-marker-alt: "\f3c5"; +@fa-var-map-pin: "\f276"; +@fa-var-map-signs: "\f277"; +@fa-var-mars: "\f222"; +@fa-var-mars-double: "\f227"; +@fa-var-mars-stroke: "\f229"; +@fa-var-mars-stroke-h: "\f22b"; +@fa-var-mars-stroke-v: "\f22a"; +@fa-var-maxcdn: "\f136"; +@fa-var-medapps: "\f3c6"; +@fa-var-medium: "\f23a"; +@fa-var-medium-m: "\f3c7"; +@fa-var-medkit: "\f0fa"; +@fa-var-medrt: "\f3c8"; +@fa-var-meetup: "\f2e0"; +@fa-var-meh: "\f11a"; +@fa-var-mercury: "\f223"; +@fa-var-microchip: "\f2db"; +@fa-var-microphone: "\f130"; +@fa-var-microphone-slash: "\f131"; +@fa-var-microsoft: "\f3ca"; +@fa-var-minus: "\f068"; +@fa-var-minus-circle: "\f056"; +@fa-var-minus-square: "\f146"; +@fa-var-mix: "\f3cb"; +@fa-var-mixcloud: "\f289"; +@fa-var-mizuni: "\f3cc"; +@fa-var-mobile: "\f10b"; +@fa-var-mobile-alt: "\f3cd"; +@fa-var-modx: "\f285"; +@fa-var-monero: "\f3d0"; +@fa-var-money-bill-alt: "\f3d1"; +@fa-var-moon: "\f186"; +@fa-var-motorcycle: "\f21c"; +@fa-var-mouse-pointer: "\f245"; +@fa-var-music: "\f001"; +@fa-var-napster: "\f3d2"; +@fa-var-neuter: "\f22c"; +@fa-var-newspaper: "\f1ea"; +@fa-var-nintendo-switch: "\f418"; +@fa-var-node: "\f419"; +@fa-var-node-js: "\f3d3"; +@fa-var-notes-medical: "\f481"; +@fa-var-npm: "\f3d4"; +@fa-var-ns8: "\f3d5"; +@fa-var-nutritionix: "\f3d6"; +@fa-var-object-group: "\f247"; +@fa-var-object-ungroup: "\f248"; +@fa-var-odnoklassniki: "\f263"; +@fa-var-odnoklassniki-square: "\f264"; +@fa-var-opencart: "\f23d"; +@fa-var-openid: "\f19b"; +@fa-var-opera: "\f26a"; +@fa-var-optin-monster: "\f23c"; +@fa-var-osi: "\f41a"; +@fa-var-outdent: "\f03b"; +@fa-var-page4: "\f3d7"; +@fa-var-pagelines: "\f18c"; +@fa-var-paint-brush: "\f1fc"; +@fa-var-palfed: "\f3d8"; +@fa-var-pallet: "\f482"; +@fa-var-paper-plane: "\f1d8"; +@fa-var-paperclip: "\f0c6"; +@fa-var-parachute-box: "\f4cd"; +@fa-var-paragraph: "\f1dd"; +@fa-var-paste: "\f0ea"; +@fa-var-patreon: "\f3d9"; +@fa-var-pause: "\f04c"; +@fa-var-pause-circle: "\f28b"; +@fa-var-paw: "\f1b0"; +@fa-var-paypal: "\f1ed"; +@fa-var-pen-square: "\f14b"; +@fa-var-pencil-alt: "\f303"; +@fa-var-people-carry: "\f4ce"; +@fa-var-percent: "\f295"; +@fa-var-periscope: "\f3da"; +@fa-var-phabricator: "\f3db"; +@fa-var-phoenix-framework: "\f3dc"; +@fa-var-phone: "\f095"; +@fa-var-phone-slash: "\f3dd"; +@fa-var-phone-square: "\f098"; +@fa-var-phone-volume: "\f2a0"; +@fa-var-php: "\f457"; +@fa-var-pied-piper: "\f2ae"; +@fa-var-pied-piper-alt: "\f1a8"; +@fa-var-pied-piper-hat: "\f4e5"; +@fa-var-pied-piper-pp: "\f1a7"; +@fa-var-piggy-bank: "\f4d3"; +@fa-var-pills: "\f484"; +@fa-var-pinterest: "\f0d2"; +@fa-var-pinterest-p: "\f231"; +@fa-var-pinterest-square: "\f0d3"; +@fa-var-plane: "\f072"; +@fa-var-play: "\f04b"; +@fa-var-play-circle: "\f144"; +@fa-var-playstation: "\f3df"; +@fa-var-plug: "\f1e6"; +@fa-var-plus: "\f067"; +@fa-var-plus-circle: "\f055"; +@fa-var-plus-square: "\f0fe"; +@fa-var-podcast: "\f2ce"; +@fa-var-poo: "\f2fe"; +@fa-var-pound-sign: "\f154"; +@fa-var-power-off: "\f011"; +@fa-var-prescription-bottle: "\f485"; +@fa-var-prescription-bottle-alt: "\f486"; +@fa-var-print: "\f02f"; +@fa-var-procedures: "\f487"; +@fa-var-product-hunt: "\f288"; +@fa-var-pushed: "\f3e1"; +@fa-var-puzzle-piece: "\f12e"; +@fa-var-python: "\f3e2"; +@fa-var-qq: "\f1d6"; +@fa-var-qrcode: "\f029"; +@fa-var-question: "\f128"; +@fa-var-question-circle: "\f059"; +@fa-var-quidditch: "\f458"; +@fa-var-quinscape: "\f459"; +@fa-var-quora: "\f2c4"; +@fa-var-quote-left: "\f10d"; +@fa-var-quote-right: "\f10e"; +@fa-var-random: "\f074"; +@fa-var-ravelry: "\f2d9"; +@fa-var-react: "\f41b"; +@fa-var-readme: "\f4d5"; +@fa-var-rebel: "\f1d0"; +@fa-var-recycle: "\f1b8"; +@fa-var-red-river: "\f3e3"; +@fa-var-reddit: "\f1a1"; +@fa-var-reddit-alien: "\f281"; +@fa-var-reddit-square: "\f1a2"; +@fa-var-redo: "\f01e"; +@fa-var-redo-alt: "\f2f9"; +@fa-var-registered: "\f25d"; +@fa-var-rendact: "\f3e4"; +@fa-var-renren: "\f18b"; +@fa-var-reply: "\f3e5"; +@fa-var-reply-all: "\f122"; +@fa-var-replyd: "\f3e6"; +@fa-var-resolving: "\f3e7"; +@fa-var-retweet: "\f079"; +@fa-var-ribbon: "\f4d6"; +@fa-var-road: "\f018"; +@fa-var-rocket: "\f135"; +@fa-var-rocketchat: "\f3e8"; +@fa-var-rockrms: "\f3e9"; +@fa-var-rss: "\f09e"; +@fa-var-rss-square: "\f143"; +@fa-var-ruble-sign: "\f158"; +@fa-var-rupee-sign: "\f156"; +@fa-var-safari: "\f267"; +@fa-var-sass: "\f41e"; +@fa-var-save: "\f0c7"; +@fa-var-schlix: "\f3ea"; +@fa-var-scribd: "\f28a"; +@fa-var-search: "\f002"; +@fa-var-search-minus: "\f010"; +@fa-var-search-plus: "\f00e"; +@fa-var-searchengin: "\f3eb"; +@fa-var-seedling: "\f4d8"; +@fa-var-sellcast: "\f2da"; +@fa-var-sellsy: "\f213"; +@fa-var-server: "\f233"; +@fa-var-servicestack: "\f3ec"; +@fa-var-share: "\f064"; +@fa-var-share-alt: "\f1e0"; +@fa-var-share-alt-square: "\f1e1"; +@fa-var-share-square: "\f14d"; +@fa-var-shekel-sign: "\f20b"; +@fa-var-shield-alt: "\f3ed"; +@fa-var-ship: "\f21a"; +@fa-var-shipping-fast: "\f48b"; +@fa-var-shirtsinbulk: "\f214"; +@fa-var-shopping-bag: "\f290"; +@fa-var-shopping-basket: "\f291"; +@fa-var-shopping-cart: "\f07a"; +@fa-var-shower: "\f2cc"; +@fa-var-sign: "\f4d9"; +@fa-var-sign-in-alt: "\f2f6"; +@fa-var-sign-language: "\f2a7"; +@fa-var-sign-out-alt: "\f2f5"; +@fa-var-signal: "\f012"; +@fa-var-simplybuilt: "\f215"; +@fa-var-sistrix: "\f3ee"; +@fa-var-sitemap: "\f0e8"; +@fa-var-skyatlas: "\f216"; +@fa-var-skype: "\f17e"; +@fa-var-slack: "\f198"; +@fa-var-slack-hash: "\f3ef"; +@fa-var-sliders-h: "\f1de"; +@fa-var-slideshare: "\f1e7"; +@fa-var-smile: "\f118"; +@fa-var-smoking: "\f48d"; +@fa-var-snapchat: "\f2ab"; +@fa-var-snapchat-ghost: "\f2ac"; +@fa-var-snapchat-square: "\f2ad"; +@fa-var-snowflake: "\f2dc"; +@fa-var-sort: "\f0dc"; +@fa-var-sort-alpha-down: "\f15d"; +@fa-var-sort-alpha-up: "\f15e"; +@fa-var-sort-amount-down: "\f160"; +@fa-var-sort-amount-up: "\f161"; +@fa-var-sort-down: "\f0dd"; +@fa-var-sort-numeric-down: "\f162"; +@fa-var-sort-numeric-up: "\f163"; +@fa-var-sort-up: "\f0de"; +@fa-var-soundcloud: "\f1be"; +@fa-var-space-shuttle: "\f197"; +@fa-var-speakap: "\f3f3"; +@fa-var-spinner: "\f110"; +@fa-var-spotify: "\f1bc"; +@fa-var-square: "\f0c8"; +@fa-var-square-full: "\f45c"; +@fa-var-stack-exchange: "\f18d"; +@fa-var-stack-overflow: "\f16c"; +@fa-var-star: "\f005"; +@fa-var-star-half: "\f089"; +@fa-var-staylinked: "\f3f5"; +@fa-var-steam: "\f1b6"; +@fa-var-steam-square: "\f1b7"; +@fa-var-steam-symbol: "\f3f6"; +@fa-var-step-backward: "\f048"; +@fa-var-step-forward: "\f051"; +@fa-var-stethoscope: "\f0f1"; +@fa-var-sticker-mule: "\f3f7"; +@fa-var-sticky-note: "\f249"; +@fa-var-stop: "\f04d"; +@fa-var-stop-circle: "\f28d"; +@fa-var-stopwatch: "\f2f2"; +@fa-var-strava: "\f428"; +@fa-var-street-view: "\f21d"; +@fa-var-strikethrough: "\f0cc"; +@fa-var-stripe: "\f429"; +@fa-var-stripe-s: "\f42a"; +@fa-var-studiovinari: "\f3f8"; +@fa-var-stumbleupon: "\f1a4"; +@fa-var-stumbleupon-circle: "\f1a3"; +@fa-var-subscript: "\f12c"; +@fa-var-subway: "\f239"; +@fa-var-suitcase: "\f0f2"; +@fa-var-sun: "\f185"; +@fa-var-superpowers: "\f2dd"; +@fa-var-superscript: "\f12b"; +@fa-var-supple: "\f3f9"; +@fa-var-sync: "\f021"; +@fa-var-sync-alt: "\f2f1"; +@fa-var-syringe: "\f48e"; +@fa-var-table: "\f0ce"; +@fa-var-table-tennis: "\f45d"; +@fa-var-tablet: "\f10a"; +@fa-var-tablet-alt: "\f3fa"; +@fa-var-tablets: "\f490"; +@fa-var-tachometer-alt: "\f3fd"; +@fa-var-tag: "\f02b"; +@fa-var-tags: "\f02c"; +@fa-var-tape: "\f4db"; +@fa-var-tasks: "\f0ae"; +@fa-var-taxi: "\f1ba"; +@fa-var-telegram: "\f2c6"; +@fa-var-telegram-plane: "\f3fe"; +@fa-var-tencent-weibo: "\f1d5"; +@fa-var-terminal: "\f120"; +@fa-var-text-height: "\f034"; +@fa-var-text-width: "\f035"; +@fa-var-th: "\f00a"; +@fa-var-th-large: "\f009"; +@fa-var-th-list: "\f00b"; +@fa-var-themeisle: "\f2b2"; +@fa-var-thermometer: "\f491"; +@fa-var-thermometer-empty: "\f2cb"; +@fa-var-thermometer-full: "\f2c7"; +@fa-var-thermometer-half: "\f2c9"; +@fa-var-thermometer-quarter: "\f2ca"; +@fa-var-thermometer-three-quarters: "\f2c8"; +@fa-var-thumbs-down: "\f165"; +@fa-var-thumbs-up: "\f164"; +@fa-var-thumbtack: "\f08d"; +@fa-var-ticket-alt: "\f3ff"; +@fa-var-times: "\f00d"; +@fa-var-times-circle: "\f057"; +@fa-var-tint: "\f043"; +@fa-var-toggle-off: "\f204"; +@fa-var-toggle-on: "\f205"; +@fa-var-trademark: "\f25c"; +@fa-var-train: "\f238"; +@fa-var-transgender: "\f224"; +@fa-var-transgender-alt: "\f225"; +@fa-var-trash: "\f1f8"; +@fa-var-trash-alt: "\f2ed"; +@fa-var-tree: "\f1bb"; +@fa-var-trello: "\f181"; +@fa-var-tripadvisor: "\f262"; +@fa-var-trophy: "\f091"; +@fa-var-truck: "\f0d1"; +@fa-var-truck-loading: "\f4de"; +@fa-var-truck-moving: "\f4df"; +@fa-var-tty: "\f1e4"; +@fa-var-tumblr: "\f173"; +@fa-var-tumblr-square: "\f174"; +@fa-var-tv: "\f26c"; +@fa-var-twitch: "\f1e8"; +@fa-var-twitter: "\f099"; +@fa-var-twitter-square: "\f081"; +@fa-var-typo3: "\f42b"; +@fa-var-uber: "\f402"; +@fa-var-uikit: "\f403"; +@fa-var-umbrella: "\f0e9"; +@fa-var-underline: "\f0cd"; +@fa-var-undo: "\f0e2"; +@fa-var-undo-alt: "\f2ea"; +@fa-var-uniregistry: "\f404"; +@fa-var-universal-access: "\f29a"; +@fa-var-university: "\f19c"; +@fa-var-unlink: "\f127"; +@fa-var-unlock: "\f09c"; +@fa-var-unlock-alt: "\f13e"; +@fa-var-untappd: "\f405"; +@fa-var-upload: "\f093"; +@fa-var-usb: "\f287"; +@fa-var-user: "\f007"; +@fa-var-user-circle: "\f2bd"; +@fa-var-user-md: "\f0f0"; +@fa-var-user-plus: "\f234"; +@fa-var-user-secret: "\f21b"; +@fa-var-user-times: "\f235"; +@fa-var-users: "\f0c0"; +@fa-var-ussunnah: "\f407"; +@fa-var-utensil-spoon: "\f2e5"; +@fa-var-utensils: "\f2e7"; +@fa-var-vaadin: "\f408"; +@fa-var-venus: "\f221"; +@fa-var-venus-double: "\f226"; +@fa-var-venus-mars: "\f228"; +@fa-var-viacoin: "\f237"; +@fa-var-viadeo: "\f2a9"; +@fa-var-viadeo-square: "\f2aa"; +@fa-var-vial: "\f492"; +@fa-var-vials: "\f493"; +@fa-var-viber: "\f409"; +@fa-var-video: "\f03d"; +@fa-var-video-slash: "\f4e2"; +@fa-var-vimeo: "\f40a"; +@fa-var-vimeo-square: "\f194"; +@fa-var-vimeo-v: "\f27d"; +@fa-var-vine: "\f1ca"; +@fa-var-vk: "\f189"; +@fa-var-vnv: "\f40b"; +@fa-var-volleyball-ball: "\f45f"; +@fa-var-volume-down: "\f027"; +@fa-var-volume-off: "\f026"; +@fa-var-volume-up: "\f028"; +@fa-var-vuejs: "\f41f"; +@fa-var-warehouse: "\f494"; +@fa-var-weibo: "\f18a"; +@fa-var-weight: "\f496"; +@fa-var-weixin: "\f1d7"; +@fa-var-whatsapp: "\f232"; +@fa-var-whatsapp-square: "\f40c"; +@fa-var-wheelchair: "\f193"; +@fa-var-whmcs: "\f40d"; +@fa-var-wifi: "\f1eb"; +@fa-var-wikipedia-w: "\f266"; +@fa-var-window-close: "\f410"; +@fa-var-window-maximize: "\f2d0"; +@fa-var-window-minimize: "\f2d1"; +@fa-var-window-restore: "\f2d2"; +@fa-var-windows: "\f17a"; +@fa-var-wine-glass: "\f4e3"; +@fa-var-won-sign: "\f159"; +@fa-var-wordpress: "\f19a"; +@fa-var-wordpress-simple: "\f411"; +@fa-var-wpbeginner: "\f297"; +@fa-var-wpexplorer: "\f2de"; +@fa-var-wpforms: "\f298"; +@fa-var-wrench: "\f0ad"; +@fa-var-x-ray: "\f497"; +@fa-var-xbox: "\f412"; +@fa-var-xing: "\f168"; +@fa-var-xing-square: "\f169"; +@fa-var-y-combinator: "\f23b"; +@fa-var-yahoo: "\f19e"; +@fa-var-yandex: "\f413"; +@fa-var-yandex-international: "\f414"; +@fa-var-yelp: "\f1e9"; +@fa-var-yen-sign: "\f157"; +@fa-var-yoast: "\f2b1"; +@fa-var-youtube: "\f167"; +@fa-var-youtube-square: "\f431"; diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fa-brands.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fa-brands.less new file mode 100644 index 000000000000..f136d6290d7a --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fa-brands.less @@ -0,0 +1,21 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@import "_variables.less"; + +@font-face { + font-family: 'Font Awesome 5 Brands'; + font-style: normal; + font-weight: normal; + src: url('@{fa-font-path}/fa-brands-400.eot'); + src: url('@{fa-font-path}/fa-brands-400.eot?#iefix') format('embedded-opentype'), + url('@{fa-font-path}/fa-brands-400.woff2') format('woff2'), + url('@{fa-font-path}/fa-brands-400.woff') format('woff'), + url('@{fa-font-path}/fa-brands-400.ttf') format('truetype'), + url('@{fa-font-path}/fa-brands-400.svg#fontawesome') format('svg'); +} + +.fab { + font-family: 'Font Awesome 5 Brands'; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fa-regular.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fa-regular.less new file mode 100644 index 000000000000..04820bb5f9be --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fa-regular.less @@ -0,0 +1,22 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@import "_variables.less"; + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-style: normal; + font-weight: 400; + src: url('@{fa-font-path}/fa-regular-400.eot'); + src: url('@{fa-font-path}/fa-regular-400.eot?#iefix') format('embedded-opentype'), + url('@{fa-font-path}/fa-regular-400.woff2') format('woff2'), + url('@{fa-font-path}/fa-regular-400.woff') format('woff'), + url('@{fa-font-path}/fa-regular-400.ttf') format('truetype'), + url('@{fa-font-path}/fa-regular-400.svg#fontawesome') format('svg'); +} + +.far { + font-family: 'Font Awesome 5 Free'; + font-weight: 400; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fa-solid.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fa-solid.less new file mode 100644 index 000000000000..2bcfb0d267dd --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fa-solid.less @@ -0,0 +1,23 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@import "_variables.less"; + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-style: normal; + font-weight: 900; + src: url('@{fa-font-path}/fa-solid-900.eot'); + src: url('@{fa-font-path}/fa-solid-900.eot?#iefix') format('embedded-opentype'), + url('@{fa-font-path}/fa-solid-900.woff2') format('woff2'), + url('@{fa-font-path}/fa-solid-900.woff') format('woff'), + url('@{fa-font-path}/fa-solid-900.ttf') format('truetype'), + url('@{fa-font-path}/fa-solid-900.svg#fontawesome') format('svg'); +} + +.fa, +.fas { + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fontawesome.less b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fontawesome.less new file mode 100644 index 000000000000..fecdf7831cea --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/less/fontawesome.less @@ -0,0 +1,16 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@import "_variables.less"; +@import "_mixins.less"; +@import "_core.less"; +@import "_larger.less"; +@import "_fixed-width.less"; +@import "_list.less"; +@import "_bordered-pulled.less"; +@import "_animated.less"; +@import "_rotated-flipped.less"; +@import "_stacked.less"; +@import "_icons.less"; +@import "_screen-reader.less"; diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_animated.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_animated.scss new file mode 100644 index 000000000000..7c7c0e173c5b --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_animated.scss @@ -0,0 +1,20 @@ +// Animated Icons +// -------------------------- + +.#{$fa-css-prefix}-spin { + animation: fa-spin 2s infinite linear; +} + +.#{$fa-css-prefix}-pulse { + animation: fa-spin 1s infinite steps(8); +} + +@keyframes fa-spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_bordered-pulled.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_bordered-pulled.scss new file mode 100644 index 000000000000..c8c4274c4095 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_bordered-pulled.scss @@ -0,0 +1,20 @@ +// Bordered & Pulled +// ------------------------- + +.#{$fa-css-prefix}-border { + border: solid .08em $fa-border-color; + border-radius: .1em; + padding: .2em .25em .15em; +} + +.#{$fa-css-prefix}-pull-left { float: left; } +.#{$fa-css-prefix}-pull-right { float: right; } + +.#{$fa-css-prefix}, +.fas, +.far, +.fal, +.fab { + &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } + &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_core.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_core.scss new file mode 100644 index 000000000000..7fd37f855cf5 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_core.scss @@ -0,0 +1,16 @@ +// Base Class Definition +// ------------------------- + +.#{$fa-css-prefix}, +.fas, +.far, +.fal, +.fab { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_fixed-width.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_fixed-width.scss new file mode 100644 index 000000000000..5b33eb49aa9b --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_fixed-width.scss @@ -0,0 +1,6 @@ +// Fixed Width Icons +// ------------------------- +.#{$fa-css-prefix}-fw { + text-align: center; + width: (20em / 16); +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_icons.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_icons.scss new file mode 100644 index 000000000000..37f969c74f66 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_icons.scss @@ -0,0 +1,878 @@ +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen +readers do not read off random characters that represent icons */ + +.#{$fa-css-prefix}-500px:before { content: fa-content($fa-var-500px); } +.#{$fa-css-prefix}-accessible-icon:before { content: fa-content($fa-var-accessible-icon); } +.#{$fa-css-prefix}-accusoft:before { content: fa-content($fa-var-accusoft); } +.#{$fa-css-prefix}-address-book:before { content: fa-content($fa-var-address-book); } +.#{$fa-css-prefix}-address-card:before { content: fa-content($fa-var-address-card); } +.#{$fa-css-prefix}-adjust:before { content: fa-content($fa-var-adjust); } +.#{$fa-css-prefix}-adn:before { content: fa-content($fa-var-adn); } +.#{$fa-css-prefix}-adversal:before { content: fa-content($fa-var-adversal); } +.#{$fa-css-prefix}-affiliatetheme:before { content: fa-content($fa-var-affiliatetheme); } +.#{$fa-css-prefix}-algolia:before { content: fa-content($fa-var-algolia); } +.#{$fa-css-prefix}-align-center:before { content: fa-content($fa-var-align-center); } +.#{$fa-css-prefix}-align-justify:before { content: fa-content($fa-var-align-justify); } +.#{$fa-css-prefix}-align-left:before { content: fa-content($fa-var-align-left); } +.#{$fa-css-prefix}-align-right:before { content: fa-content($fa-var-align-right); } +.#{$fa-css-prefix}-allergies:before { content: fa-content($fa-var-allergies); } +.#{$fa-css-prefix}-amazon:before { content: fa-content($fa-var-amazon); } +.#{$fa-css-prefix}-amazon-pay:before { content: fa-content($fa-var-amazon-pay); } +.#{$fa-css-prefix}-ambulance:before { content: fa-content($fa-var-ambulance); } +.#{$fa-css-prefix}-american-sign-language-interpreting:before { content: fa-content($fa-var-american-sign-language-interpreting); } +.#{$fa-css-prefix}-amilia:before { content: fa-content($fa-var-amilia); } +.#{$fa-css-prefix}-anchor:before { content: fa-content($fa-var-anchor); } +.#{$fa-css-prefix}-android:before { content: fa-content($fa-var-android); } +.#{$fa-css-prefix}-angellist:before { content: fa-content($fa-var-angellist); } +.#{$fa-css-prefix}-angle-double-down:before { content: fa-content($fa-var-angle-double-down); } +.#{$fa-css-prefix}-angle-double-left:before { content: fa-content($fa-var-angle-double-left); } +.#{$fa-css-prefix}-angle-double-right:before { content: fa-content($fa-var-angle-double-right); } +.#{$fa-css-prefix}-angle-double-up:before { content: fa-content($fa-var-angle-double-up); } +.#{$fa-css-prefix}-angle-down:before { content: fa-content($fa-var-angle-down); } +.#{$fa-css-prefix}-angle-left:before { content: fa-content($fa-var-angle-left); } +.#{$fa-css-prefix}-angle-right:before { content: fa-content($fa-var-angle-right); } +.#{$fa-css-prefix}-angle-up:before { content: fa-content($fa-var-angle-up); } +.#{$fa-css-prefix}-angrycreative:before { content: fa-content($fa-var-angrycreative); } +.#{$fa-css-prefix}-angular:before { content: fa-content($fa-var-angular); } +.#{$fa-css-prefix}-app-store:before { content: fa-content($fa-var-app-store); } +.#{$fa-css-prefix}-app-store-ios:before { content: fa-content($fa-var-app-store-ios); } +.#{$fa-css-prefix}-apper:before { content: fa-content($fa-var-apper); } +.#{$fa-css-prefix}-apple:before { content: fa-content($fa-var-apple); } +.#{$fa-css-prefix}-apple-pay:before { content: fa-content($fa-var-apple-pay); } +.#{$fa-css-prefix}-archive:before { content: fa-content($fa-var-archive); } +.#{$fa-css-prefix}-arrow-alt-circle-down:before { content: fa-content($fa-var-arrow-alt-circle-down); } +.#{$fa-css-prefix}-arrow-alt-circle-left:before { content: fa-content($fa-var-arrow-alt-circle-left); } +.#{$fa-css-prefix}-arrow-alt-circle-right:before { content: fa-content($fa-var-arrow-alt-circle-right); } +.#{$fa-css-prefix}-arrow-alt-circle-up:before { content: fa-content($fa-var-arrow-alt-circle-up); } +.#{$fa-css-prefix}-arrow-circle-down:before { content: fa-content($fa-var-arrow-circle-down); } +.#{$fa-css-prefix}-arrow-circle-left:before { content: fa-content($fa-var-arrow-circle-left); } +.#{$fa-css-prefix}-arrow-circle-right:before { content: fa-content($fa-var-arrow-circle-right); } +.#{$fa-css-prefix}-arrow-circle-up:before { content: fa-content($fa-var-arrow-circle-up); } +.#{$fa-css-prefix}-arrow-down:before { content: fa-content($fa-var-arrow-down); } +.#{$fa-css-prefix}-arrow-left:before { content: fa-content($fa-var-arrow-left); } +.#{$fa-css-prefix}-arrow-right:before { content: fa-content($fa-var-arrow-right); } +.#{$fa-css-prefix}-arrow-up:before { content: fa-content($fa-var-arrow-up); } +.#{$fa-css-prefix}-arrows-alt:before { content: fa-content($fa-var-arrows-alt); } +.#{$fa-css-prefix}-arrows-alt-h:before { content: fa-content($fa-var-arrows-alt-h); } +.#{$fa-css-prefix}-arrows-alt-v:before { content: fa-content($fa-var-arrows-alt-v); } +.#{$fa-css-prefix}-assistive-listening-systems:before { content: fa-content($fa-var-assistive-listening-systems); } +.#{$fa-css-prefix}-asterisk:before { content: fa-content($fa-var-asterisk); } +.#{$fa-css-prefix}-asymmetrik:before { content: fa-content($fa-var-asymmetrik); } +.#{$fa-css-prefix}-at:before { content: fa-content($fa-var-at); } +.#{$fa-css-prefix}-audible:before { content: fa-content($fa-var-audible); } +.#{$fa-css-prefix}-audio-description:before { content: fa-content($fa-var-audio-description); } +.#{$fa-css-prefix}-autoprefixer:before { content: fa-content($fa-var-autoprefixer); } +.#{$fa-css-prefix}-avianex:before { content: fa-content($fa-var-avianex); } +.#{$fa-css-prefix}-aviato:before { content: fa-content($fa-var-aviato); } +.#{$fa-css-prefix}-aws:before { content: fa-content($fa-var-aws); } +.#{$fa-css-prefix}-backward:before { content: fa-content($fa-var-backward); } +.#{$fa-css-prefix}-balance-scale:before { content: fa-content($fa-var-balance-scale); } +.#{$fa-css-prefix}-ban:before { content: fa-content($fa-var-ban); } +.#{$fa-css-prefix}-band-aid:before { content: fa-content($fa-var-band-aid); } +.#{$fa-css-prefix}-bandcamp:before { content: fa-content($fa-var-bandcamp); } +.#{$fa-css-prefix}-barcode:before { content: fa-content($fa-var-barcode); } +.#{$fa-css-prefix}-bars:before { content: fa-content($fa-var-bars); } +.#{$fa-css-prefix}-baseball-ball:before { content: fa-content($fa-var-baseball-ball); } +.#{$fa-css-prefix}-basketball-ball:before { content: fa-content($fa-var-basketball-ball); } +.#{$fa-css-prefix}-bath:before { content: fa-content($fa-var-bath); } +.#{$fa-css-prefix}-battery-empty:before { content: fa-content($fa-var-battery-empty); } +.#{$fa-css-prefix}-battery-full:before { content: fa-content($fa-var-battery-full); } +.#{$fa-css-prefix}-battery-half:before { content: fa-content($fa-var-battery-half); } +.#{$fa-css-prefix}-battery-quarter:before { content: fa-content($fa-var-battery-quarter); } +.#{$fa-css-prefix}-battery-three-quarters:before { content: fa-content($fa-var-battery-three-quarters); } +.#{$fa-css-prefix}-bed:before { content: fa-content($fa-var-bed); } +.#{$fa-css-prefix}-beer:before { content: fa-content($fa-var-beer); } +.#{$fa-css-prefix}-behance:before { content: fa-content($fa-var-behance); } +.#{$fa-css-prefix}-behance-square:before { content: fa-content($fa-var-behance-square); } +.#{$fa-css-prefix}-bell:before { content: fa-content($fa-var-bell); } +.#{$fa-css-prefix}-bell-slash:before { content: fa-content($fa-var-bell-slash); } +.#{$fa-css-prefix}-bicycle:before { content: fa-content($fa-var-bicycle); } +.#{$fa-css-prefix}-bimobject:before { content: fa-content($fa-var-bimobject); } +.#{$fa-css-prefix}-binoculars:before { content: fa-content($fa-var-binoculars); } +.#{$fa-css-prefix}-birthday-cake:before { content: fa-content($fa-var-birthday-cake); } +.#{$fa-css-prefix}-bitbucket:before { content: fa-content($fa-var-bitbucket); } +.#{$fa-css-prefix}-bitcoin:before { content: fa-content($fa-var-bitcoin); } +.#{$fa-css-prefix}-bity:before { content: fa-content($fa-var-bity); } +.#{$fa-css-prefix}-black-tie:before { content: fa-content($fa-var-black-tie); } +.#{$fa-css-prefix}-blackberry:before { content: fa-content($fa-var-blackberry); } +.#{$fa-css-prefix}-blind:before { content: fa-content($fa-var-blind); } +.#{$fa-css-prefix}-blogger:before { content: fa-content($fa-var-blogger); } +.#{$fa-css-prefix}-blogger-b:before { content: fa-content($fa-var-blogger-b); } +.#{$fa-css-prefix}-bluetooth:before { content: fa-content($fa-var-bluetooth); } +.#{$fa-css-prefix}-bluetooth-b:before { content: fa-content($fa-var-bluetooth-b); } +.#{$fa-css-prefix}-bold:before { content: fa-content($fa-var-bold); } +.#{$fa-css-prefix}-bolt:before { content: fa-content($fa-var-bolt); } +.#{$fa-css-prefix}-bomb:before { content: fa-content($fa-var-bomb); } +.#{$fa-css-prefix}-book:before { content: fa-content($fa-var-book); } +.#{$fa-css-prefix}-bookmark:before { content: fa-content($fa-var-bookmark); } +.#{$fa-css-prefix}-bowling-ball:before { content: fa-content($fa-var-bowling-ball); } +.#{$fa-css-prefix}-box:before { content: fa-content($fa-var-box); } +.#{$fa-css-prefix}-box-open:before { content: fa-content($fa-var-box-open); } +.#{$fa-css-prefix}-boxes:before { content: fa-content($fa-var-boxes); } +.#{$fa-css-prefix}-braille:before { content: fa-content($fa-var-braille); } +.#{$fa-css-prefix}-briefcase:before { content: fa-content($fa-var-briefcase); } +.#{$fa-css-prefix}-briefcase-medical:before { content: fa-content($fa-var-briefcase-medical); } +.#{$fa-css-prefix}-btc:before { content: fa-content($fa-var-btc); } +.#{$fa-css-prefix}-bug:before { content: fa-content($fa-var-bug); } +.#{$fa-css-prefix}-building:before { content: fa-content($fa-var-building); } +.#{$fa-css-prefix}-bullhorn:before { content: fa-content($fa-var-bullhorn); } +.#{$fa-css-prefix}-bullseye:before { content: fa-content($fa-var-bullseye); } +.#{$fa-css-prefix}-burn:before { content: fa-content($fa-var-burn); } +.#{$fa-css-prefix}-buromobelexperte:before { content: fa-content($fa-var-buromobelexperte); } +.#{$fa-css-prefix}-bus:before { content: fa-content($fa-var-bus); } +.#{$fa-css-prefix}-buysellads:before { content: fa-content($fa-var-buysellads); } +.#{$fa-css-prefix}-calculator:before { content: fa-content($fa-var-calculator); } +.#{$fa-css-prefix}-calendar:before { content: fa-content($fa-var-calendar); } +.#{$fa-css-prefix}-calendar-alt:before { content: fa-content($fa-var-calendar-alt); } +.#{$fa-css-prefix}-calendar-check:before { content: fa-content($fa-var-calendar-check); } +.#{$fa-css-prefix}-calendar-minus:before { content: fa-content($fa-var-calendar-minus); } +.#{$fa-css-prefix}-calendar-plus:before { content: fa-content($fa-var-calendar-plus); } +.#{$fa-css-prefix}-calendar-times:before { content: fa-content($fa-var-calendar-times); } +.#{$fa-css-prefix}-camera:before { content: fa-content($fa-var-camera); } +.#{$fa-css-prefix}-camera-retro:before { content: fa-content($fa-var-camera-retro); } +.#{$fa-css-prefix}-capsules:before { content: fa-content($fa-var-capsules); } +.#{$fa-css-prefix}-car:before { content: fa-content($fa-var-car); } +.#{$fa-css-prefix}-caret-down:before { content: fa-content($fa-var-caret-down); } +.#{$fa-css-prefix}-caret-left:before { content: fa-content($fa-var-caret-left); } +.#{$fa-css-prefix}-caret-right:before { content: fa-content($fa-var-caret-right); } +.#{$fa-css-prefix}-caret-square-down:before { content: fa-content($fa-var-caret-square-down); } +.#{$fa-css-prefix}-caret-square-left:before { content: fa-content($fa-var-caret-square-left); } +.#{$fa-css-prefix}-caret-square-right:before { content: fa-content($fa-var-caret-square-right); } +.#{$fa-css-prefix}-caret-square-up:before { content: fa-content($fa-var-caret-square-up); } +.#{$fa-css-prefix}-caret-up:before { content: fa-content($fa-var-caret-up); } +.#{$fa-css-prefix}-cart-arrow-down:before { content: fa-content($fa-var-cart-arrow-down); } +.#{$fa-css-prefix}-cart-plus:before { content: fa-content($fa-var-cart-plus); } +.#{$fa-css-prefix}-cc-amazon-pay:before { content: fa-content($fa-var-cc-amazon-pay); } +.#{$fa-css-prefix}-cc-amex:before { content: fa-content($fa-var-cc-amex); } +.#{$fa-css-prefix}-cc-apple-pay:before { content: fa-content($fa-var-cc-apple-pay); } +.#{$fa-css-prefix}-cc-diners-club:before { content: fa-content($fa-var-cc-diners-club); } +.#{$fa-css-prefix}-cc-discover:before { content: fa-content($fa-var-cc-discover); } +.#{$fa-css-prefix}-cc-jcb:before { content: fa-content($fa-var-cc-jcb); } +.#{$fa-css-prefix}-cc-mastercard:before { content: fa-content($fa-var-cc-mastercard); } +.#{$fa-css-prefix}-cc-paypal:before { content: fa-content($fa-var-cc-paypal); } +.#{$fa-css-prefix}-cc-stripe:before { content: fa-content($fa-var-cc-stripe); } +.#{$fa-css-prefix}-cc-visa:before { content: fa-content($fa-var-cc-visa); } +.#{$fa-css-prefix}-centercode:before { content: fa-content($fa-var-centercode); } +.#{$fa-css-prefix}-certificate:before { content: fa-content($fa-var-certificate); } +.#{$fa-css-prefix}-chart-area:before { content: fa-content($fa-var-chart-area); } +.#{$fa-css-prefix}-chart-bar:before { content: fa-content($fa-var-chart-bar); } +.#{$fa-css-prefix}-chart-line:before { content: fa-content($fa-var-chart-line); } +.#{$fa-css-prefix}-chart-pie:before { content: fa-content($fa-var-chart-pie); } +.#{$fa-css-prefix}-check:before { content: fa-content($fa-var-check); } +.#{$fa-css-prefix}-check-circle:before { content: fa-content($fa-var-check-circle); } +.#{$fa-css-prefix}-check-square:before { content: fa-content($fa-var-check-square); } +.#{$fa-css-prefix}-chess:before { content: fa-content($fa-var-chess); } +.#{$fa-css-prefix}-chess-bishop:before { content: fa-content($fa-var-chess-bishop); } +.#{$fa-css-prefix}-chess-board:before { content: fa-content($fa-var-chess-board); } +.#{$fa-css-prefix}-chess-king:before { content: fa-content($fa-var-chess-king); } +.#{$fa-css-prefix}-chess-knight:before { content: fa-content($fa-var-chess-knight); } +.#{$fa-css-prefix}-chess-pawn:before { content: fa-content($fa-var-chess-pawn); } +.#{$fa-css-prefix}-chess-queen:before { content: fa-content($fa-var-chess-queen); } +.#{$fa-css-prefix}-chess-rook:before { content: fa-content($fa-var-chess-rook); } +.#{$fa-css-prefix}-chevron-circle-down:before { content: fa-content($fa-var-chevron-circle-down); } +.#{$fa-css-prefix}-chevron-circle-left:before { content: fa-content($fa-var-chevron-circle-left); } +.#{$fa-css-prefix}-chevron-circle-right:before { content: fa-content($fa-var-chevron-circle-right); } +.#{$fa-css-prefix}-chevron-circle-up:before { content: fa-content($fa-var-chevron-circle-up); } +.#{$fa-css-prefix}-chevron-down:before { content: fa-content($fa-var-chevron-down); } +.#{$fa-css-prefix}-chevron-left:before { content: fa-content($fa-var-chevron-left); } +.#{$fa-css-prefix}-chevron-right:before { content: fa-content($fa-var-chevron-right); } +.#{$fa-css-prefix}-chevron-up:before { content: fa-content($fa-var-chevron-up); } +.#{$fa-css-prefix}-child:before { content: fa-content($fa-var-child); } +.#{$fa-css-prefix}-chrome:before { content: fa-content($fa-var-chrome); } +.#{$fa-css-prefix}-circle:before { content: fa-content($fa-var-circle); } +.#{$fa-css-prefix}-circle-notch:before { content: fa-content($fa-var-circle-notch); } +.#{$fa-css-prefix}-clipboard:before { content: fa-content($fa-var-clipboard); } +.#{$fa-css-prefix}-clipboard-check:before { content: fa-content($fa-var-clipboard-check); } +.#{$fa-css-prefix}-clipboard-list:before { content: fa-content($fa-var-clipboard-list); } +.#{$fa-css-prefix}-clock:before { content: fa-content($fa-var-clock); } +.#{$fa-css-prefix}-clone:before { content: fa-content($fa-var-clone); } +.#{$fa-css-prefix}-closed-captioning:before { content: fa-content($fa-var-closed-captioning); } +.#{$fa-css-prefix}-cloud:before { content: fa-content($fa-var-cloud); } +.#{$fa-css-prefix}-cloud-download-alt:before { content: fa-content($fa-var-cloud-download-alt); } +.#{$fa-css-prefix}-cloud-upload-alt:before { content: fa-content($fa-var-cloud-upload-alt); } +.#{$fa-css-prefix}-cloudscale:before { content: fa-content($fa-var-cloudscale); } +.#{$fa-css-prefix}-cloudsmith:before { content: fa-content($fa-var-cloudsmith); } +.#{$fa-css-prefix}-cloudversify:before { content: fa-content($fa-var-cloudversify); } +.#{$fa-css-prefix}-code:before { content: fa-content($fa-var-code); } +.#{$fa-css-prefix}-code-branch:before { content: fa-content($fa-var-code-branch); } +.#{$fa-css-prefix}-codepen:before { content: fa-content($fa-var-codepen); } +.#{$fa-css-prefix}-codiepie:before { content: fa-content($fa-var-codiepie); } +.#{$fa-css-prefix}-coffee:before { content: fa-content($fa-var-coffee); } +.#{$fa-css-prefix}-cog:before { content: fa-content($fa-var-cog); } +.#{$fa-css-prefix}-cogs:before { content: fa-content($fa-var-cogs); } +.#{$fa-css-prefix}-columns:before { content: fa-content($fa-var-columns); } +.#{$fa-css-prefix}-comment:before { content: fa-content($fa-var-comment); } +.#{$fa-css-prefix}-comment-alt:before { content: fa-content($fa-var-comment-alt); } +.#{$fa-css-prefix}-comment-dots:before { content: fa-content($fa-var-comment-dots); } +.#{$fa-css-prefix}-comment-slash:before { content: fa-content($fa-var-comment-slash); } +.#{$fa-css-prefix}-comments:before { content: fa-content($fa-var-comments); } +.#{$fa-css-prefix}-compass:before { content: fa-content($fa-var-compass); } +.#{$fa-css-prefix}-compress:before { content: fa-content($fa-var-compress); } +.#{$fa-css-prefix}-connectdevelop:before { content: fa-content($fa-var-connectdevelop); } +.#{$fa-css-prefix}-contao:before { content: fa-content($fa-var-contao); } +.#{$fa-css-prefix}-copy:before { content: fa-content($fa-var-copy); } +.#{$fa-css-prefix}-copyright:before { content: fa-content($fa-var-copyright); } +.#{$fa-css-prefix}-couch:before { content: fa-content($fa-var-couch); } +.#{$fa-css-prefix}-cpanel:before { content: fa-content($fa-var-cpanel); } +.#{$fa-css-prefix}-creative-commons:before { content: fa-content($fa-var-creative-commons); } +.#{$fa-css-prefix}-credit-card:before { content: fa-content($fa-var-credit-card); } +.#{$fa-css-prefix}-crop:before { content: fa-content($fa-var-crop); } +.#{$fa-css-prefix}-crosshairs:before { content: fa-content($fa-var-crosshairs); } +.#{$fa-css-prefix}-css3:before { content: fa-content($fa-var-css3); } +.#{$fa-css-prefix}-css3-alt:before { content: fa-content($fa-var-css3-alt); } +.#{$fa-css-prefix}-cube:before { content: fa-content($fa-var-cube); } +.#{$fa-css-prefix}-cubes:before { content: fa-content($fa-var-cubes); } +.#{$fa-css-prefix}-cut:before { content: fa-content($fa-var-cut); } +.#{$fa-css-prefix}-cuttlefish:before { content: fa-content($fa-var-cuttlefish); } +.#{$fa-css-prefix}-d-and-d:before { content: fa-content($fa-var-d-and-d); } +.#{$fa-css-prefix}-dashcube:before { content: fa-content($fa-var-dashcube); } +.#{$fa-css-prefix}-database:before { content: fa-content($fa-var-database); } +.#{$fa-css-prefix}-deaf:before { content: fa-content($fa-var-deaf); } +.#{$fa-css-prefix}-delicious:before { content: fa-content($fa-var-delicious); } +.#{$fa-css-prefix}-deploydog:before { content: fa-content($fa-var-deploydog); } +.#{$fa-css-prefix}-deskpro:before { content: fa-content($fa-var-deskpro); } +.#{$fa-css-prefix}-desktop:before { content: fa-content($fa-var-desktop); } +.#{$fa-css-prefix}-deviantart:before { content: fa-content($fa-var-deviantart); } +.#{$fa-css-prefix}-diagnoses:before { content: fa-content($fa-var-diagnoses); } +.#{$fa-css-prefix}-digg:before { content: fa-content($fa-var-digg); } +.#{$fa-css-prefix}-digital-ocean:before { content: fa-content($fa-var-digital-ocean); } +.#{$fa-css-prefix}-discord:before { content: fa-content($fa-var-discord); } +.#{$fa-css-prefix}-discourse:before { content: fa-content($fa-var-discourse); } +.#{$fa-css-prefix}-dna:before { content: fa-content($fa-var-dna); } +.#{$fa-css-prefix}-dochub:before { content: fa-content($fa-var-dochub); } +.#{$fa-css-prefix}-docker:before { content: fa-content($fa-var-docker); } +.#{$fa-css-prefix}-dollar-sign:before { content: fa-content($fa-var-dollar-sign); } +.#{$fa-css-prefix}-dolly:before { content: fa-content($fa-var-dolly); } +.#{$fa-css-prefix}-dolly-flatbed:before { content: fa-content($fa-var-dolly-flatbed); } +.#{$fa-css-prefix}-donate:before { content: fa-content($fa-var-donate); } +.#{$fa-css-prefix}-dot-circle:before { content: fa-content($fa-var-dot-circle); } +.#{$fa-css-prefix}-dove:before { content: fa-content($fa-var-dove); } +.#{$fa-css-prefix}-download:before { content: fa-content($fa-var-download); } +.#{$fa-css-prefix}-draft2digital:before { content: fa-content($fa-var-draft2digital); } +.#{$fa-css-prefix}-dribbble:before { content: fa-content($fa-var-dribbble); } +.#{$fa-css-prefix}-dribbble-square:before { content: fa-content($fa-var-dribbble-square); } +.#{$fa-css-prefix}-dropbox:before { content: fa-content($fa-var-dropbox); } +.#{$fa-css-prefix}-drupal:before { content: fa-content($fa-var-drupal); } +.#{$fa-css-prefix}-dyalog:before { content: fa-content($fa-var-dyalog); } +.#{$fa-css-prefix}-earlybirds:before { content: fa-content($fa-var-earlybirds); } +.#{$fa-css-prefix}-edge:before { content: fa-content($fa-var-edge); } +.#{$fa-css-prefix}-edit:before { content: fa-content($fa-var-edit); } +.#{$fa-css-prefix}-eject:before { content: fa-content($fa-var-eject); } +.#{$fa-css-prefix}-elementor:before { content: fa-content($fa-var-elementor); } +.#{$fa-css-prefix}-ellipsis-h:before { content: fa-content($fa-var-ellipsis-h); } +.#{$fa-css-prefix}-ellipsis-v:before { content: fa-content($fa-var-ellipsis-v); } +.#{$fa-css-prefix}-ember:before { content: fa-content($fa-var-ember); } +.#{$fa-css-prefix}-empire:before { content: fa-content($fa-var-empire); } +.#{$fa-css-prefix}-envelope:before { content: fa-content($fa-var-envelope); } +.#{$fa-css-prefix}-envelope-open:before { content: fa-content($fa-var-envelope-open); } +.#{$fa-css-prefix}-envelope-square:before { content: fa-content($fa-var-envelope-square); } +.#{$fa-css-prefix}-envira:before { content: fa-content($fa-var-envira); } +.#{$fa-css-prefix}-eraser:before { content: fa-content($fa-var-eraser); } +.#{$fa-css-prefix}-erlang:before { content: fa-content($fa-var-erlang); } +.#{$fa-css-prefix}-ethereum:before { content: fa-content($fa-var-ethereum); } +.#{$fa-css-prefix}-etsy:before { content: fa-content($fa-var-etsy); } +.#{$fa-css-prefix}-euro-sign:before { content: fa-content($fa-var-euro-sign); } +.#{$fa-css-prefix}-exchange-alt:before { content: fa-content($fa-var-exchange-alt); } +.#{$fa-css-prefix}-exclamation:before { content: fa-content($fa-var-exclamation); } +.#{$fa-css-prefix}-exclamation-circle:before { content: fa-content($fa-var-exclamation-circle); } +.#{$fa-css-prefix}-exclamation-triangle:before { content: fa-content($fa-var-exclamation-triangle); } +.#{$fa-css-prefix}-expand:before { content: fa-content($fa-var-expand); } +.#{$fa-css-prefix}-expand-arrows-alt:before { content: fa-content($fa-var-expand-arrows-alt); } +.#{$fa-css-prefix}-expeditedssl:before { content: fa-content($fa-var-expeditedssl); } +.#{$fa-css-prefix}-external-link-alt:before { content: fa-content($fa-var-external-link-alt); } +.#{$fa-css-prefix}-external-link-square-alt:before { content: fa-content($fa-var-external-link-square-alt); } +.#{$fa-css-prefix}-eye:before { content: fa-content($fa-var-eye); } +.#{$fa-css-prefix}-eye-dropper:before { content: fa-content($fa-var-eye-dropper); } +.#{$fa-css-prefix}-eye-slash:before { content: fa-content($fa-var-eye-slash); } +.#{$fa-css-prefix}-facebook:before { content: fa-content($fa-var-facebook); } +.#{$fa-css-prefix}-facebook-f:before { content: fa-content($fa-var-facebook-f); } +.#{$fa-css-prefix}-facebook-messenger:before { content: fa-content($fa-var-facebook-messenger); } +.#{$fa-css-prefix}-facebook-square:before { content: fa-content($fa-var-facebook-square); } +.#{$fa-css-prefix}-fast-backward:before { content: fa-content($fa-var-fast-backward); } +.#{$fa-css-prefix}-fast-forward:before { content: fa-content($fa-var-fast-forward); } +.#{$fa-css-prefix}-fax:before { content: fa-content($fa-var-fax); } +.#{$fa-css-prefix}-female:before { content: fa-content($fa-var-female); } +.#{$fa-css-prefix}-fighter-jet:before { content: fa-content($fa-var-fighter-jet); } +.#{$fa-css-prefix}-file:before { content: fa-content($fa-var-file); } +.#{$fa-css-prefix}-file-alt:before { content: fa-content($fa-var-file-alt); } +.#{$fa-css-prefix}-file-archive:before { content: fa-content($fa-var-file-archive); } +.#{$fa-css-prefix}-file-audio:before { content: fa-content($fa-var-file-audio); } +.#{$fa-css-prefix}-file-code:before { content: fa-content($fa-var-file-code); } +.#{$fa-css-prefix}-file-excel:before { content: fa-content($fa-var-file-excel); } +.#{$fa-css-prefix}-file-image:before { content: fa-content($fa-var-file-image); } +.#{$fa-css-prefix}-file-medical:before { content: fa-content($fa-var-file-medical); } +.#{$fa-css-prefix}-file-medical-alt:before { content: fa-content($fa-var-file-medical-alt); } +.#{$fa-css-prefix}-file-pdf:before { content: fa-content($fa-var-file-pdf); } +.#{$fa-css-prefix}-file-powerpoint:before { content: fa-content($fa-var-file-powerpoint); } +.#{$fa-css-prefix}-file-video:before { content: fa-content($fa-var-file-video); } +.#{$fa-css-prefix}-file-word:before { content: fa-content($fa-var-file-word); } +.#{$fa-css-prefix}-film:before { content: fa-content($fa-var-film); } +.#{$fa-css-prefix}-filter:before { content: fa-content($fa-var-filter); } +.#{$fa-css-prefix}-fire:before { content: fa-content($fa-var-fire); } +.#{$fa-css-prefix}-fire-extinguisher:before { content: fa-content($fa-var-fire-extinguisher); } +.#{$fa-css-prefix}-firefox:before { content: fa-content($fa-var-firefox); } +.#{$fa-css-prefix}-first-aid:before { content: fa-content($fa-var-first-aid); } +.#{$fa-css-prefix}-first-order:before { content: fa-content($fa-var-first-order); } +.#{$fa-css-prefix}-firstdraft:before { content: fa-content($fa-var-firstdraft); } +.#{$fa-css-prefix}-flag:before { content: fa-content($fa-var-flag); } +.#{$fa-css-prefix}-flag-checkered:before { content: fa-content($fa-var-flag-checkered); } +.#{$fa-css-prefix}-flask:before { content: fa-content($fa-var-flask); } +.#{$fa-css-prefix}-flickr:before { content: fa-content($fa-var-flickr); } +.#{$fa-css-prefix}-flipboard:before { content: fa-content($fa-var-flipboard); } +.#{$fa-css-prefix}-fly:before { content: fa-content($fa-var-fly); } +.#{$fa-css-prefix}-folder:before { content: fa-content($fa-var-folder); } +.#{$fa-css-prefix}-folder-open:before { content: fa-content($fa-var-folder-open); } +.#{$fa-css-prefix}-font:before { content: fa-content($fa-var-font); } +.#{$fa-css-prefix}-font-awesome:before { content: fa-content($fa-var-font-awesome); } +.#{$fa-css-prefix}-font-awesome-alt:before { content: fa-content($fa-var-font-awesome-alt); } +.#{$fa-css-prefix}-font-awesome-flag:before { content: fa-content($fa-var-font-awesome-flag); } +.#{$fa-css-prefix}-fonticons:before { content: fa-content($fa-var-fonticons); } +.#{$fa-css-prefix}-fonticons-fi:before { content: fa-content($fa-var-fonticons-fi); } +.#{$fa-css-prefix}-football-ball:before { content: fa-content($fa-var-football-ball); } +.#{$fa-css-prefix}-fort-awesome:before { content: fa-content($fa-var-fort-awesome); } +.#{$fa-css-prefix}-fort-awesome-alt:before { content: fa-content($fa-var-fort-awesome-alt); } +.#{$fa-css-prefix}-forumbee:before { content: fa-content($fa-var-forumbee); } +.#{$fa-css-prefix}-forward:before { content: fa-content($fa-var-forward); } +.#{$fa-css-prefix}-foursquare:before { content: fa-content($fa-var-foursquare); } +.#{$fa-css-prefix}-free-code-camp:before { content: fa-content($fa-var-free-code-camp); } +.#{$fa-css-prefix}-freebsd:before { content: fa-content($fa-var-freebsd); } +.#{$fa-css-prefix}-frown:before { content: fa-content($fa-var-frown); } +.#{$fa-css-prefix}-futbol:before { content: fa-content($fa-var-futbol); } +.#{$fa-css-prefix}-gamepad:before { content: fa-content($fa-var-gamepad); } +.#{$fa-css-prefix}-gavel:before { content: fa-content($fa-var-gavel); } +.#{$fa-css-prefix}-gem:before { content: fa-content($fa-var-gem); } +.#{$fa-css-prefix}-genderless:before { content: fa-content($fa-var-genderless); } +.#{$fa-css-prefix}-get-pocket:before { content: fa-content($fa-var-get-pocket); } +.#{$fa-css-prefix}-gg:before { content: fa-content($fa-var-gg); } +.#{$fa-css-prefix}-gg-circle:before { content: fa-content($fa-var-gg-circle); } +.#{$fa-css-prefix}-gift:before { content: fa-content($fa-var-gift); } +.#{$fa-css-prefix}-git:before { content: fa-content($fa-var-git); } +.#{$fa-css-prefix}-git-square:before { content: fa-content($fa-var-git-square); } +.#{$fa-css-prefix}-github:before { content: fa-content($fa-var-github); } +.#{$fa-css-prefix}-github-alt:before { content: fa-content($fa-var-github-alt); } +.#{$fa-css-prefix}-github-square:before { content: fa-content($fa-var-github-square); } +.#{$fa-css-prefix}-gitkraken:before { content: fa-content($fa-var-gitkraken); } +.#{$fa-css-prefix}-gitlab:before { content: fa-content($fa-var-gitlab); } +.#{$fa-css-prefix}-gitter:before { content: fa-content($fa-var-gitter); } +.#{$fa-css-prefix}-glass-martini:before { content: fa-content($fa-var-glass-martini); } +.#{$fa-css-prefix}-glide:before { content: fa-content($fa-var-glide); } +.#{$fa-css-prefix}-glide-g:before { content: fa-content($fa-var-glide-g); } +.#{$fa-css-prefix}-globe:before { content: fa-content($fa-var-globe); } +.#{$fa-css-prefix}-gofore:before { content: fa-content($fa-var-gofore); } +.#{$fa-css-prefix}-golf-ball:before { content: fa-content($fa-var-golf-ball); } +.#{$fa-css-prefix}-goodreads:before { content: fa-content($fa-var-goodreads); } +.#{$fa-css-prefix}-goodreads-g:before { content: fa-content($fa-var-goodreads-g); } +.#{$fa-css-prefix}-google:before { content: fa-content($fa-var-google); } +.#{$fa-css-prefix}-google-drive:before { content: fa-content($fa-var-google-drive); } +.#{$fa-css-prefix}-google-play:before { content: fa-content($fa-var-google-play); } +.#{$fa-css-prefix}-google-plus:before { content: fa-content($fa-var-google-plus); } +.#{$fa-css-prefix}-google-plus-g:before { content: fa-content($fa-var-google-plus-g); } +.#{$fa-css-prefix}-google-plus-square:before { content: fa-content($fa-var-google-plus-square); } +.#{$fa-css-prefix}-google-wallet:before { content: fa-content($fa-var-google-wallet); } +.#{$fa-css-prefix}-graduation-cap:before { content: fa-content($fa-var-graduation-cap); } +.#{$fa-css-prefix}-gratipay:before { content: fa-content($fa-var-gratipay); } +.#{$fa-css-prefix}-grav:before { content: fa-content($fa-var-grav); } +.#{$fa-css-prefix}-gripfire:before { content: fa-content($fa-var-gripfire); } +.#{$fa-css-prefix}-grunt:before { content: fa-content($fa-var-grunt); } +.#{$fa-css-prefix}-gulp:before { content: fa-content($fa-var-gulp); } +.#{$fa-css-prefix}-h-square:before { content: fa-content($fa-var-h-square); } +.#{$fa-css-prefix}-hacker-news:before { content: fa-content($fa-var-hacker-news); } +.#{$fa-css-prefix}-hacker-news-square:before { content: fa-content($fa-var-hacker-news-square); } +.#{$fa-css-prefix}-hand-holding:before { content: fa-content($fa-var-hand-holding); } +.#{$fa-css-prefix}-hand-holding-heart:before { content: fa-content($fa-var-hand-holding-heart); } +.#{$fa-css-prefix}-hand-holding-usd:before { content: fa-content($fa-var-hand-holding-usd); } +.#{$fa-css-prefix}-hand-lizard:before { content: fa-content($fa-var-hand-lizard); } +.#{$fa-css-prefix}-hand-paper:before { content: fa-content($fa-var-hand-paper); } +.#{$fa-css-prefix}-hand-peace:before { content: fa-content($fa-var-hand-peace); } +.#{$fa-css-prefix}-hand-point-down:before { content: fa-content($fa-var-hand-point-down); } +.#{$fa-css-prefix}-hand-point-left:before { content: fa-content($fa-var-hand-point-left); } +.#{$fa-css-prefix}-hand-point-right:before { content: fa-content($fa-var-hand-point-right); } +.#{$fa-css-prefix}-hand-point-up:before { content: fa-content($fa-var-hand-point-up); } +.#{$fa-css-prefix}-hand-pointer:before { content: fa-content($fa-var-hand-pointer); } +.#{$fa-css-prefix}-hand-rock:before { content: fa-content($fa-var-hand-rock); } +.#{$fa-css-prefix}-hand-scissors:before { content: fa-content($fa-var-hand-scissors); } +.#{$fa-css-prefix}-hand-spock:before { content: fa-content($fa-var-hand-spock); } +.#{$fa-css-prefix}-hands:before { content: fa-content($fa-var-hands); } +.#{$fa-css-prefix}-hands-helping:before { content: fa-content($fa-var-hands-helping); } +.#{$fa-css-prefix}-handshake:before { content: fa-content($fa-var-handshake); } +.#{$fa-css-prefix}-hashtag:before { content: fa-content($fa-var-hashtag); } +.#{$fa-css-prefix}-hdd:before { content: fa-content($fa-var-hdd); } +.#{$fa-css-prefix}-heading:before { content: fa-content($fa-var-heading); } +.#{$fa-css-prefix}-headphones:before { content: fa-content($fa-var-headphones); } +.#{$fa-css-prefix}-heart:before { content: fa-content($fa-var-heart); } +.#{$fa-css-prefix}-heartbeat:before { content: fa-content($fa-var-heartbeat); } +.#{$fa-css-prefix}-hips:before { content: fa-content($fa-var-hips); } +.#{$fa-css-prefix}-hire-a-helper:before { content: fa-content($fa-var-hire-a-helper); } +.#{$fa-css-prefix}-history:before { content: fa-content($fa-var-history); } +.#{$fa-css-prefix}-hockey-puck:before { content: fa-content($fa-var-hockey-puck); } +.#{$fa-css-prefix}-home:before { content: fa-content($fa-var-home); } +.#{$fa-css-prefix}-hooli:before { content: fa-content($fa-var-hooli); } +.#{$fa-css-prefix}-hospital:before { content: fa-content($fa-var-hospital); } +.#{$fa-css-prefix}-hospital-alt:before { content: fa-content($fa-var-hospital-alt); } +.#{$fa-css-prefix}-hospital-symbol:before { content: fa-content($fa-var-hospital-symbol); } +.#{$fa-css-prefix}-hotjar:before { content: fa-content($fa-var-hotjar); } +.#{$fa-css-prefix}-hourglass:before { content: fa-content($fa-var-hourglass); } +.#{$fa-css-prefix}-hourglass-end:before { content: fa-content($fa-var-hourglass-end); } +.#{$fa-css-prefix}-hourglass-half:before { content: fa-content($fa-var-hourglass-half); } +.#{$fa-css-prefix}-hourglass-start:before { content: fa-content($fa-var-hourglass-start); } +.#{$fa-css-prefix}-houzz:before { content: fa-content($fa-var-houzz); } +.#{$fa-css-prefix}-html5:before { content: fa-content($fa-var-html5); } +.#{$fa-css-prefix}-hubspot:before { content: fa-content($fa-var-hubspot); } +.#{$fa-css-prefix}-i-cursor:before { content: fa-content($fa-var-i-cursor); } +.#{$fa-css-prefix}-id-badge:before { content: fa-content($fa-var-id-badge); } +.#{$fa-css-prefix}-id-card:before { content: fa-content($fa-var-id-card); } +.#{$fa-css-prefix}-id-card-alt:before { content: fa-content($fa-var-id-card-alt); } +.#{$fa-css-prefix}-image:before { content: fa-content($fa-var-image); } +.#{$fa-css-prefix}-images:before { content: fa-content($fa-var-images); } +.#{$fa-css-prefix}-imdb:before { content: fa-content($fa-var-imdb); } +.#{$fa-css-prefix}-inbox:before { content: fa-content($fa-var-inbox); } +.#{$fa-css-prefix}-indent:before { content: fa-content($fa-var-indent); } +.#{$fa-css-prefix}-industry:before { content: fa-content($fa-var-industry); } +.#{$fa-css-prefix}-info:before { content: fa-content($fa-var-info); } +.#{$fa-css-prefix}-info-circle:before { content: fa-content($fa-var-info-circle); } +.#{$fa-css-prefix}-instagram:before { content: fa-content($fa-var-instagram); } +.#{$fa-css-prefix}-internet-explorer:before { content: fa-content($fa-var-internet-explorer); } +.#{$fa-css-prefix}-ioxhost:before { content: fa-content($fa-var-ioxhost); } +.#{$fa-css-prefix}-italic:before { content: fa-content($fa-var-italic); } +.#{$fa-css-prefix}-itunes:before { content: fa-content($fa-var-itunes); } +.#{$fa-css-prefix}-itunes-note:before { content: fa-content($fa-var-itunes-note); } +.#{$fa-css-prefix}-java:before { content: fa-content($fa-var-java); } +.#{$fa-css-prefix}-jenkins:before { content: fa-content($fa-var-jenkins); } +.#{$fa-css-prefix}-joget:before { content: fa-content($fa-var-joget); } +.#{$fa-css-prefix}-joomla:before { content: fa-content($fa-var-joomla); } +.#{$fa-css-prefix}-js:before { content: fa-content($fa-var-js); } +.#{$fa-css-prefix}-js-square:before { content: fa-content($fa-var-js-square); } +.#{$fa-css-prefix}-jsfiddle:before { content: fa-content($fa-var-jsfiddle); } +.#{$fa-css-prefix}-key:before { content: fa-content($fa-var-key); } +.#{$fa-css-prefix}-keyboard:before { content: fa-content($fa-var-keyboard); } +.#{$fa-css-prefix}-keycdn:before { content: fa-content($fa-var-keycdn); } +.#{$fa-css-prefix}-kickstarter:before { content: fa-content($fa-var-kickstarter); } +.#{$fa-css-prefix}-kickstarter-k:before { content: fa-content($fa-var-kickstarter-k); } +.#{$fa-css-prefix}-korvue:before { content: fa-content($fa-var-korvue); } +.#{$fa-css-prefix}-language:before { content: fa-content($fa-var-language); } +.#{$fa-css-prefix}-laptop:before { content: fa-content($fa-var-laptop); } +.#{$fa-css-prefix}-laravel:before { content: fa-content($fa-var-laravel); } +.#{$fa-css-prefix}-lastfm:before { content: fa-content($fa-var-lastfm); } +.#{$fa-css-prefix}-lastfm-square:before { content: fa-content($fa-var-lastfm-square); } +.#{$fa-css-prefix}-leaf:before { content: fa-content($fa-var-leaf); } +.#{$fa-css-prefix}-leanpub:before { content: fa-content($fa-var-leanpub); } +.#{$fa-css-prefix}-lemon:before { content: fa-content($fa-var-lemon); } +.#{$fa-css-prefix}-less:before { content: fa-content($fa-var-less); } +.#{$fa-css-prefix}-level-down-alt:before { content: fa-content($fa-var-level-down-alt); } +.#{$fa-css-prefix}-level-up-alt:before { content: fa-content($fa-var-level-up-alt); } +.#{$fa-css-prefix}-life-ring:before { content: fa-content($fa-var-life-ring); } +.#{$fa-css-prefix}-lightbulb:before { content: fa-content($fa-var-lightbulb); } +.#{$fa-css-prefix}-line:before { content: fa-content($fa-var-line); } +.#{$fa-css-prefix}-link:before { content: fa-content($fa-var-link); } +.#{$fa-css-prefix}-linkedin:before { content: fa-content($fa-var-linkedin); } +.#{$fa-css-prefix}-linkedin-in:before { content: fa-content($fa-var-linkedin-in); } +.#{$fa-css-prefix}-linode:before { content: fa-content($fa-var-linode); } +.#{$fa-css-prefix}-linux:before { content: fa-content($fa-var-linux); } +.#{$fa-css-prefix}-lira-sign:before { content: fa-content($fa-var-lira-sign); } +.#{$fa-css-prefix}-list:before { content: fa-content($fa-var-list); } +.#{$fa-css-prefix}-list-alt:before { content: fa-content($fa-var-list-alt); } +.#{$fa-css-prefix}-list-ol:before { content: fa-content($fa-var-list-ol); } +.#{$fa-css-prefix}-list-ul:before { content: fa-content($fa-var-list-ul); } +.#{$fa-css-prefix}-location-arrow:before { content: fa-content($fa-var-location-arrow); } +.#{$fa-css-prefix}-lock:before { content: fa-content($fa-var-lock); } +.#{$fa-css-prefix}-lock-open:before { content: fa-content($fa-var-lock-open); } +.#{$fa-css-prefix}-long-arrow-alt-down:before { content: fa-content($fa-var-long-arrow-alt-down); } +.#{$fa-css-prefix}-long-arrow-alt-left:before { content: fa-content($fa-var-long-arrow-alt-left); } +.#{$fa-css-prefix}-long-arrow-alt-right:before { content: fa-content($fa-var-long-arrow-alt-right); } +.#{$fa-css-prefix}-long-arrow-alt-up:before { content: fa-content($fa-var-long-arrow-alt-up); } +.#{$fa-css-prefix}-low-vision:before { content: fa-content($fa-var-low-vision); } +.#{$fa-css-prefix}-lyft:before { content: fa-content($fa-var-lyft); } +.#{$fa-css-prefix}-magento:before { content: fa-content($fa-var-magento); } +.#{$fa-css-prefix}-magic:before { content: fa-content($fa-var-magic); } +.#{$fa-css-prefix}-magnet:before { content: fa-content($fa-var-magnet); } +.#{$fa-css-prefix}-male:before { content: fa-content($fa-var-male); } +.#{$fa-css-prefix}-map:before { content: fa-content($fa-var-map); } +.#{$fa-css-prefix}-map-marker:before { content: fa-content($fa-var-map-marker); } +.#{$fa-css-prefix}-map-marker-alt:before { content: fa-content($fa-var-map-marker-alt); } +.#{$fa-css-prefix}-map-pin:before { content: fa-content($fa-var-map-pin); } +.#{$fa-css-prefix}-map-signs:before { content: fa-content($fa-var-map-signs); } +.#{$fa-css-prefix}-mars:before { content: fa-content($fa-var-mars); } +.#{$fa-css-prefix}-mars-double:before { content: fa-content($fa-var-mars-double); } +.#{$fa-css-prefix}-mars-stroke:before { content: fa-content($fa-var-mars-stroke); } +.#{$fa-css-prefix}-mars-stroke-h:before { content: fa-content($fa-var-mars-stroke-h); } +.#{$fa-css-prefix}-mars-stroke-v:before { content: fa-content($fa-var-mars-stroke-v); } +.#{$fa-css-prefix}-maxcdn:before { content: fa-content($fa-var-maxcdn); } +.#{$fa-css-prefix}-medapps:before { content: fa-content($fa-var-medapps); } +.#{$fa-css-prefix}-medium:before { content: fa-content($fa-var-medium); } +.#{$fa-css-prefix}-medium-m:before { content: fa-content($fa-var-medium-m); } +.#{$fa-css-prefix}-medkit:before { content: fa-content($fa-var-medkit); } +.#{$fa-css-prefix}-medrt:before { content: fa-content($fa-var-medrt); } +.#{$fa-css-prefix}-meetup:before { content: fa-content($fa-var-meetup); } +.#{$fa-css-prefix}-meh:before { content: fa-content($fa-var-meh); } +.#{$fa-css-prefix}-mercury:before { content: fa-content($fa-var-mercury); } +.#{$fa-css-prefix}-microchip:before { content: fa-content($fa-var-microchip); } +.#{$fa-css-prefix}-microphone:before { content: fa-content($fa-var-microphone); } +.#{$fa-css-prefix}-microphone-slash:before { content: fa-content($fa-var-microphone-slash); } +.#{$fa-css-prefix}-microsoft:before { content: fa-content($fa-var-microsoft); } +.#{$fa-css-prefix}-minus:before { content: fa-content($fa-var-minus); } +.#{$fa-css-prefix}-minus-circle:before { content: fa-content($fa-var-minus-circle); } +.#{$fa-css-prefix}-minus-square:before { content: fa-content($fa-var-minus-square); } +.#{$fa-css-prefix}-mix:before { content: fa-content($fa-var-mix); } +.#{$fa-css-prefix}-mixcloud:before { content: fa-content($fa-var-mixcloud); } +.#{$fa-css-prefix}-mizuni:before { content: fa-content($fa-var-mizuni); } +.#{$fa-css-prefix}-mobile:before { content: fa-content($fa-var-mobile); } +.#{$fa-css-prefix}-mobile-alt:before { content: fa-content($fa-var-mobile-alt); } +.#{$fa-css-prefix}-modx:before { content: fa-content($fa-var-modx); } +.#{$fa-css-prefix}-monero:before { content: fa-content($fa-var-monero); } +.#{$fa-css-prefix}-money-bill-alt:before { content: fa-content($fa-var-money-bill-alt); } +.#{$fa-css-prefix}-moon:before { content: fa-content($fa-var-moon); } +.#{$fa-css-prefix}-motorcycle:before { content: fa-content($fa-var-motorcycle); } +.#{$fa-css-prefix}-mouse-pointer:before { content: fa-content($fa-var-mouse-pointer); } +.#{$fa-css-prefix}-music:before { content: fa-content($fa-var-music); } +.#{$fa-css-prefix}-napster:before { content: fa-content($fa-var-napster); } +.#{$fa-css-prefix}-neuter:before { content: fa-content($fa-var-neuter); } +.#{$fa-css-prefix}-newspaper:before { content: fa-content($fa-var-newspaper); } +.#{$fa-css-prefix}-nintendo-switch:before { content: fa-content($fa-var-nintendo-switch); } +.#{$fa-css-prefix}-node:before { content: fa-content($fa-var-node); } +.#{$fa-css-prefix}-node-js:before { content: fa-content($fa-var-node-js); } +.#{$fa-css-prefix}-notes-medical:before { content: fa-content($fa-var-notes-medical); } +.#{$fa-css-prefix}-npm:before { content: fa-content($fa-var-npm); } +.#{$fa-css-prefix}-ns8:before { content: fa-content($fa-var-ns8); } +.#{$fa-css-prefix}-nutritionix:before { content: fa-content($fa-var-nutritionix); } +.#{$fa-css-prefix}-object-group:before { content: fa-content($fa-var-object-group); } +.#{$fa-css-prefix}-object-ungroup:before { content: fa-content($fa-var-object-ungroup); } +.#{$fa-css-prefix}-odnoklassniki:before { content: fa-content($fa-var-odnoklassniki); } +.#{$fa-css-prefix}-odnoklassniki-square:before { content: fa-content($fa-var-odnoklassniki-square); } +.#{$fa-css-prefix}-opencart:before { content: fa-content($fa-var-opencart); } +.#{$fa-css-prefix}-openid:before { content: fa-content($fa-var-openid); } +.#{$fa-css-prefix}-opera:before { content: fa-content($fa-var-opera); } +.#{$fa-css-prefix}-optin-monster:before { content: fa-content($fa-var-optin-monster); } +.#{$fa-css-prefix}-osi:before { content: fa-content($fa-var-osi); } +.#{$fa-css-prefix}-outdent:before { content: fa-content($fa-var-outdent); } +.#{$fa-css-prefix}-page4:before { content: fa-content($fa-var-page4); } +.#{$fa-css-prefix}-pagelines:before { content: fa-content($fa-var-pagelines); } +.#{$fa-css-prefix}-paint-brush:before { content: fa-content($fa-var-paint-brush); } +.#{$fa-css-prefix}-palfed:before { content: fa-content($fa-var-palfed); } +.#{$fa-css-prefix}-pallet:before { content: fa-content($fa-var-pallet); } +.#{$fa-css-prefix}-paper-plane:before { content: fa-content($fa-var-paper-plane); } +.#{$fa-css-prefix}-paperclip:before { content: fa-content($fa-var-paperclip); } +.#{$fa-css-prefix}-parachute-box:before { content: fa-content($fa-var-parachute-box); } +.#{$fa-css-prefix}-paragraph:before { content: fa-content($fa-var-paragraph); } +.#{$fa-css-prefix}-paste:before { content: fa-content($fa-var-paste); } +.#{$fa-css-prefix}-patreon:before { content: fa-content($fa-var-patreon); } +.#{$fa-css-prefix}-pause:before { content: fa-content($fa-var-pause); } +.#{$fa-css-prefix}-pause-circle:before { content: fa-content($fa-var-pause-circle); } +.#{$fa-css-prefix}-paw:before { content: fa-content($fa-var-paw); } +.#{$fa-css-prefix}-paypal:before { content: fa-content($fa-var-paypal); } +.#{$fa-css-prefix}-pen-square:before { content: fa-content($fa-var-pen-square); } +.#{$fa-css-prefix}-pencil-alt:before { content: fa-content($fa-var-pencil-alt); } +.#{$fa-css-prefix}-people-carry:before { content: fa-content($fa-var-people-carry); } +.#{$fa-css-prefix}-percent:before { content: fa-content($fa-var-percent); } +.#{$fa-css-prefix}-periscope:before { content: fa-content($fa-var-periscope); } +.#{$fa-css-prefix}-phabricator:before { content: fa-content($fa-var-phabricator); } +.#{$fa-css-prefix}-phoenix-framework:before { content: fa-content($fa-var-phoenix-framework); } +.#{$fa-css-prefix}-phone:before { content: fa-content($fa-var-phone); } +.#{$fa-css-prefix}-phone-slash:before { content: fa-content($fa-var-phone-slash); } +.#{$fa-css-prefix}-phone-square:before { content: fa-content($fa-var-phone-square); } +.#{$fa-css-prefix}-phone-volume:before { content: fa-content($fa-var-phone-volume); } +.#{$fa-css-prefix}-php:before { content: fa-content($fa-var-php); } +.#{$fa-css-prefix}-pied-piper:before { content: fa-content($fa-var-pied-piper); } +.#{$fa-css-prefix}-pied-piper-alt:before { content: fa-content($fa-var-pied-piper-alt); } +.#{$fa-css-prefix}-pied-piper-hat:before { content: fa-content($fa-var-pied-piper-hat); } +.#{$fa-css-prefix}-pied-piper-pp:before { content: fa-content($fa-var-pied-piper-pp); } +.#{$fa-css-prefix}-piggy-bank:before { content: fa-content($fa-var-piggy-bank); } +.#{$fa-css-prefix}-pills:before { content: fa-content($fa-var-pills); } +.#{$fa-css-prefix}-pinterest:before { content: fa-content($fa-var-pinterest); } +.#{$fa-css-prefix}-pinterest-p:before { content: fa-content($fa-var-pinterest-p); } +.#{$fa-css-prefix}-pinterest-square:before { content: fa-content($fa-var-pinterest-square); } +.#{$fa-css-prefix}-plane:before { content: fa-content($fa-var-plane); } +.#{$fa-css-prefix}-play:before { content: fa-content($fa-var-play); } +.#{$fa-css-prefix}-play-circle:before { content: fa-content($fa-var-play-circle); } +.#{$fa-css-prefix}-playstation:before { content: fa-content($fa-var-playstation); } +.#{$fa-css-prefix}-plug:before { content: fa-content($fa-var-plug); } +.#{$fa-css-prefix}-plus:before { content: fa-content($fa-var-plus); } +.#{$fa-css-prefix}-plus-circle:before { content: fa-content($fa-var-plus-circle); } +.#{$fa-css-prefix}-plus-square:before { content: fa-content($fa-var-plus-square); } +.#{$fa-css-prefix}-podcast:before { content: fa-content($fa-var-podcast); } +.#{$fa-css-prefix}-poo:before { content: fa-content($fa-var-poo); } +.#{$fa-css-prefix}-pound-sign:before { content: fa-content($fa-var-pound-sign); } +.#{$fa-css-prefix}-power-off:before { content: fa-content($fa-var-power-off); } +.#{$fa-css-prefix}-prescription-bottle:before { content: fa-content($fa-var-prescription-bottle); } +.#{$fa-css-prefix}-prescription-bottle-alt:before { content: fa-content($fa-var-prescription-bottle-alt); } +.#{$fa-css-prefix}-print:before { content: fa-content($fa-var-print); } +.#{$fa-css-prefix}-procedures:before { content: fa-content($fa-var-procedures); } +.#{$fa-css-prefix}-product-hunt:before { content: fa-content($fa-var-product-hunt); } +.#{$fa-css-prefix}-pushed:before { content: fa-content($fa-var-pushed); } +.#{$fa-css-prefix}-puzzle-piece:before { content: fa-content($fa-var-puzzle-piece); } +.#{$fa-css-prefix}-python:before { content: fa-content($fa-var-python); } +.#{$fa-css-prefix}-qq:before { content: fa-content($fa-var-qq); } +.#{$fa-css-prefix}-qrcode:before { content: fa-content($fa-var-qrcode); } +.#{$fa-css-prefix}-question:before { content: fa-content($fa-var-question); } +.#{$fa-css-prefix}-question-circle:before { content: fa-content($fa-var-question-circle); } +.#{$fa-css-prefix}-quidditch:before { content: fa-content($fa-var-quidditch); } +.#{$fa-css-prefix}-quinscape:before { content: fa-content($fa-var-quinscape); } +.#{$fa-css-prefix}-quora:before { content: fa-content($fa-var-quora); } +.#{$fa-css-prefix}-quote-left:before { content: fa-content($fa-var-quote-left); } +.#{$fa-css-prefix}-quote-right:before { content: fa-content($fa-var-quote-right); } +.#{$fa-css-prefix}-random:before { content: fa-content($fa-var-random); } +.#{$fa-css-prefix}-ravelry:before { content: fa-content($fa-var-ravelry); } +.#{$fa-css-prefix}-react:before { content: fa-content($fa-var-react); } +.#{$fa-css-prefix}-readme:before { content: fa-content($fa-var-readme); } +.#{$fa-css-prefix}-rebel:before { content: fa-content($fa-var-rebel); } +.#{$fa-css-prefix}-recycle:before { content: fa-content($fa-var-recycle); } +.#{$fa-css-prefix}-red-river:before { content: fa-content($fa-var-red-river); } +.#{$fa-css-prefix}-reddit:before { content: fa-content($fa-var-reddit); } +.#{$fa-css-prefix}-reddit-alien:before { content: fa-content($fa-var-reddit-alien); } +.#{$fa-css-prefix}-reddit-square:before { content: fa-content($fa-var-reddit-square); } +.#{$fa-css-prefix}-redo:before { content: fa-content($fa-var-redo); } +.#{$fa-css-prefix}-redo-alt:before { content: fa-content($fa-var-redo-alt); } +.#{$fa-css-prefix}-registered:before { content: fa-content($fa-var-registered); } +.#{$fa-css-prefix}-rendact:before { content: fa-content($fa-var-rendact); } +.#{$fa-css-prefix}-renren:before { content: fa-content($fa-var-renren); } +.#{$fa-css-prefix}-reply:before { content: fa-content($fa-var-reply); } +.#{$fa-css-prefix}-reply-all:before { content: fa-content($fa-var-reply-all); } +.#{$fa-css-prefix}-replyd:before { content: fa-content($fa-var-replyd); } +.#{$fa-css-prefix}-resolving:before { content: fa-content($fa-var-resolving); } +.#{$fa-css-prefix}-retweet:before { content: fa-content($fa-var-retweet); } +.#{$fa-css-prefix}-ribbon:before { content: fa-content($fa-var-ribbon); } +.#{$fa-css-prefix}-road:before { content: fa-content($fa-var-road); } +.#{$fa-css-prefix}-rocket:before { content: fa-content($fa-var-rocket); } +.#{$fa-css-prefix}-rocketchat:before { content: fa-content($fa-var-rocketchat); } +.#{$fa-css-prefix}-rockrms:before { content: fa-content($fa-var-rockrms); } +.#{$fa-css-prefix}-rss:before { content: fa-content($fa-var-rss); } +.#{$fa-css-prefix}-rss-square:before { content: fa-content($fa-var-rss-square); } +.#{$fa-css-prefix}-ruble-sign:before { content: fa-content($fa-var-ruble-sign); } +.#{$fa-css-prefix}-rupee-sign:before { content: fa-content($fa-var-rupee-sign); } +.#{$fa-css-prefix}-safari:before { content: fa-content($fa-var-safari); } +.#{$fa-css-prefix}-sass:before { content: fa-content($fa-var-sass); } +.#{$fa-css-prefix}-save:before { content: fa-content($fa-var-save); } +.#{$fa-css-prefix}-schlix:before { content: fa-content($fa-var-schlix); } +.#{$fa-css-prefix}-scribd:before { content: fa-content($fa-var-scribd); } +.#{$fa-css-prefix}-search:before { content: fa-content($fa-var-search); } +.#{$fa-css-prefix}-search-minus:before { content: fa-content($fa-var-search-minus); } +.#{$fa-css-prefix}-search-plus:before { content: fa-content($fa-var-search-plus); } +.#{$fa-css-prefix}-searchengin:before { content: fa-content($fa-var-searchengin); } +.#{$fa-css-prefix}-seedling:before { content: fa-content($fa-var-seedling); } +.#{$fa-css-prefix}-sellcast:before { content: fa-content($fa-var-sellcast); } +.#{$fa-css-prefix}-sellsy:before { content: fa-content($fa-var-sellsy); } +.#{$fa-css-prefix}-server:before { content: fa-content($fa-var-server); } +.#{$fa-css-prefix}-servicestack:before { content: fa-content($fa-var-servicestack); } +.#{$fa-css-prefix}-share:before { content: fa-content($fa-var-share); } +.#{$fa-css-prefix}-share-alt:before { content: fa-content($fa-var-share-alt); } +.#{$fa-css-prefix}-share-alt-square:before { content: fa-content($fa-var-share-alt-square); } +.#{$fa-css-prefix}-share-square:before { content: fa-content($fa-var-share-square); } +.#{$fa-css-prefix}-shekel-sign:before { content: fa-content($fa-var-shekel-sign); } +.#{$fa-css-prefix}-shield-alt:before { content: fa-content($fa-var-shield-alt); } +.#{$fa-css-prefix}-ship:before { content: fa-content($fa-var-ship); } +.#{$fa-css-prefix}-shipping-fast:before { content: fa-content($fa-var-shipping-fast); } +.#{$fa-css-prefix}-shirtsinbulk:before { content: fa-content($fa-var-shirtsinbulk); } +.#{$fa-css-prefix}-shopping-bag:before { content: fa-content($fa-var-shopping-bag); } +.#{$fa-css-prefix}-shopping-basket:before { content: fa-content($fa-var-shopping-basket); } +.#{$fa-css-prefix}-shopping-cart:before { content: fa-content($fa-var-shopping-cart); } +.#{$fa-css-prefix}-shower:before { content: fa-content($fa-var-shower); } +.#{$fa-css-prefix}-sign:before { content: fa-content($fa-var-sign); } +.#{$fa-css-prefix}-sign-in-alt:before { content: fa-content($fa-var-sign-in-alt); } +.#{$fa-css-prefix}-sign-language:before { content: fa-content($fa-var-sign-language); } +.#{$fa-css-prefix}-sign-out-alt:before { content: fa-content($fa-var-sign-out-alt); } +.#{$fa-css-prefix}-signal:before { content: fa-content($fa-var-signal); } +.#{$fa-css-prefix}-simplybuilt:before { content: fa-content($fa-var-simplybuilt); } +.#{$fa-css-prefix}-sistrix:before { content: fa-content($fa-var-sistrix); } +.#{$fa-css-prefix}-sitemap:before { content: fa-content($fa-var-sitemap); } +.#{$fa-css-prefix}-skyatlas:before { content: fa-content($fa-var-skyatlas); } +.#{$fa-css-prefix}-skype:before { content: fa-content($fa-var-skype); } +.#{$fa-css-prefix}-slack:before { content: fa-content($fa-var-slack); } +.#{$fa-css-prefix}-slack-hash:before { content: fa-content($fa-var-slack-hash); } +.#{$fa-css-prefix}-sliders-h:before { content: fa-content($fa-var-sliders-h); } +.#{$fa-css-prefix}-slideshare:before { content: fa-content($fa-var-slideshare); } +.#{$fa-css-prefix}-smile:before { content: fa-content($fa-var-smile); } +.#{$fa-css-prefix}-smoking:before { content: fa-content($fa-var-smoking); } +.#{$fa-css-prefix}-snapchat:before { content: fa-content($fa-var-snapchat); } +.#{$fa-css-prefix}-snapchat-ghost:before { content: fa-content($fa-var-snapchat-ghost); } +.#{$fa-css-prefix}-snapchat-square:before { content: fa-content($fa-var-snapchat-square); } +.#{$fa-css-prefix}-snowflake:before { content: fa-content($fa-var-snowflake); } +.#{$fa-css-prefix}-sort:before { content: fa-content($fa-var-sort); } +.#{$fa-css-prefix}-sort-alpha-down:before { content: fa-content($fa-var-sort-alpha-down); } +.#{$fa-css-prefix}-sort-alpha-up:before { content: fa-content($fa-var-sort-alpha-up); } +.#{$fa-css-prefix}-sort-amount-down:before { content: fa-content($fa-var-sort-amount-down); } +.#{$fa-css-prefix}-sort-amount-up:before { content: fa-content($fa-var-sort-amount-up); } +.#{$fa-css-prefix}-sort-down:before { content: fa-content($fa-var-sort-down); } +.#{$fa-css-prefix}-sort-numeric-down:before { content: fa-content($fa-var-sort-numeric-down); } +.#{$fa-css-prefix}-sort-numeric-up:before { content: fa-content($fa-var-sort-numeric-up); } +.#{$fa-css-prefix}-sort-up:before { content: fa-content($fa-var-sort-up); } +.#{$fa-css-prefix}-soundcloud:before { content: fa-content($fa-var-soundcloud); } +.#{$fa-css-prefix}-space-shuttle:before { content: fa-content($fa-var-space-shuttle); } +.#{$fa-css-prefix}-speakap:before { content: fa-content($fa-var-speakap); } +.#{$fa-css-prefix}-spinner:before { content: fa-content($fa-var-spinner); } +.#{$fa-css-prefix}-spotify:before { content: fa-content($fa-var-spotify); } +.#{$fa-css-prefix}-square:before { content: fa-content($fa-var-square); } +.#{$fa-css-prefix}-square-full:before { content: fa-content($fa-var-square-full); } +.#{$fa-css-prefix}-stack-exchange:before { content: fa-content($fa-var-stack-exchange); } +.#{$fa-css-prefix}-stack-overflow:before { content: fa-content($fa-var-stack-overflow); } +.#{$fa-css-prefix}-star:before { content: fa-content($fa-var-star); } +.#{$fa-css-prefix}-star-half:before { content: fa-content($fa-var-star-half); } +.#{$fa-css-prefix}-staylinked:before { content: fa-content($fa-var-staylinked); } +.#{$fa-css-prefix}-steam:before { content: fa-content($fa-var-steam); } +.#{$fa-css-prefix}-steam-square:before { content: fa-content($fa-var-steam-square); } +.#{$fa-css-prefix}-steam-symbol:before { content: fa-content($fa-var-steam-symbol); } +.#{$fa-css-prefix}-step-backward:before { content: fa-content($fa-var-step-backward); } +.#{$fa-css-prefix}-step-forward:before { content: fa-content($fa-var-step-forward); } +.#{$fa-css-prefix}-stethoscope:before { content: fa-content($fa-var-stethoscope); } +.#{$fa-css-prefix}-sticker-mule:before { content: fa-content($fa-var-sticker-mule); } +.#{$fa-css-prefix}-sticky-note:before { content: fa-content($fa-var-sticky-note); } +.#{$fa-css-prefix}-stop:before { content: fa-content($fa-var-stop); } +.#{$fa-css-prefix}-stop-circle:before { content: fa-content($fa-var-stop-circle); } +.#{$fa-css-prefix}-stopwatch:before { content: fa-content($fa-var-stopwatch); } +.#{$fa-css-prefix}-strava:before { content: fa-content($fa-var-strava); } +.#{$fa-css-prefix}-street-view:before { content: fa-content($fa-var-street-view); } +.#{$fa-css-prefix}-strikethrough:before { content: fa-content($fa-var-strikethrough); } +.#{$fa-css-prefix}-stripe:before { content: fa-content($fa-var-stripe); } +.#{$fa-css-prefix}-stripe-s:before { content: fa-content($fa-var-stripe-s); } +.#{$fa-css-prefix}-studiovinari:before { content: fa-content($fa-var-studiovinari); } +.#{$fa-css-prefix}-stumbleupon:before { content: fa-content($fa-var-stumbleupon); } +.#{$fa-css-prefix}-stumbleupon-circle:before { content: fa-content($fa-var-stumbleupon-circle); } +.#{$fa-css-prefix}-subscript:before { content: fa-content($fa-var-subscript); } +.#{$fa-css-prefix}-subway:before { content: fa-content($fa-var-subway); } +.#{$fa-css-prefix}-suitcase:before { content: fa-content($fa-var-suitcase); } +.#{$fa-css-prefix}-sun:before { content: fa-content($fa-var-sun); } +.#{$fa-css-prefix}-superpowers:before { content: fa-content($fa-var-superpowers); } +.#{$fa-css-prefix}-superscript:before { content: fa-content($fa-var-superscript); } +.#{$fa-css-prefix}-supple:before { content: fa-content($fa-var-supple); } +.#{$fa-css-prefix}-sync:before { content: fa-content($fa-var-sync); } +.#{$fa-css-prefix}-sync-alt:before { content: fa-content($fa-var-sync-alt); } +.#{$fa-css-prefix}-syringe:before { content: fa-content($fa-var-syringe); } +.#{$fa-css-prefix}-table:before { content: fa-content($fa-var-table); } +.#{$fa-css-prefix}-table-tennis:before { content: fa-content($fa-var-table-tennis); } +.#{$fa-css-prefix}-tablet:before { content: fa-content($fa-var-tablet); } +.#{$fa-css-prefix}-tablet-alt:before { content: fa-content($fa-var-tablet-alt); } +.#{$fa-css-prefix}-tablets:before { content: fa-content($fa-var-tablets); } +.#{$fa-css-prefix}-tachometer-alt:before { content: fa-content($fa-var-tachometer-alt); } +.#{$fa-css-prefix}-tag:before { content: fa-content($fa-var-tag); } +.#{$fa-css-prefix}-tags:before { content: fa-content($fa-var-tags); } +.#{$fa-css-prefix}-tape:before { content: fa-content($fa-var-tape); } +.#{$fa-css-prefix}-tasks:before { content: fa-content($fa-var-tasks); } +.#{$fa-css-prefix}-taxi:before { content: fa-content($fa-var-taxi); } +.#{$fa-css-prefix}-telegram:before { content: fa-content($fa-var-telegram); } +.#{$fa-css-prefix}-telegram-plane:before { content: fa-content($fa-var-telegram-plane); } +.#{$fa-css-prefix}-tencent-weibo:before { content: fa-content($fa-var-tencent-weibo); } +.#{$fa-css-prefix}-terminal:before { content: fa-content($fa-var-terminal); } +.#{$fa-css-prefix}-text-height:before { content: fa-content($fa-var-text-height); } +.#{$fa-css-prefix}-text-width:before { content: fa-content($fa-var-text-width); } +.#{$fa-css-prefix}-th:before { content: fa-content($fa-var-th); } +.#{$fa-css-prefix}-th-large:before { content: fa-content($fa-var-th-large); } +.#{$fa-css-prefix}-th-list:before { content: fa-content($fa-var-th-list); } +.#{$fa-css-prefix}-themeisle:before { content: fa-content($fa-var-themeisle); } +.#{$fa-css-prefix}-thermometer:before { content: fa-content($fa-var-thermometer); } +.#{$fa-css-prefix}-thermometer-empty:before { content: fa-content($fa-var-thermometer-empty); } +.#{$fa-css-prefix}-thermometer-full:before { content: fa-content($fa-var-thermometer-full); } +.#{$fa-css-prefix}-thermometer-half:before { content: fa-content($fa-var-thermometer-half); } +.#{$fa-css-prefix}-thermometer-quarter:before { content: fa-content($fa-var-thermometer-quarter); } +.#{$fa-css-prefix}-thermometer-three-quarters:before { content: fa-content($fa-var-thermometer-three-quarters); } +.#{$fa-css-prefix}-thumbs-down:before { content: fa-content($fa-var-thumbs-down); } +.#{$fa-css-prefix}-thumbs-up:before { content: fa-content($fa-var-thumbs-up); } +.#{$fa-css-prefix}-thumbtack:before { content: fa-content($fa-var-thumbtack); } +.#{$fa-css-prefix}-ticket-alt:before { content: fa-content($fa-var-ticket-alt); } +.#{$fa-css-prefix}-times:before { content: fa-content($fa-var-times); } +.#{$fa-css-prefix}-times-circle:before { content: fa-content($fa-var-times-circle); } +.#{$fa-css-prefix}-tint:before { content: fa-content($fa-var-tint); } +.#{$fa-css-prefix}-toggle-off:before { content: fa-content($fa-var-toggle-off); } +.#{$fa-css-prefix}-toggle-on:before { content: fa-content($fa-var-toggle-on); } +.#{$fa-css-prefix}-trademark:before { content: fa-content($fa-var-trademark); } +.#{$fa-css-prefix}-train:before { content: fa-content($fa-var-train); } +.#{$fa-css-prefix}-transgender:before { content: fa-content($fa-var-transgender); } +.#{$fa-css-prefix}-transgender-alt:before { content: fa-content($fa-var-transgender-alt); } +.#{$fa-css-prefix}-trash:before { content: fa-content($fa-var-trash); } +.#{$fa-css-prefix}-trash-alt:before { content: fa-content($fa-var-trash-alt); } +.#{$fa-css-prefix}-tree:before { content: fa-content($fa-var-tree); } +.#{$fa-css-prefix}-trello:before { content: fa-content($fa-var-trello); } +.#{$fa-css-prefix}-tripadvisor:before { content: fa-content($fa-var-tripadvisor); } +.#{$fa-css-prefix}-trophy:before { content: fa-content($fa-var-trophy); } +.#{$fa-css-prefix}-truck:before { content: fa-content($fa-var-truck); } +.#{$fa-css-prefix}-truck-loading:before { content: fa-content($fa-var-truck-loading); } +.#{$fa-css-prefix}-truck-moving:before { content: fa-content($fa-var-truck-moving); } +.#{$fa-css-prefix}-tty:before { content: fa-content($fa-var-tty); } +.#{$fa-css-prefix}-tumblr:before { content: fa-content($fa-var-tumblr); } +.#{$fa-css-prefix}-tumblr-square:before { content: fa-content($fa-var-tumblr-square); } +.#{$fa-css-prefix}-tv:before { content: fa-content($fa-var-tv); } +.#{$fa-css-prefix}-twitch:before { content: fa-content($fa-var-twitch); } +.#{$fa-css-prefix}-twitter:before { content: fa-content($fa-var-twitter); } +.#{$fa-css-prefix}-twitter-square:before { content: fa-content($fa-var-twitter-square); } +.#{$fa-css-prefix}-typo3:before { content: fa-content($fa-var-typo3); } +.#{$fa-css-prefix}-uber:before { content: fa-content($fa-var-uber); } +.#{$fa-css-prefix}-uikit:before { content: fa-content($fa-var-uikit); } +.#{$fa-css-prefix}-umbrella:before { content: fa-content($fa-var-umbrella); } +.#{$fa-css-prefix}-underline:before { content: fa-content($fa-var-underline); } +.#{$fa-css-prefix}-undo:before { content: fa-content($fa-var-undo); } +.#{$fa-css-prefix}-undo-alt:before { content: fa-content($fa-var-undo-alt); } +.#{$fa-css-prefix}-uniregistry:before { content: fa-content($fa-var-uniregistry); } +.#{$fa-css-prefix}-universal-access:before { content: fa-content($fa-var-universal-access); } +.#{$fa-css-prefix}-university:before { content: fa-content($fa-var-university); } +.#{$fa-css-prefix}-unlink:before { content: fa-content($fa-var-unlink); } +.#{$fa-css-prefix}-unlock:before { content: fa-content($fa-var-unlock); } +.#{$fa-css-prefix}-unlock-alt:before { content: fa-content($fa-var-unlock-alt); } +.#{$fa-css-prefix}-untappd:before { content: fa-content($fa-var-untappd); } +.#{$fa-css-prefix}-upload:before { content: fa-content($fa-var-upload); } +.#{$fa-css-prefix}-usb:before { content: fa-content($fa-var-usb); } +.#{$fa-css-prefix}-user:before { content: fa-content($fa-var-user); } +.#{$fa-css-prefix}-user-circle:before { content: fa-content($fa-var-user-circle); } +.#{$fa-css-prefix}-user-md:before { content: fa-content($fa-var-user-md); } +.#{$fa-css-prefix}-user-plus:before { content: fa-content($fa-var-user-plus); } +.#{$fa-css-prefix}-user-secret:before { content: fa-content($fa-var-user-secret); } +.#{$fa-css-prefix}-user-times:before { content: fa-content($fa-var-user-times); } +.#{$fa-css-prefix}-users:before { content: fa-content($fa-var-users); } +.#{$fa-css-prefix}-ussunnah:before { content: fa-content($fa-var-ussunnah); } +.#{$fa-css-prefix}-utensil-spoon:before { content: fa-content($fa-var-utensil-spoon); } +.#{$fa-css-prefix}-utensils:before { content: fa-content($fa-var-utensils); } +.#{$fa-css-prefix}-vaadin:before { content: fa-content($fa-var-vaadin); } +.#{$fa-css-prefix}-venus:before { content: fa-content($fa-var-venus); } +.#{$fa-css-prefix}-venus-double:before { content: fa-content($fa-var-venus-double); } +.#{$fa-css-prefix}-venus-mars:before { content: fa-content($fa-var-venus-mars); } +.#{$fa-css-prefix}-viacoin:before { content: fa-content($fa-var-viacoin); } +.#{$fa-css-prefix}-viadeo:before { content: fa-content($fa-var-viadeo); } +.#{$fa-css-prefix}-viadeo-square:before { content: fa-content($fa-var-viadeo-square); } +.#{$fa-css-prefix}-vial:before { content: fa-content($fa-var-vial); } +.#{$fa-css-prefix}-vials:before { content: fa-content($fa-var-vials); } +.#{$fa-css-prefix}-viber:before { content: fa-content($fa-var-viber); } +.#{$fa-css-prefix}-video:before { content: fa-content($fa-var-video); } +.#{$fa-css-prefix}-video-slash:before { content: fa-content($fa-var-video-slash); } +.#{$fa-css-prefix}-vimeo:before { content: fa-content($fa-var-vimeo); } +.#{$fa-css-prefix}-vimeo-square:before { content: fa-content($fa-var-vimeo-square); } +.#{$fa-css-prefix}-vimeo-v:before { content: fa-content($fa-var-vimeo-v); } +.#{$fa-css-prefix}-vine:before { content: fa-content($fa-var-vine); } +.#{$fa-css-prefix}-vk:before { content: fa-content($fa-var-vk); } +.#{$fa-css-prefix}-vnv:before { content: fa-content($fa-var-vnv); } +.#{$fa-css-prefix}-volleyball-ball:before { content: fa-content($fa-var-volleyball-ball); } +.#{$fa-css-prefix}-volume-down:before { content: fa-content($fa-var-volume-down); } +.#{$fa-css-prefix}-volume-off:before { content: fa-content($fa-var-volume-off); } +.#{$fa-css-prefix}-volume-up:before { content: fa-content($fa-var-volume-up); } +.#{$fa-css-prefix}-vuejs:before { content: fa-content($fa-var-vuejs); } +.#{$fa-css-prefix}-warehouse:before { content: fa-content($fa-var-warehouse); } +.#{$fa-css-prefix}-weibo:before { content: fa-content($fa-var-weibo); } +.#{$fa-css-prefix}-weight:before { content: fa-content($fa-var-weight); } +.#{$fa-css-prefix}-weixin:before { content: fa-content($fa-var-weixin); } +.#{$fa-css-prefix}-whatsapp:before { content: fa-content($fa-var-whatsapp); } +.#{$fa-css-prefix}-whatsapp-square:before { content: fa-content($fa-var-whatsapp-square); } +.#{$fa-css-prefix}-wheelchair:before { content: fa-content($fa-var-wheelchair); } +.#{$fa-css-prefix}-whmcs:before { content: fa-content($fa-var-whmcs); } +.#{$fa-css-prefix}-wifi:before { content: fa-content($fa-var-wifi); } +.#{$fa-css-prefix}-wikipedia-w:before { content: fa-content($fa-var-wikipedia-w); } +.#{$fa-css-prefix}-window-close:before { content: fa-content($fa-var-window-close); } +.#{$fa-css-prefix}-window-maximize:before { content: fa-content($fa-var-window-maximize); } +.#{$fa-css-prefix}-window-minimize:before { content: fa-content($fa-var-window-minimize); } +.#{$fa-css-prefix}-window-restore:before { content: fa-content($fa-var-window-restore); } +.#{$fa-css-prefix}-windows:before { content: fa-content($fa-var-windows); } +.#{$fa-css-prefix}-wine-glass:before { content: fa-content($fa-var-wine-glass); } +.#{$fa-css-prefix}-won-sign:before { content: fa-content($fa-var-won-sign); } +.#{$fa-css-prefix}-wordpress:before { content: fa-content($fa-var-wordpress); } +.#{$fa-css-prefix}-wordpress-simple:before { content: fa-content($fa-var-wordpress-simple); } +.#{$fa-css-prefix}-wpbeginner:before { content: fa-content($fa-var-wpbeginner); } +.#{$fa-css-prefix}-wpexplorer:before { content: fa-content($fa-var-wpexplorer); } +.#{$fa-css-prefix}-wpforms:before { content: fa-content($fa-var-wpforms); } +.#{$fa-css-prefix}-wrench:before { content: fa-content($fa-var-wrench); } +.#{$fa-css-prefix}-x-ray:before { content: fa-content($fa-var-x-ray); } +.#{$fa-css-prefix}-xbox:before { content: fa-content($fa-var-xbox); } +.#{$fa-css-prefix}-xing:before { content: fa-content($fa-var-xing); } +.#{$fa-css-prefix}-xing-square:before { content: fa-content($fa-var-xing-square); } +.#{$fa-css-prefix}-y-combinator:before { content: fa-content($fa-var-y-combinator); } +.#{$fa-css-prefix}-yahoo:before { content: fa-content($fa-var-yahoo); } +.#{$fa-css-prefix}-yandex:before { content: fa-content($fa-var-yandex); } +.#{$fa-css-prefix}-yandex-international:before { content: fa-content($fa-var-yandex-international); } +.#{$fa-css-prefix}-yelp:before { content: fa-content($fa-var-yelp); } +.#{$fa-css-prefix}-yen-sign:before { content: fa-content($fa-var-yen-sign); } +.#{$fa-css-prefix}-yoast:before { content: fa-content($fa-var-yoast); } +.#{$fa-css-prefix}-youtube:before { content: fa-content($fa-var-youtube); } +.#{$fa-css-prefix}-youtube-square:before { content: fa-content($fa-var-youtube-square); } diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_larger.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_larger.scss new file mode 100644 index 000000000000..27c2ad5fc452 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_larger.scss @@ -0,0 +1,23 @@ +// Icon Sizes +// ------------------------- + +// makes the font 33% larger relative to the icon container +.#{$fa-css-prefix}-lg { + font-size: (4em / 3); + line-height: (3em / 4); + vertical-align: -.0667em; +} + +.#{$fa-css-prefix}-xs { + font-size: .75em; +} + +.#{$fa-css-prefix}-sm { + font-size: .875em; +} + +@for $i from 1 through 10 { + .#{$fa-css-prefix}-#{$i}x { + font-size: $i * 1em; + } +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_list.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_list.scss new file mode 100644 index 000000000000..8ebf33333cfd --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_list.scss @@ -0,0 +1,18 @@ +// List Icons +// ------------------------- + +.#{$fa-css-prefix}-ul { + list-style-type: none; + margin-left: $fa-li-width * 5/4; + padding-left: 0; + + > li { position: relative; } +} + +.#{$fa-css-prefix}-li { + left: -$fa-li-width; + position: absolute; + text-align: center; + width: $fa-li-width; + line-height: inherit; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_mixins.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_mixins.scss new file mode 100644 index 000000000000..50a2e9f18c97 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_mixins.scss @@ -0,0 +1,57 @@ +// Mixins +// -------------------------- + +@mixin fa-icon { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + display: inline-block; + font-style: normal; + font-variant: normal; + font-weight: normal; + line-height: 1; + vertical-align: -.125em; +} + +@mixin fa-icon-rotate($degrees, $rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; + transform: rotate($degrees); +} + +@mixin fa-icon-flip($horiz, $vert, $rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; + transform: scale($horiz, $vert); +} + + +// Only display content to screen readers. A la Bootstrap 4. +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +@mixin sr-only { + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +@mixin sr-only-focusable { + &:active, + &:focus { + clip: auto; + height: auto; + margin: 0; + overflow: visible; + position: static; + width: auto; + } +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_rotated-flipped.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_rotated-flipped.scss new file mode 100644 index 000000000000..995bc4cc70da --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_rotated-flipped.scss @@ -0,0 +1,23 @@ +// Rotated & Flipped Icons +// ------------------------- + +.#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } +.#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } +.#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } + +.#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } +.#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } +.#{$fa-css-prefix}-flip-horizontal.#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(-1, -1, 2); } + +// Hook for IE8-9 +// ------------------------- + +:root { + .#{$fa-css-prefix}-rotate-90, + .#{$fa-css-prefix}-rotate-180, + .#{$fa-css-prefix}-rotate-270, + .#{$fa-css-prefix}-flip-horizontal, + .#{$fa-css-prefix}-flip-vertical { + filter: none; + } +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_screen-reader.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_screen-reader.scss new file mode 100644 index 000000000000..5d0ab262f15f --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_screen-reader.scss @@ -0,0 +1,5 @@ +// Screen Readers +// ------------------------- + +.sr-only { @include sr-only; } +.sr-only-focusable { @include sr-only-focusable; } diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_stacked.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_stacked.scss new file mode 100644 index 000000000000..6c09d84cd11f --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_stacked.scss @@ -0,0 +1,31 @@ +// Stacked Icons +// ------------------------- + +.#{$fa-css-prefix}-stack { + display: inline-block; + height: 2em; + line-height: 2em; + position: relative; + vertical-align: middle; + width: 2em; +} + +.#{$fa-css-prefix}-stack-1x, +.#{$fa-css-prefix}-stack-2x { + left: 0; + position: absolute; + text-align: center; + width: 100%; +} + +.#{$fa-css-prefix}-stack-1x { + line-height: inherit; +} + +.#{$fa-css-prefix}-stack-2x { + font-size: 2em; +} + +.#{$fa-css-prefix}-inverse { + color: $fa-inverse; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_variables.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_variables.scss new file mode 100644 index 000000000000..25cc62d16a5b --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/_variables.scss @@ -0,0 +1,891 @@ +// Variables +// -------------------------- + +$fa-font-path: "../webfonts" !default; +$fa-font-size-base: 16px !default; +$fa-css-prefix: fa !default; +$fa-version: "5.0.10" !default; +$fa-border-color: #eee !default; +$fa-inverse: #fff !default; +$fa-li-width: 2em !default; + +// Convenience function used to set content property +@function fa-content($fa-var) { + @return unquote("\"#{ $fa-var }\""); +} + +$fa-var-500px: \f26e; +$fa-var-accessible-icon: \f368; +$fa-var-accusoft: \f369; +$fa-var-address-book: \f2b9; +$fa-var-address-card: \f2bb; +$fa-var-adjust: \f042; +$fa-var-adn: \f170; +$fa-var-adversal: \f36a; +$fa-var-affiliatetheme: \f36b; +$fa-var-algolia: \f36c; +$fa-var-align-center: \f037; +$fa-var-align-justify: \f039; +$fa-var-align-left: \f036; +$fa-var-align-right: \f038; +$fa-var-allergies: \f461; +$fa-var-amazon: \f270; +$fa-var-amazon-pay: \f42c; +$fa-var-ambulance: \f0f9; +$fa-var-american-sign-language-interpreting: \f2a3; +$fa-var-amilia: \f36d; +$fa-var-anchor: \f13d; +$fa-var-android: \f17b; +$fa-var-angellist: \f209; +$fa-var-angle-double-down: \f103; +$fa-var-angle-double-left: \f100; +$fa-var-angle-double-right: \f101; +$fa-var-angle-double-up: \f102; +$fa-var-angle-down: \f107; +$fa-var-angle-left: \f104; +$fa-var-angle-right: \f105; +$fa-var-angle-up: \f106; +$fa-var-angrycreative: \f36e; +$fa-var-angular: \f420; +$fa-var-app-store: \f36f; +$fa-var-app-store-ios: \f370; +$fa-var-apper: \f371; +$fa-var-apple: \f179; +$fa-var-apple-pay: \f415; +$fa-var-archive: \f187; +$fa-var-arrow-alt-circle-down: \f358; +$fa-var-arrow-alt-circle-left: \f359; +$fa-var-arrow-alt-circle-right: \f35a; +$fa-var-arrow-alt-circle-up: \f35b; +$fa-var-arrow-circle-down: \f0ab; +$fa-var-arrow-circle-left: \f0a8; +$fa-var-arrow-circle-right: \f0a9; +$fa-var-arrow-circle-up: \f0aa; +$fa-var-arrow-down: \f063; +$fa-var-arrow-left: \f060; +$fa-var-arrow-right: \f061; +$fa-var-arrow-up: \f062; +$fa-var-arrows-alt: \f0b2; +$fa-var-arrows-alt-h: \f337; +$fa-var-arrows-alt-v: \f338; +$fa-var-assistive-listening-systems: \f2a2; +$fa-var-asterisk: \f069; +$fa-var-asymmetrik: \f372; +$fa-var-at: \f1fa; +$fa-var-audible: \f373; +$fa-var-audio-description: \f29e; +$fa-var-autoprefixer: \f41c; +$fa-var-avianex: \f374; +$fa-var-aviato: \f421; +$fa-var-aws: \f375; +$fa-var-backward: \f04a; +$fa-var-balance-scale: \f24e; +$fa-var-ban: \f05e; +$fa-var-band-aid: \f462; +$fa-var-bandcamp: \f2d5; +$fa-var-barcode: \f02a; +$fa-var-bars: \f0c9; +$fa-var-baseball-ball: \f433; +$fa-var-basketball-ball: \f434; +$fa-var-bath: \f2cd; +$fa-var-battery-empty: \f244; +$fa-var-battery-full: \f240; +$fa-var-battery-half: \f242; +$fa-var-battery-quarter: \f243; +$fa-var-battery-three-quarters: \f241; +$fa-var-bed: \f236; +$fa-var-beer: \f0fc; +$fa-var-behance: \f1b4; +$fa-var-behance-square: \f1b5; +$fa-var-bell: \f0f3; +$fa-var-bell-slash: \f1f6; +$fa-var-bicycle: \f206; +$fa-var-bimobject: \f378; +$fa-var-binoculars: \f1e5; +$fa-var-birthday-cake: \f1fd; +$fa-var-bitbucket: \f171; +$fa-var-bitcoin: \f379; +$fa-var-bity: \f37a; +$fa-var-black-tie: \f27e; +$fa-var-blackberry: \f37b; +$fa-var-blind: \f29d; +$fa-var-blogger: \f37c; +$fa-var-blogger-b: \f37d; +$fa-var-bluetooth: \f293; +$fa-var-bluetooth-b: \f294; +$fa-var-bold: \f032; +$fa-var-bolt: \f0e7; +$fa-var-bomb: \f1e2; +$fa-var-book: \f02d; +$fa-var-bookmark: \f02e; +$fa-var-bowling-ball: \f436; +$fa-var-box: \f466; +$fa-var-box-open: \f49e; +$fa-var-boxes: \f468; +$fa-var-braille: \f2a1; +$fa-var-briefcase: \f0b1; +$fa-var-briefcase-medical: \f469; +$fa-var-btc: \f15a; +$fa-var-bug: \f188; +$fa-var-building: \f1ad; +$fa-var-bullhorn: \f0a1; +$fa-var-bullseye: \f140; +$fa-var-burn: \f46a; +$fa-var-buromobelexperte: \f37f; +$fa-var-bus: \f207; +$fa-var-buysellads: \f20d; +$fa-var-calculator: \f1ec; +$fa-var-calendar: \f133; +$fa-var-calendar-alt: \f073; +$fa-var-calendar-check: \f274; +$fa-var-calendar-minus: \f272; +$fa-var-calendar-plus: \f271; +$fa-var-calendar-times: \f273; +$fa-var-camera: \f030; +$fa-var-camera-retro: \f083; +$fa-var-capsules: \f46b; +$fa-var-car: \f1b9; +$fa-var-caret-down: \f0d7; +$fa-var-caret-left: \f0d9; +$fa-var-caret-right: \f0da; +$fa-var-caret-square-down: \f150; +$fa-var-caret-square-left: \f191; +$fa-var-caret-square-right: \f152; +$fa-var-caret-square-up: \f151; +$fa-var-caret-up: \f0d8; +$fa-var-cart-arrow-down: \f218; +$fa-var-cart-plus: \f217; +$fa-var-cc-amazon-pay: \f42d; +$fa-var-cc-amex: \f1f3; +$fa-var-cc-apple-pay: \f416; +$fa-var-cc-diners-club: \f24c; +$fa-var-cc-discover: \f1f2; +$fa-var-cc-jcb: \f24b; +$fa-var-cc-mastercard: \f1f1; +$fa-var-cc-paypal: \f1f4; +$fa-var-cc-stripe: \f1f5; +$fa-var-cc-visa: \f1f0; +$fa-var-centercode: \f380; +$fa-var-certificate: \f0a3; +$fa-var-chart-area: \f1fe; +$fa-var-chart-bar: \f080; +$fa-var-chart-line: \f201; +$fa-var-chart-pie: \f200; +$fa-var-check: \f00c; +$fa-var-check-circle: \f058; +$fa-var-check-square: \f14a; +$fa-var-chess: \f439; +$fa-var-chess-bishop: \f43a; +$fa-var-chess-board: \f43c; +$fa-var-chess-king: \f43f; +$fa-var-chess-knight: \f441; +$fa-var-chess-pawn: \f443; +$fa-var-chess-queen: \f445; +$fa-var-chess-rook: \f447; +$fa-var-chevron-circle-down: \f13a; +$fa-var-chevron-circle-left: \f137; +$fa-var-chevron-circle-right: \f138; +$fa-var-chevron-circle-up: \f139; +$fa-var-chevron-down: \f078; +$fa-var-chevron-left: \f053; +$fa-var-chevron-right: \f054; +$fa-var-chevron-up: \f077; +$fa-var-child: \f1ae; +$fa-var-chrome: \f268; +$fa-var-circle: \f111; +$fa-var-circle-notch: \f1ce; +$fa-var-clipboard: \f328; +$fa-var-clipboard-check: \f46c; +$fa-var-clipboard-list: \f46d; +$fa-var-clock: \f017; +$fa-var-clone: \f24d; +$fa-var-closed-captioning: \f20a; +$fa-var-cloud: \f0c2; +$fa-var-cloud-download-alt: \f381; +$fa-var-cloud-upload-alt: \f382; +$fa-var-cloudscale: \f383; +$fa-var-cloudsmith: \f384; +$fa-var-cloudversify: \f385; +$fa-var-code: \f121; +$fa-var-code-branch: \f126; +$fa-var-codepen: \f1cb; +$fa-var-codiepie: \f284; +$fa-var-coffee: \f0f4; +$fa-var-cog: \f013; +$fa-var-cogs: \f085; +$fa-var-columns: \f0db; +$fa-var-comment: \f075; +$fa-var-comment-alt: \f27a; +$fa-var-comment-dots: \f4ad; +$fa-var-comment-slash: \f4b3; +$fa-var-comments: \f086; +$fa-var-compass: \f14e; +$fa-var-compress: \f066; +$fa-var-connectdevelop: \f20e; +$fa-var-contao: \f26d; +$fa-var-copy: \f0c5; +$fa-var-copyright: \f1f9; +$fa-var-couch: \f4b8; +$fa-var-cpanel: \f388; +$fa-var-creative-commons: \f25e; +$fa-var-credit-card: \f09d; +$fa-var-crop: \f125; +$fa-var-crosshairs: \f05b; +$fa-var-css3: \f13c; +$fa-var-css3-alt: \f38b; +$fa-var-cube: \f1b2; +$fa-var-cubes: \f1b3; +$fa-var-cut: \f0c4; +$fa-var-cuttlefish: \f38c; +$fa-var-d-and-d: \f38d; +$fa-var-dashcube: \f210; +$fa-var-database: \f1c0; +$fa-var-deaf: \f2a4; +$fa-var-delicious: \f1a5; +$fa-var-deploydog: \f38e; +$fa-var-deskpro: \f38f; +$fa-var-desktop: \f108; +$fa-var-deviantart: \f1bd; +$fa-var-diagnoses: \f470; +$fa-var-digg: \f1a6; +$fa-var-digital-ocean: \f391; +$fa-var-discord: \f392; +$fa-var-discourse: \f393; +$fa-var-dna: \f471; +$fa-var-dochub: \f394; +$fa-var-docker: \f395; +$fa-var-dollar-sign: \f155; +$fa-var-dolly: \f472; +$fa-var-dolly-flatbed: \f474; +$fa-var-donate: \f4b9; +$fa-var-dot-circle: \f192; +$fa-var-dove: \f4ba; +$fa-var-download: \f019; +$fa-var-draft2digital: \f396; +$fa-var-dribbble: \f17d; +$fa-var-dribbble-square: \f397; +$fa-var-dropbox: \f16b; +$fa-var-drupal: \f1a9; +$fa-var-dyalog: \f399; +$fa-var-earlybirds: \f39a; +$fa-var-edge: \f282; +$fa-var-edit: \f044; +$fa-var-eject: \f052; +$fa-var-elementor: \f430; +$fa-var-ellipsis-h: \f141; +$fa-var-ellipsis-v: \f142; +$fa-var-ember: \f423; +$fa-var-empire: \f1d1; +$fa-var-envelope: \f0e0; +$fa-var-envelope-open: \f2b6; +$fa-var-envelope-square: \f199; +$fa-var-envira: \f299; +$fa-var-eraser: \f12d; +$fa-var-erlang: \f39d; +$fa-var-ethereum: \f42e; +$fa-var-etsy: \f2d7; +$fa-var-euro-sign: \f153; +$fa-var-exchange-alt: \f362; +$fa-var-exclamation: \f12a; +$fa-var-exclamation-circle: \f06a; +$fa-var-exclamation-triangle: \f071; +$fa-var-expand: \f065; +$fa-var-expand-arrows-alt: \f31e; +$fa-var-expeditedssl: \f23e; +$fa-var-external-link-alt: \f35d; +$fa-var-external-link-square-alt: \f360; +$fa-var-eye: \f06e; +$fa-var-eye-dropper: \f1fb; +$fa-var-eye-slash: \f070; +$fa-var-facebook: \f09a; +$fa-var-facebook-f: \f39e; +$fa-var-facebook-messenger: \f39f; +$fa-var-facebook-square: \f082; +$fa-var-fast-backward: \f049; +$fa-var-fast-forward: \f050; +$fa-var-fax: \f1ac; +$fa-var-female: \f182; +$fa-var-fighter-jet: \f0fb; +$fa-var-file: \f15b; +$fa-var-file-alt: \f15c; +$fa-var-file-archive: \f1c6; +$fa-var-file-audio: \f1c7; +$fa-var-file-code: \f1c9; +$fa-var-file-excel: \f1c3; +$fa-var-file-image: \f1c5; +$fa-var-file-medical: \f477; +$fa-var-file-medical-alt: \f478; +$fa-var-file-pdf: \f1c1; +$fa-var-file-powerpoint: \f1c4; +$fa-var-file-video: \f1c8; +$fa-var-file-word: \f1c2; +$fa-var-film: \f008; +$fa-var-filter: \f0b0; +$fa-var-fire: \f06d; +$fa-var-fire-extinguisher: \f134; +$fa-var-firefox: \f269; +$fa-var-first-aid: \f479; +$fa-var-first-order: \f2b0; +$fa-var-firstdraft: \f3a1; +$fa-var-flag: \f024; +$fa-var-flag-checkered: \f11e; +$fa-var-flask: \f0c3; +$fa-var-flickr: \f16e; +$fa-var-flipboard: \f44d; +$fa-var-fly: \f417; +$fa-var-folder: \f07b; +$fa-var-folder-open: \f07c; +$fa-var-font: \f031; +$fa-var-font-awesome: \f2b4; +$fa-var-font-awesome-alt: \f35c; +$fa-var-font-awesome-flag: \f425; +$fa-var-fonticons: \f280; +$fa-var-fonticons-fi: \f3a2; +$fa-var-football-ball: \f44e; +$fa-var-fort-awesome: \f286; +$fa-var-fort-awesome-alt: \f3a3; +$fa-var-forumbee: \f211; +$fa-var-forward: \f04e; +$fa-var-foursquare: \f180; +$fa-var-free-code-camp: \f2c5; +$fa-var-freebsd: \f3a4; +$fa-var-frown: \f119; +$fa-var-futbol: \f1e3; +$fa-var-gamepad: \f11b; +$fa-var-gavel: \f0e3; +$fa-var-gem: \f3a5; +$fa-var-genderless: \f22d; +$fa-var-get-pocket: \f265; +$fa-var-gg: \f260; +$fa-var-gg-circle: \f261; +$fa-var-gift: \f06b; +$fa-var-git: \f1d3; +$fa-var-git-square: \f1d2; +$fa-var-github: \f09b; +$fa-var-github-alt: \f113; +$fa-var-github-square: \f092; +$fa-var-gitkraken: \f3a6; +$fa-var-gitlab: \f296; +$fa-var-gitter: \f426; +$fa-var-glass-martini: \f000; +$fa-var-glide: \f2a5; +$fa-var-glide-g: \f2a6; +$fa-var-globe: \f0ac; +$fa-var-gofore: \f3a7; +$fa-var-golf-ball: \f450; +$fa-var-goodreads: \f3a8; +$fa-var-goodreads-g: \f3a9; +$fa-var-google: \f1a0; +$fa-var-google-drive: \f3aa; +$fa-var-google-play: \f3ab; +$fa-var-google-plus: \f2b3; +$fa-var-google-plus-g: \f0d5; +$fa-var-google-plus-square: \f0d4; +$fa-var-google-wallet: \f1ee; +$fa-var-graduation-cap: \f19d; +$fa-var-gratipay: \f184; +$fa-var-grav: \f2d6; +$fa-var-gripfire: \f3ac; +$fa-var-grunt: \f3ad; +$fa-var-gulp: \f3ae; +$fa-var-h-square: \f0fd; +$fa-var-hacker-news: \f1d4; +$fa-var-hacker-news-square: \f3af; +$fa-var-hand-holding: \f4bd; +$fa-var-hand-holding-heart: \f4be; +$fa-var-hand-holding-usd: \f4c0; +$fa-var-hand-lizard: \f258; +$fa-var-hand-paper: \f256; +$fa-var-hand-peace: \f25b; +$fa-var-hand-point-down: \f0a7; +$fa-var-hand-point-left: \f0a5; +$fa-var-hand-point-right: \f0a4; +$fa-var-hand-point-up: \f0a6; +$fa-var-hand-pointer: \f25a; +$fa-var-hand-rock: \f255; +$fa-var-hand-scissors: \f257; +$fa-var-hand-spock: \f259; +$fa-var-hands: \f4c2; +$fa-var-hands-helping: \f4c4; +$fa-var-handshake: \f2b5; +$fa-var-hashtag: \f292; +$fa-var-hdd: \f0a0; +$fa-var-heading: \f1dc; +$fa-var-headphones: \f025; +$fa-var-heart: \f004; +$fa-var-heartbeat: \f21e; +$fa-var-hips: \f452; +$fa-var-hire-a-helper: \f3b0; +$fa-var-history: \f1da; +$fa-var-hockey-puck: \f453; +$fa-var-home: \f015; +$fa-var-hooli: \f427; +$fa-var-hospital: \f0f8; +$fa-var-hospital-alt: \f47d; +$fa-var-hospital-symbol: \f47e; +$fa-var-hotjar: \f3b1; +$fa-var-hourglass: \f254; +$fa-var-hourglass-end: \f253; +$fa-var-hourglass-half: \f252; +$fa-var-hourglass-start: \f251; +$fa-var-houzz: \f27c; +$fa-var-html5: \f13b; +$fa-var-hubspot: \f3b2; +$fa-var-i-cursor: \f246; +$fa-var-id-badge: \f2c1; +$fa-var-id-card: \f2c2; +$fa-var-id-card-alt: \f47f; +$fa-var-image: \f03e; +$fa-var-images: \f302; +$fa-var-imdb: \f2d8; +$fa-var-inbox: \f01c; +$fa-var-indent: \f03c; +$fa-var-industry: \f275; +$fa-var-info: \f129; +$fa-var-info-circle: \f05a; +$fa-var-instagram: \f16d; +$fa-var-internet-explorer: \f26b; +$fa-var-ioxhost: \f208; +$fa-var-italic: \f033; +$fa-var-itunes: \f3b4; +$fa-var-itunes-note: \f3b5; +$fa-var-java: \f4e4; +$fa-var-jenkins: \f3b6; +$fa-var-joget: \f3b7; +$fa-var-joomla: \f1aa; +$fa-var-js: \f3b8; +$fa-var-js-square: \f3b9; +$fa-var-jsfiddle: \f1cc; +$fa-var-key: \f084; +$fa-var-keyboard: \f11c; +$fa-var-keycdn: \f3ba; +$fa-var-kickstarter: \f3bb; +$fa-var-kickstarter-k: \f3bc; +$fa-var-korvue: \f42f; +$fa-var-language: \f1ab; +$fa-var-laptop: \f109; +$fa-var-laravel: \f3bd; +$fa-var-lastfm: \f202; +$fa-var-lastfm-square: \f203; +$fa-var-leaf: \f06c; +$fa-var-leanpub: \f212; +$fa-var-lemon: \f094; +$fa-var-less: \f41d; +$fa-var-level-down-alt: \f3be; +$fa-var-level-up-alt: \f3bf; +$fa-var-life-ring: \f1cd; +$fa-var-lightbulb: \f0eb; +$fa-var-line: \f3c0; +$fa-var-link: \f0c1; +$fa-var-linkedin: \f08c; +$fa-var-linkedin-in: \f0e1; +$fa-var-linode: \f2b8; +$fa-var-linux: \f17c; +$fa-var-lira-sign: \f195; +$fa-var-list: \f03a; +$fa-var-list-alt: \f022; +$fa-var-list-ol: \f0cb; +$fa-var-list-ul: \f0ca; +$fa-var-location-arrow: \f124; +$fa-var-lock: \f023; +$fa-var-lock-open: \f3c1; +$fa-var-long-arrow-alt-down: \f309; +$fa-var-long-arrow-alt-left: \f30a; +$fa-var-long-arrow-alt-right: \f30b; +$fa-var-long-arrow-alt-up: \f30c; +$fa-var-low-vision: \f2a8; +$fa-var-lyft: \f3c3; +$fa-var-magento: \f3c4; +$fa-var-magic: \f0d0; +$fa-var-magnet: \f076; +$fa-var-male: \f183; +$fa-var-map: \f279; +$fa-var-map-marker: \f041; +$fa-var-map-marker-alt: \f3c5; +$fa-var-map-pin: \f276; +$fa-var-map-signs: \f277; +$fa-var-mars: \f222; +$fa-var-mars-double: \f227; +$fa-var-mars-stroke: \f229; +$fa-var-mars-stroke-h: \f22b; +$fa-var-mars-stroke-v: \f22a; +$fa-var-maxcdn: \f136; +$fa-var-medapps: \f3c6; +$fa-var-medium: \f23a; +$fa-var-medium-m: \f3c7; +$fa-var-medkit: \f0fa; +$fa-var-medrt: \f3c8; +$fa-var-meetup: \f2e0; +$fa-var-meh: \f11a; +$fa-var-mercury: \f223; +$fa-var-microchip: \f2db; +$fa-var-microphone: \f130; +$fa-var-microphone-slash: \f131; +$fa-var-microsoft: \f3ca; +$fa-var-minus: \f068; +$fa-var-minus-circle: \f056; +$fa-var-minus-square: \f146; +$fa-var-mix: \f3cb; +$fa-var-mixcloud: \f289; +$fa-var-mizuni: \f3cc; +$fa-var-mobile: \f10b; +$fa-var-mobile-alt: \f3cd; +$fa-var-modx: \f285; +$fa-var-monero: \f3d0; +$fa-var-money-bill-alt: \f3d1; +$fa-var-moon: \f186; +$fa-var-motorcycle: \f21c; +$fa-var-mouse-pointer: \f245; +$fa-var-music: \f001; +$fa-var-napster: \f3d2; +$fa-var-neuter: \f22c; +$fa-var-newspaper: \f1ea; +$fa-var-nintendo-switch: \f418; +$fa-var-node: \f419; +$fa-var-node-js: \f3d3; +$fa-var-notes-medical: \f481; +$fa-var-npm: \f3d4; +$fa-var-ns8: \f3d5; +$fa-var-nutritionix: \f3d6; +$fa-var-object-group: \f247; +$fa-var-object-ungroup: \f248; +$fa-var-odnoklassniki: \f263; +$fa-var-odnoklassniki-square: \f264; +$fa-var-opencart: \f23d; +$fa-var-openid: \f19b; +$fa-var-opera: \f26a; +$fa-var-optin-monster: \f23c; +$fa-var-osi: \f41a; +$fa-var-outdent: \f03b; +$fa-var-page4: \f3d7; +$fa-var-pagelines: \f18c; +$fa-var-paint-brush: \f1fc; +$fa-var-palfed: \f3d8; +$fa-var-pallet: \f482; +$fa-var-paper-plane: \f1d8; +$fa-var-paperclip: \f0c6; +$fa-var-parachute-box: \f4cd; +$fa-var-paragraph: \f1dd; +$fa-var-paste: \f0ea; +$fa-var-patreon: \f3d9; +$fa-var-pause: \f04c; +$fa-var-pause-circle: \f28b; +$fa-var-paw: \f1b0; +$fa-var-paypal: \f1ed; +$fa-var-pen-square: \f14b; +$fa-var-pencil-alt: \f303; +$fa-var-people-carry: \f4ce; +$fa-var-percent: \f295; +$fa-var-periscope: \f3da; +$fa-var-phabricator: \f3db; +$fa-var-phoenix-framework: \f3dc; +$fa-var-phone: \f095; +$fa-var-phone-slash: \f3dd; +$fa-var-phone-square: \f098; +$fa-var-phone-volume: \f2a0; +$fa-var-php: \f457; +$fa-var-pied-piper: \f2ae; +$fa-var-pied-piper-alt: \f1a8; +$fa-var-pied-piper-hat: \f4e5; +$fa-var-pied-piper-pp: \f1a7; +$fa-var-piggy-bank: \f4d3; +$fa-var-pills: \f484; +$fa-var-pinterest: \f0d2; +$fa-var-pinterest-p: \f231; +$fa-var-pinterest-square: \f0d3; +$fa-var-plane: \f072; +$fa-var-play: \f04b; +$fa-var-play-circle: \f144; +$fa-var-playstation: \f3df; +$fa-var-plug: \f1e6; +$fa-var-plus: \f067; +$fa-var-plus-circle: \f055; +$fa-var-plus-square: \f0fe; +$fa-var-podcast: \f2ce; +$fa-var-poo: \f2fe; +$fa-var-pound-sign: \f154; +$fa-var-power-off: \f011; +$fa-var-prescription-bottle: \f485; +$fa-var-prescription-bottle-alt: \f486; +$fa-var-print: \f02f; +$fa-var-procedures: \f487; +$fa-var-product-hunt: \f288; +$fa-var-pushed: \f3e1; +$fa-var-puzzle-piece: \f12e; +$fa-var-python: \f3e2; +$fa-var-qq: \f1d6; +$fa-var-qrcode: \f029; +$fa-var-question: \f128; +$fa-var-question-circle: \f059; +$fa-var-quidditch: \f458; +$fa-var-quinscape: \f459; +$fa-var-quora: \f2c4; +$fa-var-quote-left: \f10d; +$fa-var-quote-right: \f10e; +$fa-var-random: \f074; +$fa-var-ravelry: \f2d9; +$fa-var-react: \f41b; +$fa-var-readme: \f4d5; +$fa-var-rebel: \f1d0; +$fa-var-recycle: \f1b8; +$fa-var-red-river: \f3e3; +$fa-var-reddit: \f1a1; +$fa-var-reddit-alien: \f281; +$fa-var-reddit-square: \f1a2; +$fa-var-redo: \f01e; +$fa-var-redo-alt: \f2f9; +$fa-var-registered: \f25d; +$fa-var-rendact: \f3e4; +$fa-var-renren: \f18b; +$fa-var-reply: \f3e5; +$fa-var-reply-all: \f122; +$fa-var-replyd: \f3e6; +$fa-var-resolving: \f3e7; +$fa-var-retweet: \f079; +$fa-var-ribbon: \f4d6; +$fa-var-road: \f018; +$fa-var-rocket: \f135; +$fa-var-rocketchat: \f3e8; +$fa-var-rockrms: \f3e9; +$fa-var-rss: \f09e; +$fa-var-rss-square: \f143; +$fa-var-ruble-sign: \f158; +$fa-var-rupee-sign: \f156; +$fa-var-safari: \f267; +$fa-var-sass: \f41e; +$fa-var-save: \f0c7; +$fa-var-schlix: \f3ea; +$fa-var-scribd: \f28a; +$fa-var-search: \f002; +$fa-var-search-minus: \f010; +$fa-var-search-plus: \f00e; +$fa-var-searchengin: \f3eb; +$fa-var-seedling: \f4d8; +$fa-var-sellcast: \f2da; +$fa-var-sellsy: \f213; +$fa-var-server: \f233; +$fa-var-servicestack: \f3ec; +$fa-var-share: \f064; +$fa-var-share-alt: \f1e0; +$fa-var-share-alt-square: \f1e1; +$fa-var-share-square: \f14d; +$fa-var-shekel-sign: \f20b; +$fa-var-shield-alt: \f3ed; +$fa-var-ship: \f21a; +$fa-var-shipping-fast: \f48b; +$fa-var-shirtsinbulk: \f214; +$fa-var-shopping-bag: \f290; +$fa-var-shopping-basket: \f291; +$fa-var-shopping-cart: \f07a; +$fa-var-shower: \f2cc; +$fa-var-sign: \f4d9; +$fa-var-sign-in-alt: \f2f6; +$fa-var-sign-language: \f2a7; +$fa-var-sign-out-alt: \f2f5; +$fa-var-signal: \f012; +$fa-var-simplybuilt: \f215; +$fa-var-sistrix: \f3ee; +$fa-var-sitemap: \f0e8; +$fa-var-skyatlas: \f216; +$fa-var-skype: \f17e; +$fa-var-slack: \f198; +$fa-var-slack-hash: \f3ef; +$fa-var-sliders-h: \f1de; +$fa-var-slideshare: \f1e7; +$fa-var-smile: \f118; +$fa-var-smoking: \f48d; +$fa-var-snapchat: \f2ab; +$fa-var-snapchat-ghost: \f2ac; +$fa-var-snapchat-square: \f2ad; +$fa-var-snowflake: \f2dc; +$fa-var-sort: \f0dc; +$fa-var-sort-alpha-down: \f15d; +$fa-var-sort-alpha-up: \f15e; +$fa-var-sort-amount-down: \f160; +$fa-var-sort-amount-up: \f161; +$fa-var-sort-down: \f0dd; +$fa-var-sort-numeric-down: \f162; +$fa-var-sort-numeric-up: \f163; +$fa-var-sort-up: \f0de; +$fa-var-soundcloud: \f1be; +$fa-var-space-shuttle: \f197; +$fa-var-speakap: \f3f3; +$fa-var-spinner: \f110; +$fa-var-spotify: \f1bc; +$fa-var-square: \f0c8; +$fa-var-square-full: \f45c; +$fa-var-stack-exchange: \f18d; +$fa-var-stack-overflow: \f16c; +$fa-var-star: \f005; +$fa-var-star-half: \f089; +$fa-var-staylinked: \f3f5; +$fa-var-steam: \f1b6; +$fa-var-steam-square: \f1b7; +$fa-var-steam-symbol: \f3f6; +$fa-var-step-backward: \f048; +$fa-var-step-forward: \f051; +$fa-var-stethoscope: \f0f1; +$fa-var-sticker-mule: \f3f7; +$fa-var-sticky-note: \f249; +$fa-var-stop: \f04d; +$fa-var-stop-circle: \f28d; +$fa-var-stopwatch: \f2f2; +$fa-var-strava: \f428; +$fa-var-street-view: \f21d; +$fa-var-strikethrough: \f0cc; +$fa-var-stripe: \f429; +$fa-var-stripe-s: \f42a; +$fa-var-studiovinari: \f3f8; +$fa-var-stumbleupon: \f1a4; +$fa-var-stumbleupon-circle: \f1a3; +$fa-var-subscript: \f12c; +$fa-var-subway: \f239; +$fa-var-suitcase: \f0f2; +$fa-var-sun: \f185; +$fa-var-superpowers: \f2dd; +$fa-var-superscript: \f12b; +$fa-var-supple: \f3f9; +$fa-var-sync: \f021; +$fa-var-sync-alt: \f2f1; +$fa-var-syringe: \f48e; +$fa-var-table: \f0ce; +$fa-var-table-tennis: \f45d; +$fa-var-tablet: \f10a; +$fa-var-tablet-alt: \f3fa; +$fa-var-tablets: \f490; +$fa-var-tachometer-alt: \f3fd; +$fa-var-tag: \f02b; +$fa-var-tags: \f02c; +$fa-var-tape: \f4db; +$fa-var-tasks: \f0ae; +$fa-var-taxi: \f1ba; +$fa-var-telegram: \f2c6; +$fa-var-telegram-plane: \f3fe; +$fa-var-tencent-weibo: \f1d5; +$fa-var-terminal: \f120; +$fa-var-text-height: \f034; +$fa-var-text-width: \f035; +$fa-var-th: \f00a; +$fa-var-th-large: \f009; +$fa-var-th-list: \f00b; +$fa-var-themeisle: \f2b2; +$fa-var-thermometer: \f491; +$fa-var-thermometer-empty: \f2cb; +$fa-var-thermometer-full: \f2c7; +$fa-var-thermometer-half: \f2c9; +$fa-var-thermometer-quarter: \f2ca; +$fa-var-thermometer-three-quarters: \f2c8; +$fa-var-thumbs-down: \f165; +$fa-var-thumbs-up: \f164; +$fa-var-thumbtack: \f08d; +$fa-var-ticket-alt: \f3ff; +$fa-var-times: \f00d; +$fa-var-times-circle: \f057; +$fa-var-tint: \f043; +$fa-var-toggle-off: \f204; +$fa-var-toggle-on: \f205; +$fa-var-trademark: \f25c; +$fa-var-train: \f238; +$fa-var-transgender: \f224; +$fa-var-transgender-alt: \f225; +$fa-var-trash: \f1f8; +$fa-var-trash-alt: \f2ed; +$fa-var-tree: \f1bb; +$fa-var-trello: \f181; +$fa-var-tripadvisor: \f262; +$fa-var-trophy: \f091; +$fa-var-truck: \f0d1; +$fa-var-truck-loading: \f4de; +$fa-var-truck-moving: \f4df; +$fa-var-tty: \f1e4; +$fa-var-tumblr: \f173; +$fa-var-tumblr-square: \f174; +$fa-var-tv: \f26c; +$fa-var-twitch: \f1e8; +$fa-var-twitter: \f099; +$fa-var-twitter-square: \f081; +$fa-var-typo3: \f42b; +$fa-var-uber: \f402; +$fa-var-uikit: \f403; +$fa-var-umbrella: \f0e9; +$fa-var-underline: \f0cd; +$fa-var-undo: \f0e2; +$fa-var-undo-alt: \f2ea; +$fa-var-uniregistry: \f404; +$fa-var-universal-access: \f29a; +$fa-var-university: \f19c; +$fa-var-unlink: \f127; +$fa-var-unlock: \f09c; +$fa-var-unlock-alt: \f13e; +$fa-var-untappd: \f405; +$fa-var-upload: \f093; +$fa-var-usb: \f287; +$fa-var-user: \f007; +$fa-var-user-circle: \f2bd; +$fa-var-user-md: \f0f0; +$fa-var-user-plus: \f234; +$fa-var-user-secret: \f21b; +$fa-var-user-times: \f235; +$fa-var-users: \f0c0; +$fa-var-ussunnah: \f407; +$fa-var-utensil-spoon: \f2e5; +$fa-var-utensils: \f2e7; +$fa-var-vaadin: \f408; +$fa-var-venus: \f221; +$fa-var-venus-double: \f226; +$fa-var-venus-mars: \f228; +$fa-var-viacoin: \f237; +$fa-var-viadeo: \f2a9; +$fa-var-viadeo-square: \f2aa; +$fa-var-vial: \f492; +$fa-var-vials: \f493; +$fa-var-viber: \f409; +$fa-var-video: \f03d; +$fa-var-video-slash: \f4e2; +$fa-var-vimeo: \f40a; +$fa-var-vimeo-square: \f194; +$fa-var-vimeo-v: \f27d; +$fa-var-vine: \f1ca; +$fa-var-vk: \f189; +$fa-var-vnv: \f40b; +$fa-var-volleyball-ball: \f45f; +$fa-var-volume-down: \f027; +$fa-var-volume-off: \f026; +$fa-var-volume-up: \f028; +$fa-var-vuejs: \f41f; +$fa-var-warehouse: \f494; +$fa-var-weibo: \f18a; +$fa-var-weight: \f496; +$fa-var-weixin: \f1d7; +$fa-var-whatsapp: \f232; +$fa-var-whatsapp-square: \f40c; +$fa-var-wheelchair: \f193; +$fa-var-whmcs: \f40d; +$fa-var-wifi: \f1eb; +$fa-var-wikipedia-w: \f266; +$fa-var-window-close: \f410; +$fa-var-window-maximize: \f2d0; +$fa-var-window-minimize: \f2d1; +$fa-var-window-restore: \f2d2; +$fa-var-windows: \f17a; +$fa-var-wine-glass: \f4e3; +$fa-var-won-sign: \f159; +$fa-var-wordpress: \f19a; +$fa-var-wordpress-simple: \f411; +$fa-var-wpbeginner: \f297; +$fa-var-wpexplorer: \f2de; +$fa-var-wpforms: \f298; +$fa-var-wrench: \f0ad; +$fa-var-x-ray: \f497; +$fa-var-xbox: \f412; +$fa-var-xing: \f168; +$fa-var-xing-square: \f169; +$fa-var-y-combinator: \f23b; +$fa-var-yahoo: \f19e; +$fa-var-yandex: \f413; +$fa-var-yandex-international: \f414; +$fa-var-yelp: \f1e9; +$fa-var-yen-sign: \f157; +$fa-var-yoast: \f2b1; +$fa-var-youtube: \f167; +$fa-var-youtube-square: \f431; diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fa-brands.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fa-brands.scss new file mode 100644 index 000000000000..3f4c54d781fd --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fa-brands.scss @@ -0,0 +1,21 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@import 'variables'; + +@font-face { + font-family: 'Font Awesome 5 Brands'; + font-style: normal; + font-weight: normal; + src: url('#{$fa-font-path}/fa-brands-400.eot'); + src: url('#{$fa-font-path}/fa-brands-400.eot?#iefix') format('embedded-opentype'), + url('#{$fa-font-path}/fa-brands-400.woff2') format('woff2'), + url('#{$fa-font-path}/fa-brands-400.woff') format('woff'), + url('#{$fa-font-path}/fa-brands-400.ttf') format('truetype'), + url('#{$fa-font-path}/fa-brands-400.svg#fontawesome') format('svg'); +} + +.fab { + font-family: 'Font Awesome 5 Brands'; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fa-regular.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fa-regular.scss new file mode 100644 index 000000000000..2faa99d31c41 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fa-regular.scss @@ -0,0 +1,22 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@import 'variables'; + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-style: normal; + font-weight: 400; + src: url('#{$fa-font-path}/fa-regular-400.eot'); + src: url('#{$fa-font-path}/fa-regular-400.eot?#iefix') format('embedded-opentype'), + url('#{$fa-font-path}/fa-regular-400.woff2') format('woff2'), + url('#{$fa-font-path}/fa-regular-400.woff') format('woff'), + url('#{$fa-font-path}/fa-regular-400.ttf') format('truetype'), + url('#{$fa-font-path}/fa-regular-400.svg#fontawesome') format('svg'); +} + +.far { + font-family: 'Font Awesome 5 Free'; + font-weight: 400; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fa-solid.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fa-solid.scss new file mode 100644 index 000000000000..cb10c11774e5 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fa-solid.scss @@ -0,0 +1,23 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@import 'variables'; + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-style: normal; + font-weight: 900; + src: url('#{$fa-font-path}/fa-solid-900.eot'); + src: url('#{$fa-font-path}/fa-solid-900.eot?#iefix') format('embedded-opentype'), + url('#{$fa-font-path}/fa-solid-900.woff2') format('woff2'), + url('#{$fa-font-path}/fa-solid-900.woff') format('woff'), + url('#{$fa-font-path}/fa-solid-900.ttf') format('truetype'), + url('#{$fa-font-path}/fa-solid-900.svg#fontawesome') format('svg'); +} + +.fa, +.fas { + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fontawesome.scss b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fontawesome.scss new file mode 100644 index 000000000000..0a3f8ac63ee6 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/scss/fontawesome.scss @@ -0,0 +1,16 @@ +/*! + * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@import 'variables'; +@import 'mixins'; +@import 'core'; +@import 'larger'; +@import 'fixed-width'; +@import 'list'; +@import 'bordered-pulled'; +@import 'animated'; +@import 'rotated-flipped'; +@import 'stacked'; +@import 'icons'; +@import 'screen-reader'; diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.eot b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.eot new file mode 100644 index 000000000000..9d49a59a6797 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.svg b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.svg new file mode 100644 index 000000000000..bf573a1af139 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.svg @@ -0,0 +1,1017 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.ttf b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.ttf new file mode 100644 index 000000000000..374f8ee9146f Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.woff b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.woff new file mode 100644 index 000000000000..c7d31eb1e813 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.woff2 b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.woff2 new file mode 100644 index 000000000000..1ced125fe9e5 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-brands-400.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.eot b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.eot new file mode 100644 index 000000000000..3ba8c46bf13e Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.svg b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.svg new file mode 100644 index 000000000000..74db206ca881 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.svg @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.ttf b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.ttf new file mode 100644 index 000000000000..7412d88352a9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.woff b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.woff new file mode 100644 index 000000000000..797a15cbfb98 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.woff2 b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.woff2 new file mode 100644 index 000000000000..b75da04b548e Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-regular-400.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.eot b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.eot new file mode 100644 index 000000000000..3c5350af8e72 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.svg b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.svg new file mode 100644 index 000000000000..3e9cbaea9dcc --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.svg @@ -0,0 +1,1644 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.ttf b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.ttf new file mode 100644 index 000000000000..9f268151e6f2 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.woff b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.woff new file mode 100644 index 000000000000..66d6e9e42870 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.woff2 b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.woff2 new file mode 100644 index 000000000000..84cadecde7c6 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/fontawesome-free-5.0.10/webfonts/fa-solid-900.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/fonts.css b/web/src/main/webapp/v2/src/assets/fonts/fonts.css new file mode 100755 index 000000000000..39d1aac81f32 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/fonts.css @@ -0,0 +1,70 @@ +/* nanum-gothic-regular - korean */ +@font-face { + font-family: 'Nanum Gothic'; + font-style: normal; + font-weight: 400; + src: local('NanumGothic'), + url('nanum/nanum-gothic-v8-korean-regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('nanum/nanum-gothic-v8-korean-regular.woff') format('woff'), /* Modern Browsers */ + url('nanum/nanum-gothic-v8-korean-regular.ttf') format('truetype') /* Safari, Android, iOS */ +} +/* nanum-gothic-700 - korean */ +@font-face { + font-family: 'Nanum Gothic'; + font-style: normal; + font-weight: 700; + src: local('NanumGothic Bold'), local('NanumGothic-Bold'), + url('nanum/nanum-gothic-v8-korean-700.woff2') format('woff2'), /* Super Modern Browsers */ + url('nanum/nanum-gothic-v8-korean-700.woff') format('woff'), /* Modern Browsers */ + url('nanum/nanum-gothic-v8-korean-700.ttf') format('truetype') /* Safari, Android, iOS */ +} +/* nanum-gothic-800 - korean */ +@font-face { + font-family: 'Nanum Gothic'; + font-style: normal; + font-weight: 800; + src: local('NanumGothic ExtraBold'), local('NanumGothic-ExtraBold'), + url('nanum/nanum-gothic-v8-korean-800.woff2') format('woff2'), /* Super Modern Browsers */ + url('nanum/nanum-gothic-v8-korean-800.woff') format('woff'), /* Modern Browsers */ + url('nanum/nanum-gothic-v8-korean-800.ttf') format('truetype') /* Safari, Android, iOS */ +} +/* open-sans-regular - latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + src: local('Open Sans Regular'), local('OpenSans-Regular'), + url('opensans/open-sans-v15-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('opensans/open-sans-v15-latin-regular.woff') format('woff'), /* Modern Browsers */ + url('opensans/open-sans-v15-latin-regular.ttf') format('truetype') /* Safari, Android, iOS */ +} +/* open-sans-600 - latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), + url('opensans/open-sans-v15-latin-600.woff2') format('woff2'), /* Super Modern Browsers */ + url('opensans/open-sans-v15-latin-600.woff') format('woff'), /* Modern Browsers */ + url('opensans/open-sans-v15-latin-600.ttf') format('truetype') /* Safari, Android, iOS */ +} +/* open-sans-700 - latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 700; + src: local('Open Sans Bold'), local('OpenSans-Bold'), + url('opensans/open-sans-v15-latin-700.woff2') format('woff2'), /* Super Modern Browsers */ + url('opensans/open-sans-v15-latin-700.woff') format('woff'), /* Modern Browsers */ + url('opensans/open-sans-v15-latin-700.ttf') format('truetype') /* Safari, Android, iOS */ +} +/* open-sans-800 - latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 800; + src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), + url('opensans/open-sans-v15-latin-800.woff2') format('woff2'), /* Super Modern Browsers */ + url('opensans/open-sans-v15-latin-800.woff') format('woff'), /* Modern Browsers */ + url('opensans/open-sans-v15-latin-800.ttf') format('truetype') /* Safari, Android, iOS */ +} diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.eot b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.eot new file mode 100644 index 000000000000..a11a055b03b9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.svg b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.svg new file mode 100644 index 000000000000..25dddff33bb1 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.svg @@ -0,0 +1,488 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.ttf b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.ttf new file mode 100644 index 000000000000..fc3d365f7835 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.woff b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.woff new file mode 100644 index 000000000000..d17741b805a5 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.woff2 b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.woff2 new file mode 100644 index 000000000000..9ba9556d80b0 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-700.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.eot b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.eot new file mode 100644 index 000000000000..e25413ce3df7 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.svg b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.svg new file mode 100644 index 000000000000..ad5f1dd9034f --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.svg @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.ttf b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.ttf new file mode 100644 index 000000000000..4bf036ca4657 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.woff b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.woff new file mode 100644 index 000000000000..cb8e5bc5e498 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.woff2 b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.woff2 new file mode 100644 index 000000000000..b82f143aec53 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-800.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.eot b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.eot new file mode 100644 index 000000000000..b192d61694d5 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.svg b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.svg new file mode 100644 index 000000000000..7365512e498d --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.svg @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.ttf b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.ttf new file mode 100644 index 000000000000..2f08e65b3ab4 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.woff b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.woff new file mode 100644 index 000000000000..0394f4ec57d1 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.woff2 b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.woff2 new file mode 100644 index 000000000000..1bfe79c9edb2 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/nanum/nanum-gothic-v8-korean-regular.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.eot b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.eot new file mode 100644 index 000000000000..2d978e8861ca Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.svg b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.svg new file mode 100644 index 000000000000..410561e7821c --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.svg @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.ttf b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.ttf new file mode 100644 index 000000000000..bc77ab679237 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.woff b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.woff new file mode 100644 index 000000000000..5a604b3a010d Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.woff2 b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.woff2 new file mode 100644 index 000000000000..a0965b7a8990 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-600.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.eot b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.eot new file mode 100644 index 000000000000..bf88bfad7824 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.svg b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.svg new file mode 100644 index 000000000000..8e6b61ade171 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.svg @@ -0,0 +1,334 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.ttf b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.ttf new file mode 100644 index 000000000000..11aec0f49e40 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.woff b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.woff new file mode 100644 index 000000000000..2523e953cb97 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.woff2 b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.woff2 new file mode 100644 index 000000000000..2b04b15bb70a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-700.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.eot b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.eot new file mode 100644 index 000000000000..a0f8a0f37555 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.svg b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.svg new file mode 100644 index 000000000000..f2a2d9f6e746 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.svg @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.ttf b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.ttf new file mode 100644 index 000000000000..bafdd40f09a8 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.woff b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.woff new file mode 100644 index 000000000000..41ae78830858 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.woff2 b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.woff2 new file mode 100644 index 000000000000..53188bc5c0b9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-800.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.eot b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.eot new file mode 100644 index 000000000000..1a8b1160df0f Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.eot differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.svg b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.svg new file mode 100644 index 000000000000..78eb653a75e6 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.svg @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.ttf b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.ttf new file mode 100644 index 000000000000..9d4e8e526d7e Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.ttf differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.woff b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.woff new file mode 100644 index 000000000000..e495e6f010c9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.woff differ diff --git a/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.woff2 b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.woff2 new file mode 100644 index 000000000000..c8050c25f8c9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/fonts/opensans/open-sans-v15-latin-regular.woff2 differ diff --git a/web/src/main/webapp/v2/src/assets/i18n/en.json b/web/src/main/webapp/v2/src/assets/i18n/en.json new file mode 100644 index 000000000000..84703186cc5e --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/i18n/en.json @@ -0,0 +1,768 @@ +{ + "COMMON": { + "NO_DATA": "No Data", + "NO_AGENTS": "There are no running agents.", + "MIN_LENGTH": "Enter at least !{0} letters", + "REQUIRED": "!{0} is required.", + "REQUIRED_SELECT": "Select !{0}", + "MAX_SEARCH_PERIOD": "Search duration may not be greater than !{0}days.", + "DO_NOT_HAVE_PERMISSION": "Don't have permission." + }, + "MAIN": { + "SELECT_YOUR_APP": "Select your application", + "INPUT_APP_NAME_PLACE_HOLDER": "Input application name", + "FAVORITE_APP_LIST": "Favorite List", + "APP_LIST": "Application List", + "SEARCH_SERVER_MAP_PLACE_HOLDER": "Search", + "EMPTY_RESULT": "We couldn't find anything" + }, + "INSPECTOR": { + "FAILED_TO_FETCH_DATA": "Failed to fetch the data", + "NO_DATA_COLLECTED": "No data collected", + "APPLICATION_INSPECTOR_USAGE_GUIDE_MESSAGE": "[TEXT]:{value=Application Inspector is not enabled.
To enable Application Inspector, please refer to}|[LINK]:{href=https://github.com/naver/pinpoint/blob/master/doc/application-inspector.md\\target=blank\\style=color:#428bca\\linkText=this link}", + "APPLICAITION_NAME_ISSUE": { + "ISSUE_MESSAGE": "The agent is currently registered under !{1} due to the following:", + "ISSUE_CAUSES": [ + "1. The agent has moved from !{0} to !{1}", + "2. A different agent with the same agent id has been registered to !{1}" + ], + "ISSUE_SOLUTIONS": [ + "For case 1, you should delete the mapping between !{0} and !{1}.", + "For case 2, the agent id of the duplicate agent must change." + ] + } + }, + "TRANSACTION_LIST": { + "SELECT_TRANSACTION": "Select your transaction", + "TRANSACTION_RETRIEVE_ERROR": "Transaction information is missing
Will go to Main" + }, + "SCATTER": { + }, + "CONFIGURATION": { + "GENERAL": { + "DESC": "* User configuration is stored in browser cache. Server-side storage will be supported in a future release.", + "EMPTY": "Favorite list is empty." + }, + "INSTALLATION": { + "DESC": "* You can check if Application Name and Agent ID are duplicated.", + "LENGTH_GUIDE": "You can enter up to !{0} characters.", + "APPLICATION_NAME_PLACEHOLDER": "Input Application Name.", + "AGENT_ID_PLACEHOLDER": "Input Agent ID." + }, + "COMMON": { + "NAME": "Name", + "USER_ID": "User ID", + "DEPARTMENT": "Department", + "PHONE": "Phone", + "EMAIL": "Email", + "USER_GROUP": "User Group ID", + "CHECKER": "Checker", + "THRESHOLD": "Threshold", + "TYPE": "Type", + "NOTES": "Notes" + } + }, + "TRANSACTION": { + "HAS_RESULTS": "!{0} results found.", + "EMPTY_RESULT": "No matches found." + }, + "SUPPORT": { + "RESTRICT_USAGE": "[ICON]:{className=fa-exclamation-triangle}|[TEXT]:{value=Your browser(!{0}) is not supported.}|[LINK]:{href=browser-not-supported\\target=blank\\style=text-decoration:underline;font-weight:600\\linkText=Check supported browsers}", + "INSTALL_GUIDE": "Please install one of the following browsers and try again." + }, + "HELP_VIEWER": { + "NAVBAR": [{ + "TITLE": "Application List", + "DESC": "Shows the list of applications with Pinpoint installed.", + "CATEGORY" : [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Icon", + "DESC": "Type of the Application" + }, { + "NAME": "Text", + "DESC": "Name of the Application. The value of -Dpinpoint.applicationName in Pinpoint agent configuration." + }] + }] + }, { + "TITLE": "Inbound, Outbound", + "DESC": "Search-depth of server map.", + "CATEGORY" : [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Inbound", + "DESC": "Number of depth to render for requests coming into the selected node." + }, { + "NAME": "Outbound", + "DESC": "Number of depth to render for requests going out from the selected node" + }] + }] + }, { + "TITLE": "Period Selector", + "DESC": "Selects the time range for search data.", + "CATEGORY": [{ + "TITLE": "Usage", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-clock\\style=font-size:17px}", + "DESC": "Query for data traced during the most recent selected time-period.
Auto-refresh is supported for 5m, 10m, 3h time-period." + },{ + "NAME": "[ICON]:{className=fa-calendar-alt\\style=font-size:17px}", + "DESC": "Query for traced data between two selected times with the maximum of 48 hours." + }] + }] + }, { + "TITLE": "Bidirectional Search", + "DESC": "Search-method of server map.", + "CATEGORY" : [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Bidirectional", + "DESC": "Renders inbound/outbound nodes for each and every node (within limit) even if they are not directly related to the selected node.
Note that checking this option may lead to overly complex server maps." + }] + }] + }], + "RESPONSE_SUMMARY": [{ + "TITLE": "Response Summary Chart", + "DESC": "", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px}|[TEXT]:{value=X-Axis}", + "DESC": "Response Time" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px}|[TEXT]:{value=Y-Axis}", + "DESC": "Transaction Count" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#34b994}|[TEXT]:{value=1s}", + "DESC": "No. of Successful transactions (less than 1 second)" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#51afdf}|[TEXT]:{value=3s}", + "DESC": "No. of Successful transactions (1 ~ 3 seconds)" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#ffba00}|[TEXT]:{value=5s}", + "DESC": "No. of Successful transactions (3 ~ 5 seconds)" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#e67f22}|[TEXT]:{value=Slow}", + "DESC": "No. of Successful transactions (greater than 5 seconds)" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#e95459}|[TEXT]:{value=Error}", + "DESC": "No. of Failed transactions regardless of response time" + }] + }] + }], + "LOAD": [{ + "TITLE": "Load Chart", + "DESC": "", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px}|[TEXT]:{value=X-Axis}", + "DESC": "Response Time" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px}|[TEXT]:{value=Y-Axis}", + "DESC": "Transaction Count" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#34b994}|[TEXT]:{value=1s}", + "DESC": "No. of Successful transactions (less than 1 second)" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#51afdf}|[TEXT]:{value=3s}", + "DESC": "No. of Successful transactions (1 ~ 3 seconds)" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#ffba00}|[TEXT]:{value=5s}", + "DESC": "No. of Successful transactions (3 ~ 5 seconds)" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#e67f22}|[TEXT]:{value=Slow}", + "DESC": "No. of Successful transactions (greater than 5 seconds)" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#e95459}|[TEXT]:{value=Error}", + "DESC": "No. of Failed transactions regardless of response time" + }] + }, { + "TITLE": "Usage", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Clicking on a legend item shows/hides all transactions within the selected group." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Dragging on the chart zooms in to the dragged area." + }] + }] + }], + "SERVER_MAP" : [{ + "TITLE": "Server Map", + "DESC": "Displays a topological view of the distributed server map.", + "CATEGORY": [{ + "TITLE": "Node", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Each node is a logical unit of application." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "The value on the top-right corner represents the number of server instances assigned to that application. (Not shown when there is only one such instance.)" + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "An alarm icon is displayed on the top-left corner if an error/exception is detected in one of the server instances." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "When the node is selected, the transaction information flowing into the application is displayed on the right side of the screen." + }] + }, { + "TITLE": "Arrow", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Each arrow represents a transaction flow." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "The number shows the transaction count. Displayed in red for error counts that exceeds the threshold." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "When the arrow is selected, the transaction information that passes through the selected section is displayed on the right side of the screen." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Right-clicking on an arrow displays the context menu for filtering." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "The Filter in the Context menu shows only the transactions that pass through the selected section." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Filter wizard allows you to configure more detailed filters." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "[TEXT]:{value=When the filter is applied,}|[ICON]:{className=fa-filter\\style=font-size:15px}|[TEXT]:{value=icon will be displayed on the arrow.}" + }] + }, { + "TITLE": "Chart Configuration", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Right-clicking on any blank area displays a chart configuration menu." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Node Setting / Merge Unknown: Groups all applications without agent and displays it as a single node." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Double-clicking on any blank area resets the zoom level of the server map." + }] + }] + }], + "REAL_TIME": [{ + "TITLE": "Realtime Active Thread Chart", + "DESC": "Shows the Active Thread count of each agent in realtime.", + "CATEGORY": [{ + "TITLE": "Error Messages", + "ITEMS": [{ + "NAME": "UNSUPPORTED VERSION", + "DESC": "Agent version too old. (Please upgrade the agent to 1.5.0+)" + }, { + "NAME": "CLUSTER OPTION NOTSET", + "DESC": "Option disabled by agent. (Please set profiler.pinpoint.activethread to true in profiler.config)" + }, { + "NAME": "TIMEOUT", + "DESC": "Agent connection timed out receiving active thread count. Please contact the administrator if problem persists." + }, { + "NAME": "NOT FOUND", + "DESC": "Agent not found. (If you get this message while the agent is running, please set profiler.tcpdatasender.command.accept.enable to true in profiler.config)" + }, { + "NAME": "CLUSTER CHANNEL CLOSED", + "DESC": "Agent session expired." + }, { + "NAME": "PINPOINT INTERNAL ERROR", + "DESC": "Pinpoint internal error. Please contact the administrator." + }, { + "NAME": "No Active Thread", + "DESC": "The agent has no threads that are currently active." + }, { + "NAME": "No Response", + "DESC": "No response from Pinpoint Web. Please contact the administrator." + }] + }] + }], + "CALL_TREE": [{ + "TITLE": "Call Tree", + "DESC": "", + "CATEGORY": [{ + "TITLE": "Column", + "ITEMS": [{ + "NAME": "Gap", + "DESC": "Elapsed time between the start of the previous method and the entry of this method" + }, { + "NAME": "Exec", + "DESC": "Duration of the method call from entry to exit" + }, { + "NAME": "Exec(%)", + "DESC": "[ICON]:{className=fa-circle\\style=font-size:11px;color:#5bc0de;margin-right:4px}|[TEXT]:{value=The execution time of the method call as a percentage of the total execution time of the transaction}" + }, { + "NAME": "", + "DESC": "[ICON]:{className=fa-circle\\style=font-size:11px;color:#4343C8;margin-right:4px}|[TEXT]:{value= A percentage of the self execution time}" + }, { + "NAME": "Self", + "DESC": "Duration of the method call from entry to exit, excluding time consumed in nested methods call" + }] + }] + }], + "SCATTER": [{ + "TITLE": "Response Time Scatter Chart", + "DESC": "", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-circle\\style=font-size:15px;color:#2EB089}", + "DESC": "Successful Transaction" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:15px;color:#E64A4F}", + "DESC": "Failed Transaction" + }, { + "NAME": "X-Axis", + "DESC": "Transaction Timestamp" + }, { + "NAME": "Y-Axis", + "DESC": "Response Time" + }] + },{ + "TITLE": "Usage", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-plus\\style=font-size:15px}", + "DESC": "Drag on the scatter chart to show detailed information on selected transactions." + }, { + "NAME": "[ICON]:{className=fa-cog\\style=font-size:15px}", + "DESC": "Set the min/max value of the Y-axis (Response Time)." + }, { + "NAME": "[ICON]:{className=fa-download\\style=font-size:15px}", + "DESC": "Download the chart as an image file." + }, { + "NAME": "[ICON]:{className=fa-expand-arrows-alt\\style=font-size:15px}", + "DESC": "Open the chart in a new window." + }] + }] + }], + "INSPECTOR": { + "AGENT_LIST": [{ + "TITLE": "Agent List", + "DESC": "List of agents registered under the current Application Name", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-tasks\\style=font-size:15px}", + "DESC": "Hostname of the agent's machine" + }, { + "NAME": "[IMAGE]:{name=icon-down}", + "DESC": "Agent was shutdown at the time of query" + }, { + "NAME": "[IMAGE]:{name=icon-disconnect}", + "DESC": "Agent was disconnected at the time of query" + }, { + "NAME": "[IMAGE]:{name=icon-error}", + "DESC": "Agent status was unknown at the time of query" + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Agent running at the time of query has no sign." + }] + }] + }], + "AGENT_CHART": { + "HEAP": [{ + "TITLE": "Heap", + "DESC": "JVM's heap information and full garbage collection time required", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Max", + "DESC": "Maximum heap size" + }, { + "NAME": "Used", + "DESC": "Heap size currently in use" + }, { + "NAME": "FGC", + "DESC": "Time required for full garbage collection (number of FGCs in parenthesis if it occurred more than once)" + }] + }] + }], + "NON_HEAP": [{ + "TITLE": "Non-Heap", + "DESC": "JVM's non-heap information and full garbage collection time required", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Max", + "DESC": "Maximum non-heap size" + }, { + "NAME": "Used", + "DESC": "Non-heap size currently in use" + }, { + "NAME": "FGC", + "DESC": "Time required for full garbage collection (number of FGCs in parenthesis if it occurred more than once)" + }] + }] + }], + "CPU_USAGE": [{ + "TITLE": "Cpu Usage", + "DESC": "JVM/System's CPU Usage - For multi-core CPUs, displays the average CPU usage of all the cores.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Java 1.6", + "DESC": "Only JVM's CPU usage is collected." + }, { + "NAME": "Java 1.7+", + "DESC": "Both JVM's and system's CPU usage are collected." + }] + }] + }], + "TPS": [{ + "TITLE": "Transactions Per Second", + "DESC": "Transactions received by the server per second", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Sampled Continuation (S.C)", + "DESC": "Profiled transactions that started from another agent" + }, { + "NAME": "Sampled New (S.N)", + "DESC": "Profiled transactions that started from the selected agent" + }, { + "NAME": "Unsampled Continuation (U.C)", + "DESC": "Unprofiled transactions that started from another agent" + }, { + "NAME": "Unsampled New (U.N)", + "DESC": "Unprofiled transactions that started from the selected agent" + }, { + "NAME": "Total", + "DESC": "All transactions" + }] + }] + }], + "ACTIVE_THREAD": [{ + "TITLE": "Active Thread", + "DESC": "Snapshots of the agent's active thread status, categorized by how long they have been active for serving a request.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Fast (1s)", + "DESC": "Number of threads that have been active for less than or equal to 1s" + }, { + "NAME": "Normal (3s)", + "DESC": "Number of threads that have been active for less than or equal to 3s but longer than 1s" + }, { + "NAME": "Slow (5s)", + "DESC": "Number of threads that have been active for less than or equal to 5s but longer than 3s" + }, { + "NAME": "Very Slow (slow)", + "DESC": "Number of threads that have been active for longer than 5s" + }] + }] + }], + "RESPONSE_TIME": [{ + "TITLE": "Response Time", + "DESC": "Shows the status of agent's response time.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Avg", + "DESC": "Average Response Time (unit : milliseconds)" + }, { + "NAME": "Max", + "DESC": "Maximum Response Time (unit : milliseconds)" + }] + }] + }], + "DATA_SOURCE": [{ + "TITLE": "Data Source", + "DESC": "Shows the status of agent's data source.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Active Avg", + "DESC": "Average number of active connections" + }, { + "NAME": "Active Max", + "DESC": "Maximum number of active connections" + }, { + "NAME": "Total Max", + "DESC": "The maximum number of connections that can be allocated at the same time" + }, { + "NAME": "Type", + "DESC": "Type of DB Connection Pool" + }] + }] + }], + "OPEN_FILE_DESCRIPTOR": [{ + "TITLE": "File Descriptor", + "DESC": "Shows the status of agent's file descriptors.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Open File Descriptor", + "DESC": "Number of open file descriptor currently used" + }] + }] + }], + "DIRECT_BUFFER_COUNT": [{ + "TITLE": "Direct Buffer", + "DESC": "Shows the status of agent's direct buffer.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Direct Buffer Count", + "DESC": "Number of direct buffer" + }] + }] + }], + "DIRECT_BUFFER_MEMORY": [{ + "TITLE": "Direct Buffer Memory", + "DESC": "Shows the status of agent's used direct buffer memory.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Direct Buffer Memory Used", + "DESC": "Currently used direct buffer memory" + }] + }] + }], + "MAPPED_BUFFER_COUNT": [{ + "TITLE": "Mapped Buffer", + "DESC": "Shows the status of agent's Mapped buffer.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Mapped Buffer Count", + "DESC": "Number of Mapped buffer" + }] + }] + }], + "MAPPED_BUFFER_MEMORY": [{ + "TITLE": "Mapped Buffer Memory", + "DESC": "Shows the status of agent's used Mapped buffer memory.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Mapped Buffer Memory Used", + "DESC": "Currently used Mapped buffer memory" + }] + }] + }] + }, + "APPLICATION_CHART": { + "HEAP": [{ + "TITLE": "Heap", + "DESC": "Heap size used by the agent JVMs", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Smallest Heap size used by an agent JVM" + }, { + "NAME": "Avg", + "DESC": "Average Heap size used by the agent JVMs" + }, { + "NAME": "Max", + "DESC": "Largest Heap size used by an agent JVM" + }] + }] + }], + "NON_HEAP": [{ + "TITLE": "Non-Heap", + "DESC": "Non-heap size used by the agent JVMs", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Smallest Non-Heap size used by an agent JVM" + }, { + "NAME": "Avg", + "DESC": "Average Non-Heap size used by the agent JVMs" + }, { + "NAME": "Max", + "DESC": "Largest Non-Heap size used by an agent JVM" + }] + }] + }], + "JVM_CPU_USAGE": [{ + "TITLE": "JVM CPU Usage", + "DESC": "CPU used by agent JVM processes - For multi-core CPUs, displays the average CPU usage of all the cores.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Smallest CPU usage of agent JVM processes" + }, { + "NAME": "Avg", + "DESC": "Average CPU usage of agent JVM processes" + }, { + "NAME": "Max", + "DESC": "Largest CPU usage of agent JVM processes" + }] + }] + }], + "SYSTEM_CPU_USAGE": [{ + "TITLE": "System CPU Usage", + "DESC": "CPU usage of every agent's system - For multi-core CPUs, displays the average CPU usage of all cores.", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Smallest system CPU usage of agents" + }, { + "NAME": "Avg", + "DESC": "Average system CPU usage of agents" + }, { + "NAME": "Max", + "DESC": "Largest system CPU usage of agents" + }] + }] + }], + "TPS": [{ + "TITLE": "Transactions Per Second", + "DESC": "Number of transactions received by the agents per second", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Lowest TPS of the agents" + }, { + "NAME": "Avg", + "DESC": "Average TPS of the agents" + }, { + "NAME": "Max", + "DESC": "Highest TPS of the agents" + }] + }] + }], + "ACTIVE_THREAD": [{ + "TITLE": "Active Thread", + "DESC": "Number of active threads serving user requests", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Lowest active thread count of the agents serving user requests" + }, { + "NAME": "Avg", + "DESC": "Average active thread count of the agents serving user requests" + }, { + "NAME": "Max", + "DESC": "Highest active thread count of the agents serving user requests" + }] + }] + }], + "RESPONSE_TIME": [{ + "TITLE": "Response Time", + "DESC": "Average response times served by the agents", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Lowest value of agents' average response time" + }, { + "NAME": "Avg", + "DESC": "Average value of agents' average response time" + }, { + "NAME": "Max", + "DESC": "Highest value of agents' average response time" + }] + }] + }], + "DATA_SOURCE": [{ + "TITLE": "Data Source", + "DESC": "Status of the agents' data source", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Smallest data source connection count of the agents" + }, { + "NAME": "Avg", + "DESC": "Average data source connection count of the agents" + }, { + "NAME": "Max", + "DESC": "Largest data source connection count of the agents" + }] + }] + }], + "OPEN_FILE_DESCRIPTOR": [{ + "TITLE": "File Descriptor", + "DESC": "Number of file descriptors used by agents", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Min number of open file descriptors" + }, { + "NAME": "Avg", + "DESC": "Average open file descriptors" + }, { + "NAME": "Max", + "DESC": "Max number of open file descriptors" + }] + }] + }], + "DIRECT_BUFFER_COUNT": [{ + "TITLE": "Direct Buffer", + "DESC": "Number of direct buffer used by agents", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Min number of direct buffer" + }, { + "NAME": "Avg", + "DESC": "Average number of direct buffer" + }, { + "NAME": "Max", + "DESC": "Max number of direct buffer" + }] + }] + }], + "DIRECT_BUFFER_MEMORY": [{ + "TITLE": "Direct Buffer Memory", + "DESC": "Number of Direct buffer used by agents", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Min memory used by direct buffer" + }, { + "NAME": "Avg", + "DESC": "Average memory used by direct buffer" + }, { + "NAME": "Max", + "DESC": "Max memory used by direct buffer" + }] + }] + }], + "MAPPED_BUFFER_COUNT": [{ + "TITLE": "Mapped Buffer", + "DESC": "Number of Mapped Buffer used by agents", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Min number of Mapped Buffer" + }, { + "NAME": "Avg", + "DESC": "Average number of Mapped Buffer" + }, { + "NAME": "Max", + "DESC": "Max number of Mapped Buffer" + }] + }] + }], + "MAPPED_BUFFER_MEMORY": [{ + "TITLE": "Mapped Buffer Memory", + "DESC": "Number of Mapped Buffer Memory used by agents", + "CATEGORY": [{ + "TITLE": "Legend", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Min memory used by mapped buffer" + }, { + "NAME": "Avg", + "DESC": "Average memory used by mapped buffer" + }, { + "NAME": "Max", + "DESC": "Max memory used by mapped buffer" + }] + }] + }] + } + } + } +} diff --git a/web/src/main/webapp/v2/src/assets/i18n/ko.json b/web/src/main/webapp/v2/src/assets/i18n/ko.json new file mode 100644 index 000000000000..97a24c6e1b6e --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/i18n/ko.json @@ -0,0 +1,767 @@ +{ + "COMMON": { + "NO_DATA": "데이터가 없습니다.", + "NO_AGENTS": "실행 중인 에이전트가 없습니다.", + "MIN_LENGTH": "!{0}문자 이상 입력하세요.", + "REQUIRED": "!{0}를(을) 입력하세요.", + "REQUIRED_SELECT": "!{0}를(을) 선택하세요.", + "MAX_SEARCH_PERIOD": "한번에 검색할 수 있는 최대 기간은 !{0}일 입니다.", + "DO_NOT_HAVE_PERMISSION": "권한이 없습니다." + }, + "MAIN": { + "SELECT_YOUR_APP": "애플리케이션을 선택하세요.", + "INPUT_APP_NAME_PLACE_HOLDER": "애플리케이션 이름을 입력하세요.", + "FAVORITE_APP_LIST": "즐겨찾기 목록", + "APP_LIST": "애플리케이션 목록", + "SEARCH_SERVER_MAP_PLACE_HOLDER": "검색어를 입력하세요.", + "EMPTY_RESULT": "찾을 수 없습니다." + }, + "INSPECTOR": { + "FAILED_TO_FETCH_DATA": "데이터를 가져오지 못했습니다.", + "NO_DATA_COLLECTED": "데이터가 없습니다.", + "APPLICATION_INSPECTOR_USAGE_GUIDE_MESSAGE": "[TEXT]:{value=Application Inspector 기능이 활성화 되어 있지 않습니다.
Application Inspector 기능을 사용하려면,}|[LINK]:{href=https://github.com/naver/pinpoint/blob/master/doc/application-inspector.md\\target=_blank\\style=color:#428bca\\linkText=링크}|[TEXT]:{value=를 참고하세요.}", + "APPLICAITION_NAME_ISSUE": { + "ISSUE_MESSAGE": "해당 agent는 !{0}이 아닌 !{1}에 포함되어 있습니다.

원인은 다음 중 하나입니다:", + "ISSUE_CAUSES": [ + "1. 해당 agent가 !{0}에서 !{1}으로 이동한 경우", + "2. !{0}의 agent가 !{1}에도 등록된 경우" + ], + "ISSUE_SOLUTIONS": [ + "1의 경우 !{0}과 !{1}간의 매핑 정보를 삭제해야 합니다.", + "2의 경우 중복 등록된 agent의 id를 변경해야 합니다." + ] + } + }, + "TRANSACTION_LIST": { + "SELECT_TRANSACTION": "Transaction을 선택하세요.", + "TRANSACTION_RETRIEVE_ERROR": "Transaction 정보가 없습니다.
Main 화면으로 이동합니다." + }, + "SCATTER": {}, + "CONFIGURATION": { + "GENERAL": { + "DESC": "* 설정 정보는 브라우저 캐쉬에 저장합니다. 서버 측 저장은 추후 지원 할 예정입니다.", + "EMPTY": "등록된 목록이 없습니다." + }, + "INSTALLATION": { + "DESC": "* Application Name과 Agent ID의 중복 여부를 확인 할 수 있습니다.", + "LENGTH_GUIDE": "1 ~ !{0} 자의 문자를 입력하세요.", + "APPLICATION_NAME_PLACEHOLDER": "Application Name을 입력하세요.", + "AGENT_ID_PLACEHOLDER": "Agent ID를 입력하세요." + }, + "COMMON": { + "NAME": "이름", + "USER_ID": "사용자 ID", + "DEPARTMENT": "부서", + "PHONE": "전화번호", + "EMAIL": "이메일", + "USER_GROUP": "사용자 그룹 ID", + "CHECKER": "알람 항목", + "THRESHOLD": "기준점", + "TYPE": "알림 형식", + "NOTES": "메모" + } + }, + "TRANSACTION": { + "HAS_RESULTS": "!{0} 건이 검색되었습니다.", + "EMPTY_RESULT": "일치하는 결과가 없습니다." + }, + "SUPPORT": { + "RESTRICT_USAGE": "[ICON]:{className=fa-exclamation-triangle}|[TEXT]:{value=사용중인 브라우저(!{0})는 지원하지 않습니다.}|[LINK]:{href=browser-not-supported\\target=_blank\\style=text-decoration:underline;font-weight:600\\linkText=지원 브라우저 확인}", + "INSTALL_GUIDE": "아래의 브라우저들 중 하나를 설치 후 이용해주세요." + }, + "HELP_VIEWER": { + "NAVBAR": [{ + "TITLE": "응용프로그램 목록", + "DESC": "핀포인트가 설치된 응용프로그램 목록입니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "아이콘", + "DESC": "응용프로그램의 종류" + }, { + "NAME": "텍스트", + "DESC": "응용프로그램의 이름입니다. Pinpoint agent 설정에서 applicationName에 지정한 값입니다." + }] + }] + }, { + "TITLE": "Inbound, Outbound", + "DESC": "서버맵의 탐색 깊이를 설정합니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Inbound", + "DESC": "선택된 노드를 기준으로 들어오는 탐색 깊이" + }, { + "NAME": "Outbound", + "DESC": "선택된 노드를 기준으로 나가는 탐색 깊이" + }] + }] + }, { + "TITLE": "조회 시간 설정", + "DESC": "데이터 조회 시간을 선택합니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-clock\\style=font-size:17px}", + "DESC": "현재 시간을 기준으로 선택한 시간 이전부터 현재시간 사이에 수집된 데이터를 조회합니다. 최근 5m, 10m, 3h조회는 자동 새로고침 기능을 지원합니다." + },{ + "NAME": "[ICON]:{className=fa-calendar-alt\\style=font-size:17px}", + "DESC": "지정된 시간 사이에 수집된 데이터를 조회합니다. 조회 시간은 분단위로 최대 48시간을 지정할 수 있습니다." + }] + }] + }, { + "TITLE": "Bidirectional Search", + "DESC": "서버맵의 탐색 방법을 설정합니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Bidirectional", + "DESC": "모든 노드들에 대해 양방향 탐색을 하여 선택된 노드와 직접적인 연관이 없는 노드들도 탐색됩니다. 주의 : 이 옵션을 선택하시면 필요 이상으로 복잡한 서버맵이 조회될 수 있습니다." + }] + }] + }], + "RESPONSE_SUMMARY": [{ + "TITLE": "Response Summary Chart", + "DESC": "응답결과 요약입니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px}|[TEXT]:{value=X축}", + "DESC": "트랜잭션 응답시간 요약 단위" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px}|[TEXT]:{value=Y축}", + "DESC": "트랜잭션의 개수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#34b994}|[TEXT]:{value=1s}", + "DESC": "0초 <= 응답시간 < 1초 에 해당하는 성공한 트랜잭션의 수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#51afdf}|[TEXT]:{value=3s}", + "DESC": "1초 <= 응답시간 < 3초 에 해당하는 성공한 트랜잭션의 수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#ffba00}|[TEXT]:{value=5s}", + "DESC": "3초 <= 응답시간 < 5초 에 해당하는 성공한 트랜잭션의 수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#e67f22}|[TEXT]:{value=Slow}", + "DESC": "5초 <= 응답시간 에 해당하는 성공한 트랜잭션의 수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#e95459}|[TEXT]:{value=Error}", + "DESC": "응답시간과 무관하게 실패한 트랜잭션의 수" + }] + }] + }], + "LOAD": [{ + "TITLE": "Load Chart", + "DESC": "시간별 트랜잭션의 응답 결과입니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px}|[TEXT]:{value=X축}", + "DESC": "트랜잭션 응답시간 요약 단위" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px}|[TEXT]:{value=Y축}", + "DESC": "트랜잭션의 개수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#34b994}|[TEXT]:{value=1s}", + "DESC": "0초 <= 응답시간 < 1초 에 해당하는 성공한 트랜잭션의 수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#51afdf}|[TEXT]:{value=3s}", + "DESC": "1초 <= 응답시간 < 3초 에 해당하는 성공한 트랜잭션의 수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#ffba00}|[TEXT]:{value=5s}", + "DESC": "3초 <= 응답시간 < 5초 에 해당하는 성공한 트랜잭션의 수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#e67f22}|[TEXT]:{value=Slow}", + "DESC": "5초 <= 응답시간 에 해당하는 성공한 트랜잭션의 수" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:11px;margin-right:8px;color:#e95459}|[TEXT]:{value=Error}", + "DESC": "응답시간과 무관하게 실패한 트랜잭션의 수" + }] + }, { + "TITLE": "기능", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "범례를 클릭하여 해당 응답시간에 속한 트랜잭션을 차트에서 제외하거나 포함 할 수 있습니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "마우스로 드래그하여 드래그 한 범위를 확대할 수 있습니다." + }] + }] + }], + "SERVER_MAP" : [{ + "TITLE": "서버맵", + "DESC": "분산된 서버를 도식화 한 지도입니다.", + "CATEGORY": [{ + "TITLE": "박스", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "박스는 응용프로그램 그룹을 나타냅니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "우측의 숫자는 응용프로그램 그룹에 속한 서버 인스턴스의 개수입니다.(한 개일 때에는 숫자를 보여주지 않습니다.)" + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "좌측 빨간 알람은 임계값을 초과한 모니터링 항목이 있을 때 나타납니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "박스를 선택하면 어플리케이션으로 유입된 트랜잭션 정보를 화면 우측에 보여줍니다." + }] + }, { + "TITLE": "화살표", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "화살표는 트랜잭션의 흐름을 나타냅니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "화살표의 숫자는 호출 수 입니다. 임계치 이상의 에러를 포함하면 빨간색으로 보여집니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "화살표를 선택하면 선택된 구간을 통과하는 트랜잭션의 정보를 화면 우측에 보여줍니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "화살표에서 마우스 오른쪽 버튼을 클릭하면 Filtering을 위한 Context menu를 보여줍니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Context menu의 Filter는 선택된 구간을 통과하는 트랜잭션만 모아서 보여줍니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Filter wizard는 보다 상세한 필터 설정을 할 수 있습니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "[TEXT]:{value=필터가 적용되면 화살표에}|[ICON]:{className=fa-filter\\style=font-size:15px}|[TEXT]:{value=아이콘이 표시됩니다.}" + }] + }, { + "TITLE": "차트 설정", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "박스나 화살표가 아닌 화면의 여백에서 마우스 오른쪽 버튼을 클릭하면 차트 설정을 위한 Context menu를 보여줍니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "Node Setting / Merge Unknown: Agent가 설치되어있지 않은 응용프로그램을 하나의 박스로 보여줍니다." + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "박스나 화살표가 아닌 화면의 여백에서 더블클릭을 하면 서버맵의 줌이 초기화됩니다." + }] + }] + }], + "REAL_TIME": [{ + "TITLE": "Realtime Active Thread Chart", + "DESC": "각 Agent의 Active Thread 개수를 실시간으로 보여줍니다.", + "CATEGORY": [{ + "TITLE": "에러 메시지 설명", + "ITEMS": [{ + "NAME": "UNSUPPORTED VERSION", + "DESC": "해당 에이전트의 버전에서는 지원하지 않는 기능입니다. (1.5.0 이상 버전으로 업그레이드하세요.)" + }, { + "NAME": "CLUSTER OPTION NOTSET", + "DESC": "해당 에이전트의 설정에서 기능이 비활성화되어 있습니다. (pinpoint 설정 파일에서 profiler.pinpoint.activethread 항목을 true로 변경하세요.)" + }, { + "NAME": "TIMEOUT", + "DESC": "해당 에이전트에서 일시적으로 활성화 된 스레드 개수를 받지 못하였습니다.(오래 지속될 경우 담당자에게 문의하세요.)" + }, { + "NAME": "NOT FOUND", + "DESC": "해당 에이전트를 찾을 수 없습니다.(에이전트가 활성화 되어 있는 경우에 해당 메시지가 발생한다면 pinpoint 설정 파일에서 profiler.tcpdatasender.command.accept.enable 항목을 true로 변경하세요.)" + }, { + "NAME": "CLUSTER CHANNEL CLOSED", + "DESC": "해당 에이전트와의 세션이 종료되었습니다." + }, { + "NAME": "PINPOINT INTERNAL ERROR", + "DESC": "핀포인트 내부 에러가 발생하였습니다.(담당자에게 문의하세요.)" + }, { + "NAME": "No Active Thread", + "DESC": "현재 해당 에이전트는 활성화된 스레드가 존재하지 않습니다." + }, { + "NAME": "No Response", + "DESC": "핀포인트 웹 서버로부터의 응답을 받지 못하였습니다.(담당자에게 문의하세요.)" + }] + }] + }], + "CALL_TREE": [{ + "TITLE": "Call Tree", + "DESC": "Call Tree의 컬럼명을 설명합니다.", + "CATEGORY": [{ + "TITLE": "컬럼", + "ITEMS": [{ + "NAME": "Gap", + "DESC": "이전 메소드가 시작된 후 현재 메소드를 실행하기 까지의 지연 시간" + }, { + "NAME": "Exec", + "DESC": "메소드 시작부터 종료까지의 시간" + }, { + "NAME": "Exec(%)", + "DESC": "[ICON]:{className=fa-circle\\style=font-size:11px;color:#5bc0de;margin-right:4px}|[TEXT]:{value=트랜잭션 전체 실행 시간 대비 Exec 시간의 비율}" + }, { + "NAME": "", + "DESC": "[ICON]:{className=fa-circle\\style=font-size:11px;color:#4343C8;margin-right:4px}|[TEXT]:{value=Self 시간 비율}" + }, { + "NAME": "Self", + "DESC": "메소드 자체의 시작부터 종료까지의 시간으로 하위의 메소드가 실행된 시간을 제외한 값" + }] + }] + }], + "SCATTER": [{ + "TITLE": "Response Time Scatter Chart", + "DESC": "수집된 트랜잭션의 응답시간 분포도입니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-circle\\style=font-size:15px;color:#2EB089}", + "DESC": "성공한 트랜잭션" + }, { + "NAME": "[ICON]:{className=fa-circle\\style=font-size:15px;color:#E64A4F}", + "DESC": "실패한 트랜잭션" + }, { + "NAME": "X축", + "DESC": "트랜잭션이 실행된 시간" + }, { + "NAME": "Y축", + "DESC": "트랜잭션의 응답 속도" + }] + },{ + "TITLE": "기능", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-plus\\style=font-size:15px}", + "DESC": "마우스로 영역을 드래그하여 드래그 된 영역에 속한 트랜잭션의 상세정보를 조회할 수 있습니다." + }, { + "NAME": "[ICON]:{className=fa-cog\\style=font-size:15px}", + "DESC": "응답시간(Y축)의 최소 또는 최대값을 변경할 수 있습니다." + }, { + "NAME": "[ICON]:{className=fa-download\\style=font-size:15px}", + "DESC": "차트를 이미지 파일로 저장할 수 있습니다." + }, { + "NAME": "[ICON]:{className=fa-expand-arrows-alt\\style=font-size:15px}", + "DESC": "차트를 새창으로 볼 수 있습니다." + }] + }] + }], + "INSPECTOR": { + "AGENT_LIST": [{ + "TITLE": "Agent 리스트", + "DESC": "현 Appliation Name에 등록된 Agent 리스트입니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "[ICON]:{className=fa-tasks\\style=font-size:15px}", + "DESC": "Agent가 설치된 장비의 호스트 이름" + }, { + "NAME": "[IMAGE]:{name=icon-down}", + "DESC": "Shutdown된 상태의 Agent" + }, { + "NAME": "[IMAGE]:{name=icon-disconnect}", + "DESC": "연결이 끊긴 상태의 Agent" + }, { + "NAME": "[IMAGE]:{name=icon-error}", + "DESC": "알 수 없는 상태의 Agent" + }, { + "NAME": "[ICON]:{className=fa-info-circle\\style=font-size:15px}", + "DESC": "정상적으로 실행중인 Agent는 별도의 상태 표시가 없습니다." + }] + }] + }], + "AGENT_CHART": { + "HEAP": [{ + "TITLE": "Heap", + "DESC": "JVM의 Heap 정보와 Full Garbage Collection 소요 시간", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Max", + "DESC": "최대 Heap 사이즈" + }, { + "NAME": "Used", + "DESC": "현재 사용 중인 Heap 사이즈" + }, { + "NAME": "FGC", + "DESC": "Full Garbage Collection의 총 소요 시간(2번 이상 발생 시, 괄호 안에 발생 횟수 표시)" + }] + }] + }], + "NON_HEAP": [{ + "TITLE": "Non-Heap", + "DESC": "JVM의 Non-Heap 정보와 Full Garbage Collection 소요 시간", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Max", + "DESC": "최대 Non-Heap 사이즈" + }, { + "NAME": "Used", + "DESC": "현재 사용 중인 Non-Heap 사이즈" + }, { + "NAME": "FGC", + "DESC": "Full Garbage Collection의 총 소요 시간(2번 이상 발생 시, 괄호 안에 발생 횟수 표시)" + }] + }] + }], + "CPU_USAGE": [{ + "TITLE": "CPU Usage", + "DESC": "JVM과 시스템의 CPU 사용량 - 멀티코어 CPU의 경우, 전체 코어 사용량의 평균입니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Java 1.6", + "DESC": "JVM의 CPU 사용량만 수집됩니다." + }, { + "NAME": "Java 1.7+", + "DESC": "JVM과 전체 시스템의 CPU 사용량 모두 수집됩니다." + }] + }] + }], + "TPS": [{ + "TITLE": "Transactions Per Second", + "DESC": "서버로 인입된 초당 트랜잭션 수", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Sampled Continuation (S.C)", + "DESC": "다른 Agent에서 시작한 샘플링된 트랜잭션" + }, { + "NAME": "Sampled New (S.N)", + "DESC": "선택된 Agent에서 시작한 샘플링된 트랜잭션" + }, { + "NAME": "Unsampled Continuation (U.C)", + "DESC": "다른 Agent에서 시작한 샘플링되지 않은 트랜잭션" + }, { + "NAME": "Unsampled New (U.N)", + "DESC": "선택된 Agent에서 시작한 샘플링되지 않은 트랜잭션" + }, { + "NAME": "Total", + "DESC": "모든 트랜잭션" + }] + }] + }], + "ACTIVE_THREAD": [{ + "TITLE": "Active Thread", + "DESC": "사용자 Request를 처리하는 Agent의 Active Thread 현황을 보여줍니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Fast (1s)", + "DESC": "현재 소요시간이 1초 이하인 Thread 개수" + }, { + "NAME": "Normal (3s)", + "DESC": "현재 소요시간이 1초 초과, 3초 이하인 Thread 개수" + }, { + "NAME": "Slow (5s)", + "DESC": "현재 소요시간이 3초 초과, 5초 이하인 Thread 개수" + }, { + "NAME": "Very Slow (slow)", + "DESC": "현재 소요시간이 5초를 넘고 있는 Thread 개수" + }] + }] + }], + "RESPONSE_TIME": [{ + "TITLE": "Response Time", + "DESC": "Agent의 Response Time의 현황을 보여줍니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Avg", + "DESC": "평균 Response Time (단위 millisecond)" + }, { + "NAME": "Max", + "DESC": "최대 Response Time (단위 millisecond)" + }] + }] + }], + "DATA_SOURCE": [{ + "TITLE": "Data Source", + "DESC": "Agent의 DataSource 현황을 보여줍니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Active Avg", + "DESC": "사용한 Connection의 평균 개수" + }, { + "NAME": "Active Max", + "DESC": "사용한 Connection의 최대 개수" + }, { + "NAME": "Total Max", + "DESC": "사용이 가능한 Connection의 최대 개수" + }, { + "NAME": "Type", + "DESC": "DB Connection Pool 종류" + }] + }] + }], + "OPEN_FILE_DESCRIPTOR": [{ + "TITLE": "File Descriptor", + "DESC": "Agent의 File Descriptor 현황을 보여줍니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Open File Descriptor", + "DESC": "현재 열려있는 File Descriptor 개수" + }] + }] + }], + "DIRECT_BUFFER_COUNT": [{ + "TITLE": "Direct Buffer", + "DESC": "Agent의 Direct Buffer 현황을 보여줍니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Direct Buffer Count", + "DESC": "현재 Direct Buffer 의 개수" + }] + }] + }], + "DIRECT_BUFFER_MEMORY": [{ + "TITLE": "Direct Buffer Memory", + "DESC": "Agent의 Direct Buffer Memory 현황을 보여줍니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Direct Buffer Memory Used", + "DESC": "현재 Direct Buffer 가 사용중인 메모리" + }] + }] + }], + "MAPPED_BUFFER_COUNT": [{ + "TITLE": "Mapped Buffer", + "DESC": "Agent의 Mapped Buffer 현황을 보여줍니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Mapped Buffer Count", + "DESC": "현재 Mapped Buffer 의 개수" + }] + }] + }], + "MAPPED_BUFFER_MEMORY": [{ + "TITLE": "Mapped Buffer Memory", + "DESC": "Agent의 Mapped Buffer Memory 현황을 보여줍니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Mapped Buffer Memory Used", + "DESC": "현재 Mapped Buffer 가 사용중인 메모리" + }] + }] + }] + }, + "APPLICATION_CHART": { + "HEAP": [{ + "TITLE": "Heap", + "DESC": "Agent들이 사용하는 JVM Heap 사이즈 정보", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent들이 사용하는 Heap 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent들이 사용하는 Heap 의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent들이 사용하는 Heap 중 가장 큰 값" + }] + }] + }], + "NON_HEAP": [{ + "TITLE": "Non-Heap", + "DESC": "Agent들이 사용하는 JVM Non-Heap 사이즈 정보", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent들이 사용하는 Non-Heap 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent들이 사용하는 Non-Heap 의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent들이 사용하는 Non-Heap 중 가장 큰 값" + }] + }] + }], + "JVM_CPU_USAGE": [{ + "TITLE": "JVM CPU Usage", + "DESC": "Agent들이 사용하는 JVM CPU 사용량 - 멀티코어 CPU의 경우, 전체 코어 사용량의 평균입니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent들이 사용하는 JVM CPU 사용량 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent들이 사용하는 JVM CPU 사용량의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent들이 사용하는 JVM CPU 사용량 중 가장 큰 값" + }] + }] + }], + "SYSTEM_CPU_USAGE": [{ + "TITLE": "System CPU Usage", + "DESC": "Agent 서버들의 시스템 CPU 사용량 - 멀티코어 CPU의 경우, 전체 코어 사용량의 평균입니다.", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent 서버들의 시스템 CPU 사용량 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent 서버들의 시스템 CPU 사용량 평균값" + }, { + "NAME": "Max", + "DESC": "Agent 서버들의 시스템 CPU 사용량 중 가장 큰 값" + }] + }] + }], + "TPS": [{ + "TITLE": "Transactions Per Second", + "DESC": "Agent들에 인입된 초당 트랜잭션 수", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent들의 트랜잭션 수 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent들의 트랙잭션 수의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent들의 트랜잭션 수 중 가장 큰 값" + }] + }] + }], + "ACTIVE_THREAD": [{ + "TITLE": "Active Thread", + "DESC": "사용자 Request를 처리하는 Active Thread 수", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent들의 Active Thread 수 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent들의 Active Thread 수의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent들의 Active Thread 수 중 가장 큰 값" + }] + }] + }], + "RESPONSE_TIME": [{ + "TITLE": "Response Time", + "DESC": "Agent들의 평균 Response Time(단위: millisecond)", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent들의 평균 Response Time 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent들의 평균 Response Time의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent들의 평균 Response Time 중 가장 큰 값" + }] + }] + }], + "DATA_SOURCE": [{ + "TITLE": "Data Source", + "DESC": "Agent들의 DataSource 현황", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent들의 평균 DataSource Connection 개수 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent들의 평균 DataSource Connection 개수의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent들의 평균 DataSource Connection 개수 중 가장 큰 값" + }] + }] + }], + "OPEN_FILE_DESCRIPTOR": [{ + "TITLE": "File Descriptor", + "DESC": "Agent들의 File Descriptor 현황", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent가 열고 있는 File Descriptor 개수 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent가 열고 있는 File Descriptor 개수의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent가 열고 있는 File Descriptor 개수 중 가장 큰 값" + }] + }] + }], + "DIRECT_BUFFER_COUNT": [{ + "TITLE": "Direct Buffer", + "DESC": "Agent들의 Direct Buffer 현황", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent가 사용 중인 Direct Buffer 개수 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent가 사용 중인 Direct Buffer 개수의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent가 사용 중인 Direct Buffer 개수 중 가장 큰 값" + }] + }] + }], + "DIRECT_BUFFER_MEMORY": [{ + "TITLE": "Direct Buffer Memory", + "DESC": "Agent들의 Direct Buffer Memory 사용 현황", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent가 사용 중인 Direct Buffer Memory 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent가 사용 중인 Direct Buffer Memory의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent가 사용 중인 Direct Buffer Memory 중 가장 큰 값" + }] + }] + }], + "MAPPED_BUFFER_COUNT": [{ + "TITLE": "Mapped Buffer", + "DESC": "Agent들의 Mapped Buffer 현황", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent가 사용 중인 Mapped Buffer 개수 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent가 사용 중인 Mapped Buffer 개수의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent가 사용 중인 Mapped Buffer 개수 중 가장 큰 값" + }] + }] + }], + "MAPPED_BUFFER_MEMORY": [{ + "TITLE": "Mapped Buffer Memory", + "DESC": "Agent들의 Mapped Buffer Memory 사용 현황", + "CATEGORY": [{ + "TITLE": "범례", + "ITEMS": [{ + "NAME": "Min", + "DESC": "Agent가 사용 중인 Mapped Buffer Memory 중 가장 작은 값" + }, { + "NAME": "Avg", + "DESC": "Agent가 사용 중인 Mapped Buffer Memory의 평균값" + }, { + "NAME": "Max", + "DESC": "Agent가 사용 중인 Mapped Buffer Memory 중 가장 큰 값" + }] + }] + }] + } + } + } +} diff --git a/web/src/main/webapp/v2/src/assets/img/bg-title-group3.png b/web/src/main/webapp/v2/src/assets/img/bg-title-group3.png new file mode 100755 index 000000000000..bcd222a2b0c0 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/bg-title-group3.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/bidirect_off.png b/web/src/main/webapp/v2/src/assets/img/bidirect_off.png new file mode 100644 index 000000000000..5d447e0184a7 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/bidirect_off.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/bidirect_on.png b/web/src/main/webapp/v2/src/assets/img/bidirect_on.png new file mode 100644 index 000000000000..30e28ac3e770 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/bidirect_on.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/card-btn-arrow-right.png b/web/src/main/webapp/v2/src/assets/img/card-btn-arrow-right.png new file mode 100755 index 000000000000..ff9cd9a7950b Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/card-btn-arrow-right.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/chart1.gif b/web/src/main/webapp/v2/src/assets/img/chart1.gif new file mode 100755 index 000000000000..eacf1a837828 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/chart1.gif differ diff --git a/web/src/main/webapp/v2/src/assets/img/chart2.gif b/web/src/main/webapp/v2/src/assets/img/chart2.gif new file mode 100755 index 000000000000..58dabdd1a5a4 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/chart2.gif differ diff --git a/web/src/main/webapp/v2/src/assets/img/chart3.gif b/web/src/main/webapp/v2/src/assets/img/chart3.gif new file mode 100755 index 000000000000..4e2857a19d0b Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/chart3.gif differ diff --git a/web/src/main/webapp/v2/src/assets/img/chart4.gif b/web/src/main/webapp/v2/src/assets/img/chart4.gif new file mode 100755 index 000000000000..e29e6fd8c9d3 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/chart4.gif differ diff --git a/web/src/main/webapp/v2/src/assets/img/chart5.gif b/web/src/main/webapp/v2/src/assets/img/chart5.gif new file mode 100755 index 000000000000..1ca9873dbb04 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/chart5.gif differ diff --git a/web/src/main/webapp/v2/src/assets/img/chrome.png b/web/src/main/webapp/v2/src/assets/img/chrome.png new file mode 100644 index 000000000000..76c3f15a201c Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/chrome.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/edge.png b/web/src/main/webapp/v2/src/assets/img/edge.png new file mode 100644 index 000000000000..235437527ea8 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/edge.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/firefox.png b/web/src/main/webapp/v2/src/assets/img/firefox.png new file mode 100644 index 000000000000..16815259b4da Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/firefox.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/handle.png b/web/src/main/webapp/v2/src/assets/img/handle.png new file mode 100644 index 000000000000..68f71937e74f Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/handle.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-ABX-WEB-ALPHA.png b/web/src/main/webapp/v2/src/assets/img/icon-ABX-WEB-ALPHA.png new file mode 100755 index 000000000000..dd5b9d07abde Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-ABX-WEB-ALPHA.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-UNKNOWN-GROUP.png b/web/src/main/webapp/v2/src/assets/img/icon-UNKNOWN-GROUP.png new file mode 100755 index 000000000000..e7ba908fc5fc Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-UNKNOWN-GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-alert.png b/web/src/main/webapp/v2/src/assets/img/icon-alert.png new file mode 100755 index 000000000000..878443ecc937 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-alert.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-app-select1.png b/web/src/main/webapp/v2/src/assets/img/icon-app-select1.png new file mode 100755 index 000000000000..1ac5053c90fb Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-app-select1.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-app-select2.png b/web/src/main/webapp/v2/src/assets/img/icon-app-select2.png new file mode 100755 index 000000000000..119f6f4bfc14 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-app-select2.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-arrow.png b/web/src/main/webapp/v2/src/assets/img/icon-arrow.png new file mode 100755 index 000000000000..aac7f783983f Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-arrow.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-calendar-arrow.png b/web/src/main/webapp/v2/src/assets/img/icon-calendar-arrow.png new file mode 100755 index 000000000000..6936837784a3 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-calendar-arrow.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-close.png b/web/src/main/webapp/v2/src/assets/img/icon-close.png new file mode 100755 index 000000000000..1d11f5936206 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-close.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-disconnect.png b/web/src/main/webapp/v2/src/assets/img/icon-disconnect.png new file mode 100755 index 000000000000..ec86f70143a0 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-disconnect.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-down.png b/web/src/main/webapp/v2/src/assets/img/icon-down.png new file mode 100755 index 000000000000..05951ab09f98 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-down.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-error.png b/web/src/main/webapp/v2/src/assets/img/icon-error.png new file mode 100755 index 000000000000..a86b0ce845ab Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-error.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-file-glass.png b/web/src/main/webapp/v2/src/assets/img/icon-file-glass.png new file mode 100755 index 000000000000..2e53bfb0786e Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-file-glass.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icon-header-tool-group.png b/web/src/main/webapp/v2/src/assets/img/icon-header-tool-group.png new file mode 100755 index 000000000000..3b1809332fc3 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icon-header-tool-group.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/ACTIVEMQ_CLIENT.png b/web/src/main/webapp/v2/src/assets/img/icons/ACTIVEMQ_CLIENT.png new file mode 100755 index 000000000000..feed77c8e149 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/ACTIVEMQ_CLIENT.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/ACTIVEMQ_CLIENT_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/ACTIVEMQ_CLIENT_GROUP.png new file mode 100755 index 000000000000..659b52106ab9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/ACTIVEMQ_CLIENT_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/AKKA_HTTP_SERVER.png b/web/src/main/webapp/v2/src/assets/img/icons/AKKA_HTTP_SERVER.png new file mode 100644 index 000000000000..7430bfcc0bb9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/AKKA_HTTP_SERVER.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/APACHE.png b/web/src/main/webapp/v2/src/assets/img/icons/APACHE.png new file mode 100755 index 000000000000..daf44ff710b8 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/APACHE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/ARCUS.png b/web/src/main/webapp/v2/src/assets/img/icons/ARCUS.png new file mode 100755 index 000000000000..562db3b5309e Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/ARCUS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/BACKEND.png b/web/src/main/webapp/v2/src/assets/img/icons/BACKEND.png new file mode 100755 index 000000000000..d308338fd4cf Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/BACKEND.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/BLOC.png b/web/src/main/webapp/v2/src/assets/img/icons/BLOC.png new file mode 100755 index 000000000000..81526d8969fc Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/BLOC.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/CASSANDRA.png b/web/src/main/webapp/v2/src/assets/img/icons/CASSANDRA.png new file mode 100755 index 000000000000..420660979377 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/CASSANDRA.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/CLIENT.png b/web/src/main/webapp/v2/src/assets/img/icons/CLIENT.png new file mode 100755 index 000000000000..53e5faebe6ad Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/CLIENT.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/CUBRID.png b/web/src/main/webapp/v2/src/assets/img/icons/CUBRID.png new file mode 100755 index 000000000000..ba56e674b5b8 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/CUBRID.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/CUBRID_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/CUBRID_GROUP.png new file mode 100755 index 000000000000..0ee0f23eb4f8 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/CUBRID_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/DUBBO_PROVIDER.png b/web/src/main/webapp/v2/src/assets/img/icons/DUBBO_PROVIDER.png new file mode 100755 index 000000000000..a4b47c5fa53a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/DUBBO_PROVIDER.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/DUBBO_PROVIDER_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/DUBBO_PROVIDER_GROUP.png new file mode 100755 index 000000000000..8748a76dd379 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/DUBBO_PROVIDER_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/ETC.png b/web/src/main/webapp/v2/src/assets/img/icons/ETC.png new file mode 100755 index 000000000000..694baac9eaef Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/ETC.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/JBOSS.png b/web/src/main/webapp/v2/src/assets/img/icons/JBOSS.png new file mode 100755 index 000000000000..648f0da1da2a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/JBOSS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/JETTY.png b/web/src/main/webapp/v2/src/assets/img/icons/JETTY.png new file mode 100755 index 000000000000..37bb0594f585 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/JETTY.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/JEUS.png b/web/src/main/webapp/v2/src/assets/img/icons/JEUS.png new file mode 100644 index 000000000000..062029d5e132 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/JEUS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/JEUS_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/JEUS_GROUP.png new file mode 100644 index 000000000000..e7ceb6d3e686 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/JEUS_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/MARIADB.png b/web/src/main/webapp/v2/src/assets/img/icons/MARIADB.png new file mode 100755 index 000000000000..b3850a7c648d Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/MARIADB.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/MARIADB_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/MARIADB_GROUP.png new file mode 100755 index 000000000000..b391b9b5e204 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/MARIADB_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/MEMCACHED.png b/web/src/main/webapp/v2/src/assets/img/icons/MEMCACHED.png new file mode 100755 index 000000000000..eca1ba47f66a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/MEMCACHED.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/MONGODB.png b/web/src/main/webapp/v2/src/assets/img/icons/MONGODB.png new file mode 100755 index 000000000000..4089334d3d7a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/MONGODB.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/MONGODB_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/MONGODB_GROUP.png new file mode 100755 index 000000000000..9114093b2978 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/MONGODB_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/MSSQLSERVER.png b/web/src/main/webapp/v2/src/assets/img/icons/MSSQLSERVER.png new file mode 100755 index 000000000000..3bcac11ba4c4 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/MSSQLSERVER.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/MSSQLSERVER_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/MSSQLSERVER_GROUP.png new file mode 100755 index 000000000000..0bbd653e9e01 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/MSSQLSERVER_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/MYSQL.png b/web/src/main/webapp/v2/src/assets/img/icons/MYSQL.png new file mode 100755 index 000000000000..871363f86686 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/MYSQL.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/MYSQL_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/MYSQL_GROUP.png new file mode 100755 index 000000000000..2e0e8ad7e4f5 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/MYSQL_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/NBASE.png b/web/src/main/webapp/v2/src/assets/img/icons/NBASE.png new file mode 100755 index 000000000000..a8aab8afb3c0 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/NBASE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/NBASE_ARC.png b/web/src/main/webapp/v2/src/assets/img/icons/NBASE_ARC.png new file mode 100755 index 000000000000..b16ca4be159b Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/NBASE_ARC.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/NBASE_ARC_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/NBASE_ARC_GROUP.png new file mode 100755 index 000000000000..7f7cfeeef60b Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/NBASE_ARC_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/NBASE_T.png b/web/src/main/webapp/v2/src/assets/img/icons/NBASE_T.png new file mode 100755 index 000000000000..c607b633b9e1 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/NBASE_T.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/NGINX.png b/web/src/main/webapp/v2/src/assets/img/icons/NGINX.png new file mode 100755 index 000000000000..e208fc77c17f Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/NGINX.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/NO_IMAGE_FOUND.png b/web/src/main/webapp/v2/src/assets/img/icons/NO_IMAGE_FOUND.png new file mode 100644 index 000000000000..28f1a7564285 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/NO_IMAGE_FOUND.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/ORACLE.png b/web/src/main/webapp/v2/src/assets/img/icons/ORACLE.png new file mode 100755 index 000000000000..0158c0d1f7ab Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/ORACLE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/ORACLE_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/ORACLE_GROUP.png new file mode 100755 index 000000000000..88cb2983ad51 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/ORACLE_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/OWFS.png b/web/src/main/webapp/v2/src/assets/img/icons/OWFS.png new file mode 100644 index 000000000000..d6e9e67da3d0 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/OWFS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/OWFS_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/OWFS_GROUP.png new file mode 100644 index 000000000000..6d433eeb17bd Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/OWFS_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/PHP.png b/web/src/main/webapp/v2/src/assets/img/icons/PHP.png new file mode 100755 index 000000000000..b541ec9c0340 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/PHP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/POSTGRESQL.png b/web/src/main/webapp/v2/src/assets/img/icons/POSTGRESQL.png new file mode 100755 index 000000000000..50574a3e4e09 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/POSTGRESQL.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/POSTGRESQL_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/POSTGRESQL_GROUP.png new file mode 100755 index 000000000000..fb524f5dfd18 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/POSTGRESQL_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/QUEUE.png b/web/src/main/webapp/v2/src/assets/img/icons/QUEUE.png new file mode 100755 index 000000000000..a72fba5dbc02 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/QUEUE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/RABBITMQ_CLIENT.png b/web/src/main/webapp/v2/src/assets/img/icons/RABBITMQ_CLIENT.png new file mode 100644 index 000000000000..285c4f0c337d Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/RABBITMQ_CLIENT.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/RABBITMQ_CLIENT_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/RABBITMQ_CLIENT_GROUP.png new file mode 100644 index 000000000000..58b17b2b822a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/RABBITMQ_CLIENT_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/REDIS.png b/web/src/main/webapp/v2/src/assets/img/icons/REDIS.png new file mode 100755 index 000000000000..83389195ee50 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/REDIS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/RESIN.png b/web/src/main/webapp/v2/src/assets/img/icons/RESIN.png new file mode 100644 index 000000000000..08dc754f17de Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/RESIN.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/RESIN_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/RESIN_GROUP.png new file mode 100644 index 000000000000..08dc754f17de Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/RESIN_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/SPRING_BOOT.png b/web/src/main/webapp/v2/src/assets/img/icons/SPRING_BOOT.png new file mode 100755 index 000000000000..8b31511433a2 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/SPRING_BOOT.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/STAND_ALONE.png b/web/src/main/webapp/v2/src/assets/img/icons/STAND_ALONE.png new file mode 100755 index 000000000000..143397716ae5 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/STAND_ALONE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/TOMCAT.png b/web/src/main/webapp/v2/src/assets/img/icons/TOMCAT.png new file mode 100755 index 000000000000..0e36cbe21770 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/TOMCAT.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/UNAUTHORIZED.png b/web/src/main/webapp/v2/src/assets/img/icons/UNAUTHORIZED.png new file mode 100755 index 000000000000..a368a062145c Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/UNAUTHORIZED.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/UNDEFINED.png b/web/src/main/webapp/v2/src/assets/img/icons/UNDEFINED.png new file mode 100755 index 000000000000..4cbfe55be4c0 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/UNDEFINED.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/UNDEFINED_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/UNDEFINED_GROUP.png new file mode 100755 index 000000000000..4cbfe55be4c0 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/UNDEFINED_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/UNKNOWN.png b/web/src/main/webapp/v2/src/assets/img/icons/UNKNOWN.png new file mode 100755 index 000000000000..7c130ee3602a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/UNKNOWN.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/UNKNOWN_CLOUD.png b/web/src/main/webapp/v2/src/assets/img/icons/UNKNOWN_CLOUD.png new file mode 100755 index 000000000000..28dbb1874a39 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/UNKNOWN_CLOUD.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/UNKNOWN_GROUP.png b/web/src/main/webapp/v2/src/assets/img/icons/UNKNOWN_GROUP.png new file mode 100755 index 000000000000..5ba06b27d86d Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/UNKNOWN_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/USER.png b/web/src/main/webapp/v2/src/assets/img/icons/USER.png new file mode 100755 index 000000000000..c4f80a90fe99 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/USER.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/USER1.png b/web/src/main/webapp/v2/src/assets/img/icons/USER1.png new file mode 100755 index 000000000000..a1545c0f7323 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/USER1.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/VERTX.png b/web/src/main/webapp/v2/src/assets/img/icons/VERTX.png new file mode 100644 index 000000000000..ed1f3bd955d2 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/VERTX.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/WEBLOGIC.png b/web/src/main/webapp/v2/src/assets/img/icons/WEBLOGIC.png new file mode 100644 index 000000000000..a47f63d9809a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/WEBLOGIC.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/WEBSPHERE.png b/web/src/main/webapp/v2/src/assets/img/icons/WEBSPHERE.png new file mode 100644 index 000000000000..771506cfe83d Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/WEBSPHERE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/filter.png b/web/src/main/webapp/v2/src/assets/img/icons/filter.png new file mode 100755 index 000000000000..75d1790acc8c Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/filter.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/ng.png b/web/src/main/webapp/v2/src/assets/img/icons/ng.png new file mode 100755 index 000000000000..8d5831044f8a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/ng.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/icons/sq.png b/web/src/main/webapp/v2/src/assets/img/icons/sq.png new file mode 100755 index 000000000000..fc5110340116 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/icons/sq.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/logo.png b/web/src/main/webapp/v2/src/assets/img/logo.png new file mode 100755 index 000000000000..f8126f91c816 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/logo.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/safari.png b/web/src/main/webapp/v2/src/assets/img/safari.png new file mode 100644 index 000000000000..bd31983aad34 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/safari.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/select-down-arrow.png b/web/src/main/webapp/v2/src/assets/img/select-down-arrow.png new file mode 100755 index 000000000000..241b02a69087 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/select-down-arrow.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/ACTIVEMQ_CLIENT.png b/web/src/main/webapp/v2/src/assets/img/servermap/ACTIVEMQ_CLIENT.png new file mode 100755 index 000000000000..d34b3cbfbb85 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/ACTIVEMQ_CLIENT.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/ACTIVEMQ_CLIENT_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/ACTIVEMQ_CLIENT_GROUP.png new file mode 100644 index 000000000000..9287431fc23f Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/ACTIVEMQ_CLIENT_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/AKKA_HTTP_SERVER.png b/web/src/main/webapp/v2/src/assets/img/servermap/AKKA_HTTP_SERVER.png new file mode 100644 index 000000000000..29fa4249ab82 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/AKKA_HTTP_SERVER.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/APACHE.png b/web/src/main/webapp/v2/src/assets/img/servermap/APACHE.png new file mode 100755 index 000000000000..b3253187cefb Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/APACHE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/APACHE_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/APACHE_GROUP.png new file mode 100644 index 000000000000..33ea8dd4086d Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/APACHE_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/ARCUS.png b/web/src/main/webapp/v2/src/assets/img/servermap/ARCUS.png new file mode 100755 index 000000000000..69bb0de7fe1c Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/ARCUS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/ARCUS_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/ARCUS_GROUP.png new file mode 100644 index 000000000000..79eb205fa5ba Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/ARCUS_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/BACKEND.png b/web/src/main/webapp/v2/src/assets/img/servermap/BACKEND.png new file mode 100755 index 000000000000..d308338fd4cf Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/BACKEND.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/BACKEND_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/BACKEND_GROUP.png new file mode 100644 index 000000000000..d308338fd4cf Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/BACKEND_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/BLOC.png b/web/src/main/webapp/v2/src/assets/img/servermap/BLOC.png new file mode 100755 index 000000000000..c1fccc5643dd Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/BLOC.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/BLOC_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/BLOC_GROUP.png new file mode 100644 index 000000000000..58a074e4ac28 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/BLOC_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/CASSANDRA.png b/web/src/main/webapp/v2/src/assets/img/servermap/CASSANDRA.png new file mode 100755 index 000000000000..8e5d9d1d6731 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/CASSANDRA.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/CASSANDRA_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/CASSANDRA_GROUP.png new file mode 100644 index 000000000000..a958d682e6f9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/CASSANDRA_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/CUBRID.png b/web/src/main/webapp/v2/src/assets/img/servermap/CUBRID.png new file mode 100755 index 000000000000..b6e1fb851843 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/CUBRID.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/CUBRID_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/CUBRID_GROUP.png new file mode 100644 index 000000000000..2f26043fa5fa Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/CUBRID_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/DUBBO.png b/web/src/main/webapp/v2/src/assets/img/servermap/DUBBO.png new file mode 100755 index 000000000000..5b11f569dfef Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/DUBBO.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/DUBBO_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/DUBBO_GROUP.png new file mode 100644 index 000000000000..a7009ef4e410 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/DUBBO_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/ERROR.png b/web/src/main/webapp/v2/src/assets/img/servermap/ERROR.png new file mode 100755 index 000000000000..e558882889ff Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/ERROR.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/ERROR_s.png b/web/src/main/webapp/v2/src/assets/img/servermap/ERROR_s.png new file mode 100644 index 000000000000..d4efdced5532 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/ERROR_s.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/FILTER.png b/web/src/main/webapp/v2/src/assets/img/servermap/FILTER.png new file mode 100755 index 000000000000..c90e45781f46 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/FILTER.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/FILTER_s.png b/web/src/main/webapp/v2/src/assets/img/servermap/FILTER_s.png new file mode 100755 index 000000000000..bb8fc09b2453 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/FILTER_s.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/JAVA.png b/web/src/main/webapp/v2/src/assets/img/servermap/JAVA.png new file mode 100755 index 000000000000..9c62a4b8a814 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/JAVA.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/JAVA_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/JAVA_GROUP.png new file mode 100644 index 000000000000..a03cdf6b1aad Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/JAVA_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/JBOSS.png b/web/src/main/webapp/v2/src/assets/img/servermap/JBOSS.png new file mode 100755 index 000000000000..b5a983b139c4 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/JBOSS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/JBOSS_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/JBOSS_GROUP.png new file mode 100644 index 000000000000..e46b9af79b17 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/JBOSS_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/JETTY.png b/web/src/main/webapp/v2/src/assets/img/servermap/JETTY.png new file mode 100755 index 000000000000..3e2958c3a0c9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/JETTY.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/JETTY_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/JETTY_GROUP.png new file mode 100644 index 000000000000..1683ab415557 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/JETTY_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/JEUS.png b/web/src/main/webapp/v2/src/assets/img/servermap/JEUS.png new file mode 100644 index 000000000000..e1fbffeab3c7 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/JEUS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/JEUS_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/JEUS_GROUP.png new file mode 100644 index 000000000000..e1fbffeab3c7 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/JEUS_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MARIADB.png b/web/src/main/webapp/v2/src/assets/img/servermap/MARIADB.png new file mode 100755 index 000000000000..7d14c000accb Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MARIADB.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MARIADB_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/MARIADB_GROUP.png new file mode 100644 index 000000000000..4d1672bcc7cd Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MARIADB_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MEMCACHED.png b/web/src/main/webapp/v2/src/assets/img/servermap/MEMCACHED.png new file mode 100755 index 000000000000..db8c074a5881 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MEMCACHED.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MEMCACHED_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/MEMCACHED_GROUP.png new file mode 100644 index 000000000000..5fc76a66feab Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MEMCACHED_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MONGODB.png b/web/src/main/webapp/v2/src/assets/img/servermap/MONGODB.png new file mode 100755 index 000000000000..fc76c931bacc Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MONGODB.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MONGODB_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/MONGODB_GROUP.png new file mode 100644 index 000000000000..5848a6f0c2f5 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MONGODB_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MSSQLSERVER.png b/web/src/main/webapp/v2/src/assets/img/servermap/MSSQLSERVER.png new file mode 100755 index 000000000000..8236ef45dca9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MSSQLSERVER.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MSSQLSERVER_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/MSSQLSERVER_GROUP.png new file mode 100644 index 000000000000..11a1609e24a3 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MSSQLSERVER_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MYSQL.png b/web/src/main/webapp/v2/src/assets/img/servermap/MYSQL.png new file mode 100644 index 000000000000..2628c29c3e2b Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MYSQL.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/MYSQL_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/MYSQL_GROUP.png new file mode 100644 index 000000000000..2628c29c3e2b Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/MYSQL_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/NBASE.png b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE.png new file mode 100755 index 000000000000..2eacc0c66c0f Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_ARC.png b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_ARC.png new file mode 100755 index 000000000000..a0668e6c51f6 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_ARC.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_ARC_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_ARC_GROUP.png new file mode 100644 index 000000000000..2a8cdfe2f987 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_ARC_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_GROUP.png new file mode 100644 index 000000000000..f25a35834884 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_T.png b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_T.png new file mode 100755 index 000000000000..610f58cbfb79 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_T.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_T_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_T_GROUP.png new file mode 100644 index 000000000000..f25a35834884 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/NBASE_T_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/NGINX.png b/web/src/main/webapp/v2/src/assets/img/servermap/NGINX.png new file mode 100755 index 000000000000..8581672516f2 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/NGINX.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/NGINX_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/NGINX_GROUP.png new file mode 100644 index 000000000000..0bb2909e6142 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/NGINX_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/NO_IMAGE_FOUND.png b/web/src/main/webapp/v2/src/assets/img/servermap/NO_IMAGE_FOUND.png new file mode 100644 index 000000000000..07afde3fdb02 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/NO_IMAGE_FOUND.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/ORACLE.png b/web/src/main/webapp/v2/src/assets/img/servermap/ORACLE.png new file mode 100755 index 000000000000..2fc835440d88 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/ORACLE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/ORACLE_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/ORACLE_GROUP.png new file mode 100644 index 000000000000..103014aee2ef Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/ORACLE_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/OWFS.png b/web/src/main/webapp/v2/src/assets/img/servermap/OWFS.png new file mode 100644 index 000000000000..bf6e29a4a6ab Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/OWFS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/OWFS_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/OWFS_GROUP.png new file mode 100644 index 000000000000..bf6e29a4a6ab Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/OWFS_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/PHP.png b/web/src/main/webapp/v2/src/assets/img/servermap/PHP.png new file mode 100755 index 000000000000..1bd6443e7bc1 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/PHP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/POSTGRESQL.png b/web/src/main/webapp/v2/src/assets/img/servermap/POSTGRESQL.png new file mode 100755 index 000000000000..6a089eb23d5f Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/POSTGRESQL.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/POSTGRESQL_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/POSTGRESQL_GROUP.png new file mode 100644 index 000000000000..10a0945c198d Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/POSTGRESQL_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/QUEUE.png b/web/src/main/webapp/v2/src/assets/img/servermap/QUEUE.png new file mode 100755 index 000000000000..a72fba5dbc02 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/QUEUE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/QUEUE_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/QUEUE_GROUP.png new file mode 100755 index 000000000000..a72fba5dbc02 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/QUEUE_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/RABBITMQ_CLIENT.png b/web/src/main/webapp/v2/src/assets/img/servermap/RABBITMQ_CLIENT.png new file mode 100644 index 000000000000..d33071d6f6cb Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/RABBITMQ_CLIENT.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/RABBITMQ_CLIENT_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/RABBITMQ_CLIENT_GROUP.png new file mode 100644 index 000000000000..d33071d6f6cb Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/RABBITMQ_CLIENT_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/REDIS.png b/web/src/main/webapp/v2/src/assets/img/servermap/REDIS.png new file mode 100755 index 000000000000..62f25c9918dc Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/REDIS.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/REDIS_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/REDIS_GROUP.png new file mode 100644 index 000000000000..abb69c09ec51 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/REDIS_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/RESIN.png b/web/src/main/webapp/v2/src/assets/img/servermap/RESIN.png new file mode 100644 index 000000000000..ff65640ff633 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/RESIN.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/RESIN_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/RESIN_GROUP.png new file mode 100644 index 000000000000..ff65640ff633 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/RESIN_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/SPRING_BOOT.png b/web/src/main/webapp/v2/src/assets/img/servermap/SPRING_BOOT.png new file mode 100755 index 000000000000..a31b3b03365e Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/SPRING_BOOT.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/SPRING_BOOT_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/SPRING_BOOT_GROUP.png new file mode 100644 index 000000000000..0ccdef63a2dc Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/SPRING_BOOT_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/STAND_ALONE.png b/web/src/main/webapp/v2/src/assets/img/servermap/STAND_ALONE.png new file mode 100755 index 000000000000..4fcbe81c8a83 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/STAND_ALONE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/STAND_ALONE2.png b/web/src/main/webapp/v2/src/assets/img/servermap/STAND_ALONE2.png new file mode 100644 index 000000000000..28f78a703c3c Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/STAND_ALONE2.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/STAND_ALONE_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/STAND_ALONE_GROUP.png new file mode 100644 index 000000000000..2b461a8f74ef Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/STAND_ALONE_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/TOMCAT.png b/web/src/main/webapp/v2/src/assets/img/servermap/TOMCAT.png new file mode 100755 index 000000000000..4fcbe81c8a83 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/TOMCAT.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/TOMCAT_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/TOMCAT_GROUP.png new file mode 100644 index 000000000000..59a11feca358 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/TOMCAT_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/UNAUTHORIZED.png b/web/src/main/webapp/v2/src/assets/img/servermap/UNAUTHORIZED.png new file mode 100755 index 000000000000..a3ffe9177445 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/UNAUTHORIZED.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/UNAUTHORIZED_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/UNAUTHORIZED_GROUP.png new file mode 100644 index 000000000000..8b05bc24141b Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/UNAUTHORIZED_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/UNDEFINED.png b/web/src/main/webapp/v2/src/assets/img/servermap/UNDEFINED.png new file mode 100755 index 000000000000..d46a1159d94d Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/UNDEFINED.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/UNDEFINED_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/UNDEFINED_GROUP.png new file mode 100644 index 000000000000..3e75eab0564c Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/UNDEFINED_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/UNKNOWN.png b/web/src/main/webapp/v2/src/assets/img/servermap/UNKNOWN.png new file mode 100755 index 000000000000..73c53de2c0ea Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/UNKNOWN.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/UNKNOWN_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/UNKNOWN_GROUP.png new file mode 100755 index 000000000000..ae4c7bc4f392 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/UNKNOWN_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/USER.png b/web/src/main/webapp/v2/src/assets/img/servermap/USER.png new file mode 100755 index 000000000000..d7e67da244c1 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/USER.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/USER_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/USER_GROUP.png new file mode 100644 index 000000000000..69f473863dbf Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/USER_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/VERTX.png b/web/src/main/webapp/v2/src/assets/img/servermap/VERTX.png new file mode 100644 index 000000000000..1a4ec3e1d87a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/VERTX.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/VERTX_GROUP.png b/web/src/main/webapp/v2/src/assets/img/servermap/VERTX_GROUP.png new file mode 100644 index 000000000000..1a4ec3e1d87a Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/VERTX_GROUP.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/WEBLOGIC.png b/web/src/main/webapp/v2/src/assets/img/servermap/WEBLOGIC.png new file mode 100644 index 000000000000..fae014c4eac0 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/WEBLOGIC.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/servermap/WEBSPHERE.png b/web/src/main/webapp/v2/src/assets/img/servermap/WEBSPHERE.png new file mode 100644 index 000000000000..c862167c2cd9 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/servermap/WEBSPHERE.png differ diff --git a/web/src/main/webapp/v2/src/assets/img/text-layer-arrow.png b/web/src/main/webapp/v2/src/assets/img/text-layer-arrow.png new file mode 100755 index 000000000000..f13365650030 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/img/text-layer-arrow.png differ diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/highlight.pack.js b/web/src/main/webapp/v2/src/assets/lib/hljs/highlight.pack.js new file mode 100644 index 000000000000..9368b50d4c2f --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/highlight.pack.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}}); \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/agate.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/agate.css new file mode 100644 index 000000000000..8d64547c5876 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/agate.css @@ -0,0 +1,108 @@ +/*! + * Agate by Taufik Nurrohman + * ---------------------------------------------------- + * + * #ade5fc + * #a2fca2 + * #c6b4f0 + * #d36363 + * #fcc28c + * #fc9b9b + * #ffa + * #fff + * #333 + * #62c8f3 + * #888 + * + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #333; + color: white; +} + +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-code, +.hljs-emphasis { + font-style: italic; +} + +.hljs-tag { + color: #62c8f3; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-selector-id, +.hljs-selector-class { + color: #ade5fc; +} + +.hljs-string, +.hljs-bullet { + color: #a2fca2; +} + +.hljs-type, +.hljs-title, +.hljs-section, +.hljs-attribute, +.hljs-quote, +.hljs-built_in, +.hljs-builtin-name { + color: #ffa; +} + +.hljs-number, +.hljs-symbol, +.hljs-bullet { + color: #d36363; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color: #fcc28c; +} + +.hljs-comment, +.hljs-deletion, +.hljs-code { + color: #888; +} + +.hljs-regexp, +.hljs-link { + color: #c6b4f0; +} + +.hljs-meta { + color: #fc9b9b; +} + +.hljs-deletion { + background-color: #fc9b9b; + color: #333; +} + +.hljs-addition { + background-color: #a2fca2; + color: #333; +} + +.hljs a { + color: inherit; +} + +.hljs a:focus, +.hljs a:hover { + color: inherit; + text-decoration: underline; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/androidstudio.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/androidstudio.css new file mode 100644 index 000000000000..bc8e473b5934 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/androidstudio.css @@ -0,0 +1,66 @@ +/* +Date: 24 Fev 2015 +Author: Pedro Oliveira +*/ + +.hljs { + color: #a9b7c6; + background: #282b2e; + display: block; + overflow-x: auto; + padding: 0.5em; +} + +.hljs-number, +.hljs-literal, +.hljs-symbol, +.hljs-bullet { + color: #6897BB; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-deletion { + color: #cc7832; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-link { + color: #629755; +} + +.hljs-comment, +.hljs-quote { + color: #808080; +} + +.hljs-meta { + color: #bbb529; +} + +.hljs-string, +.hljs-attribute, +.hljs-addition { + color: #6A8759; +} + +.hljs-section, +.hljs-title, +.hljs-type { + color: #ffc66d; +} + +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #e8bf6a; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/arduino-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/arduino-light.css new file mode 100644 index 000000000000..4b8b7fd3c935 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/arduino-light.css @@ -0,0 +1,88 @@ +/* + +Arduino® Light Theme - Stefania Mellai + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #FFFFFF; +} + +.hljs, +.hljs-subst { + color: #434f54; +} + +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-doctag, +.hljs-name { + color: #00979D; +} + +.hljs-built_in, +.hljs-literal, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #D35400; +} + +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #00979D; +} + +.hljs-type, +.hljs-string, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #005C5F; +} + +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} + +.hljs-comment { + color: rgba(149,165,166,.8); +} + +.hljs-meta-keyword { + color: #728E00; +} + +.hljs-meta { + color: #728E00; + color: #434f54; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-function { + color: #728E00; +} + +.hljs-number { + color: #8A7B52; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/arta.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/arta.css new file mode 100644 index 000000000000..75ef3a9e5955 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/arta.css @@ -0,0 +1,73 @@ +/* +Date: 17.V.2011 +Author: pumbur +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #222; +} + +.hljs, +.hljs-subst { + color: #aaa; +} + +.hljs-section { + color: #fff; +} + +.hljs-comment, +.hljs-quote, +.hljs-meta { + color: #444; +} + +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-regexp { + color: #ffcc33; +} + +.hljs-number, +.hljs-addition { + color: #00cc66; +} + +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-template-variable, +.hljs-attribute, +.hljs-link { + color: #32aaee; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #6644aa; +} + +.hljs-title, +.hljs-variable, +.hljs-deletion, +.hljs-template-tag { + color: #bb1166; +} + +.hljs-section, +.hljs-doctag, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/ascetic.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/ascetic.css new file mode 100644 index 000000000000..48397e889dd1 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/ascetic.css @@ -0,0 +1,45 @@ +/* + +Original style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-symbol, +.hljs-bullet, +.hljs-section, +.hljs-addition, +.hljs-attribute, +.hljs-link { + color: #888; +} + +.hljs-comment, +.hljs-quote, +.hljs-meta, +.hljs-deletion { + color: #ccc; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-section, +.hljs-name, +.hljs-type, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-cave-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-cave-dark.css new file mode 100644 index 000000000000..65428f3b12a2 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-cave-dark.css @@ -0,0 +1,83 @@ +/* Base16 Atelier Cave Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Cave Comment */ +.hljs-comment, +.hljs-quote { + color: #7e7887; +} + +/* Atelier-Cave Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-regexp, +.hljs-link, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #be4678; +} + +/* Atelier-Cave Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #aa573c; +} + +/* Atelier-Cave Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #2a9292; +} + +/* Atelier-Cave Blue */ +.hljs-title, +.hljs-section { + color: #576ddb; +} + +/* Atelier-Cave Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #955ae7; +} + +.hljs-deletion, +.hljs-addition { + color: #19171c; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #be4678; +} + +.hljs-addition { + background-color: #2a9292; +} + +.hljs { + display: block; + overflow-x: auto; + background: #19171c; + color: #8b8792; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-cave-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-cave-light.css new file mode 100644 index 000000000000..b419f9fd8f87 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-cave-light.css @@ -0,0 +1,85 @@ +/* Base16 Atelier Cave Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Cave Comment */ +.hljs-comment, +.hljs-quote { + color: #655f6d; +} + +/* Atelier-Cave Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #be4678; +} + +/* Atelier-Cave Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #aa573c; +} + +/* Atelier-Cave Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #2a9292; +} + +/* Atelier-Cave Blue */ +.hljs-title, +.hljs-section { + color: #576ddb; +} + +/* Atelier-Cave Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #955ae7; +} + +.hljs-deletion, +.hljs-addition { + color: #19171c; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #be4678; +} + +.hljs-addition { + background-color: #2a9292; +} + +.hljs { + display: block; + overflow-x: auto; + background: #efecf4; + color: #585260; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-dune-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-dune-dark.css new file mode 100644 index 000000000000..1684f5225af9 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-dune-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Dune Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Dune Comment */ +.hljs-comment, +.hljs-quote { + color: #999580; +} + +/* Atelier-Dune Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d73737; +} + +/* Atelier-Dune Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b65611; +} + +/* Atelier-Dune Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #60ac39; +} + +/* Atelier-Dune Blue */ +.hljs-title, +.hljs-section { + color: #6684e1; +} + +/* Atelier-Dune Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b854d4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #20201d; + color: #a6a28c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-dune-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-dune-light.css new file mode 100644 index 000000000000..547719de8262 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-dune-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Dune Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Dune Comment */ +.hljs-comment, +.hljs-quote { + color: #7d7a68; +} + +/* Atelier-Dune Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d73737; +} + +/* Atelier-Dune Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b65611; +} + +/* Atelier-Dune Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #60ac39; +} + +/* Atelier-Dune Blue */ +.hljs-title, +.hljs-section { + color: #6684e1; +} + +/* Atelier-Dune Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b854d4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #fefbec; + color: #6e6b5e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-estuary-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-estuary-dark.css new file mode 100644 index 000000000000..a5e507187e95 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-estuary-dark.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Estuary Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/estuary) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Estuary Comment */ +.hljs-comment, +.hljs-quote { + color: #878573; +} + +/* Atelier-Estuary Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ba6236; +} + +/* Atelier-Estuary Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #ae7313; +} + +/* Atelier-Estuary Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #7d9726; +} + +/* Atelier-Estuary Blue */ +.hljs-title, +.hljs-section { + color: #36a166; +} + +/* Atelier-Estuary Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #5f9182; +} + +.hljs-deletion, +.hljs-addition { + color: #22221b; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #ba6236; +} + +.hljs-addition { + background-color: #7d9726; +} + +.hljs { + display: block; + overflow-x: auto; + background: #22221b; + color: #929181; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-estuary-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-estuary-light.css new file mode 100644 index 000000000000..1daee5d98556 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-estuary-light.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Estuary Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/estuary) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Estuary Comment */ +.hljs-comment, +.hljs-quote { + color: #6c6b5a; +} + +/* Atelier-Estuary Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ba6236; +} + +/* Atelier-Estuary Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #ae7313; +} + +/* Atelier-Estuary Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #7d9726; +} + +/* Atelier-Estuary Blue */ +.hljs-title, +.hljs-section { + color: #36a166; +} + +/* Atelier-Estuary Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #5f9182; +} + +.hljs-deletion, +.hljs-addition { + color: #22221b; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #ba6236; +} + +.hljs-addition { + background-color: #7d9726; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f4f3ec; + color: #5f5e4e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-forest-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-forest-dark.css new file mode 100644 index 000000000000..0ef4fae31748 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-forest-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Forest Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Forest Comment */ +.hljs-comment, +.hljs-quote { + color: #9c9491; +} + +/* Atelier-Forest Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #f22c40; +} + +/* Atelier-Forest Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #df5320; +} + +/* Atelier-Forest Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #7b9726; +} + +/* Atelier-Forest Blue */ +.hljs-title, +.hljs-section { + color: #407ee7; +} + +/* Atelier-Forest Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6666ea; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1b1918; + color: #a8a19f; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-forest-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-forest-light.css new file mode 100644 index 000000000000..bbedde18a0a8 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-forest-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Forest Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Forest Comment */ +.hljs-comment, +.hljs-quote { + color: #766e6b; +} + +/* Atelier-Forest Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #f22c40; +} + +/* Atelier-Forest Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #df5320; +} + +/* Atelier-Forest Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #7b9726; +} + +/* Atelier-Forest Blue */ +.hljs-title, +.hljs-section { + color: #407ee7; +} + +/* Atelier-Forest Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6666ea; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f1efee; + color: #68615e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-heath-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-heath-dark.css new file mode 100644 index 000000000000..fe01ff721b9d --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-heath-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Heath Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Heath Comment */ +.hljs-comment, +.hljs-quote { + color: #9e8f9e; +} + +/* Atelier-Heath Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ca402b; +} + +/* Atelier-Heath Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #a65926; +} + +/* Atelier-Heath Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #918b3b; +} + +/* Atelier-Heath Blue */ +.hljs-title, +.hljs-section { + color: #516aec; +} + +/* Atelier-Heath Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #7b59c0; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1b181b; + color: #ab9bab; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-heath-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-heath-light.css new file mode 100644 index 000000000000..ee43786d12e9 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-heath-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Heath Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Heath Comment */ +.hljs-comment, +.hljs-quote { + color: #776977; +} + +/* Atelier-Heath Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ca402b; +} + +/* Atelier-Heath Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #a65926; +} + +/* Atelier-Heath Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #918b3b; +} + +/* Atelier-Heath Blue */ +.hljs-title, +.hljs-section { + color: #516aec; +} + +/* Atelier-Heath Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #7b59c0; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f7f3f7; + color: #695d69; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-lakeside-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-lakeside-dark.css new file mode 100644 index 000000000000..a937d3bf5f72 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-lakeside-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Lakeside Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Lakeside Comment */ +.hljs-comment, +.hljs-quote { + color: #7195a8; +} + +/* Atelier-Lakeside Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d22d72; +} + +/* Atelier-Lakeside Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #935c25; +} + +/* Atelier-Lakeside Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #568c3b; +} + +/* Atelier-Lakeside Blue */ +.hljs-title, +.hljs-section { + color: #257fad; +} + +/* Atelier-Lakeside Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6b6bb8; +} + +.hljs { + display: block; + overflow-x: auto; + background: #161b1d; + color: #7ea2b4; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-lakeside-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-lakeside-light.css new file mode 100644 index 000000000000..6c7e8f9ef2d2 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-lakeside-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Lakeside Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Lakeside Comment */ +.hljs-comment, +.hljs-quote { + color: #5a7b8c; +} + +/* Atelier-Lakeside Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d22d72; +} + +/* Atelier-Lakeside Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #935c25; +} + +/* Atelier-Lakeside Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #568c3b; +} + +/* Atelier-Lakeside Blue */ +.hljs-title, +.hljs-section { + color: #257fad; +} + +/* Atelier-Lakeside Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6b6bb8; +} + +.hljs { + display: block; + overflow-x: auto; + background: #ebf8ff; + color: #516d7b; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-plateau-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-plateau-dark.css new file mode 100644 index 000000000000..3bb052693c1c --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-plateau-dark.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Plateau Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/plateau) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Plateau Comment */ +.hljs-comment, +.hljs-quote { + color: #7e7777; +} + +/* Atelier-Plateau Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ca4949; +} + +/* Atelier-Plateau Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b45a3c; +} + +/* Atelier-Plateau Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #4b8b8b; +} + +/* Atelier-Plateau Blue */ +.hljs-title, +.hljs-section { + color: #7272ca; +} + +/* Atelier-Plateau Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #8464c4; +} + +.hljs-deletion, +.hljs-addition { + color: #1b1818; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #ca4949; +} + +.hljs-addition { + background-color: #4b8b8b; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1b1818; + color: #8a8585; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-plateau-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-plateau-light.css new file mode 100644 index 000000000000..5f0222bec1f3 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-plateau-light.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Plateau Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/plateau) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Plateau Comment */ +.hljs-comment, +.hljs-quote { + color: #655d5d; +} + +/* Atelier-Plateau Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ca4949; +} + +/* Atelier-Plateau Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b45a3c; +} + +/* Atelier-Plateau Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #4b8b8b; +} + +/* Atelier-Plateau Blue */ +.hljs-title, +.hljs-section { + color: #7272ca; +} + +/* Atelier-Plateau Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #8464c4; +} + +.hljs-deletion, +.hljs-addition { + color: #1b1818; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #ca4949; +} + +.hljs-addition { + background-color: #4b8b8b; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f4ecec; + color: #585050; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-savanna-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-savanna-dark.css new file mode 100644 index 000000000000..38f831431c37 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-savanna-dark.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Savanna Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/savanna) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Savanna Comment */ +.hljs-comment, +.hljs-quote { + color: #78877d; +} + +/* Atelier-Savanna Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #b16139; +} + +/* Atelier-Savanna Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #9f713c; +} + +/* Atelier-Savanna Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #489963; +} + +/* Atelier-Savanna Blue */ +.hljs-title, +.hljs-section { + color: #478c90; +} + +/* Atelier-Savanna Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #55859b; +} + +.hljs-deletion, +.hljs-addition { + color: #171c19; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #b16139; +} + +.hljs-addition { + background-color: #489963; +} + +.hljs { + display: block; + overflow-x: auto; + background: #171c19; + color: #87928a; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-savanna-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-savanna-light.css new file mode 100644 index 000000000000..1ccd7c6858f9 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-savanna-light.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Savanna Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/savanna) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Savanna Comment */ +.hljs-comment, +.hljs-quote { + color: #5f6d64; +} + +/* Atelier-Savanna Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #b16139; +} + +/* Atelier-Savanna Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #9f713c; +} + +/* Atelier-Savanna Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #489963; +} + +/* Atelier-Savanna Blue */ +.hljs-title, +.hljs-section { + color: #478c90; +} + +/* Atelier-Savanna Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #55859b; +} + +.hljs-deletion, +.hljs-addition { + color: #171c19; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #b16139; +} + +.hljs-addition { + background-color: #489963; +} + +.hljs { + display: block; + overflow-x: auto; + background: #ecf4ee; + color: #526057; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-seaside-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-seaside-dark.css new file mode 100644 index 000000000000..df29949c69f5 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-seaside-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Seaside Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Seaside Comment */ +.hljs-comment, +.hljs-quote { + color: #809980; +} + +/* Atelier-Seaside Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #e6193c; +} + +/* Atelier-Seaside Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #87711d; +} + +/* Atelier-Seaside Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #29a329; +} + +/* Atelier-Seaside Blue */ +.hljs-title, +.hljs-section { + color: #3d62f5; +} + +/* Atelier-Seaside Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #ad2bee; +} + +.hljs { + display: block; + overflow-x: auto; + background: #131513; + color: #8ca68c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-seaside-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-seaside-light.css new file mode 100644 index 000000000000..9d960f29f385 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-seaside-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Seaside Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Seaside Comment */ +.hljs-comment, +.hljs-quote { + color: #687d68; +} + +/* Atelier-Seaside Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #e6193c; +} + +/* Atelier-Seaside Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #87711d; +} + +/* Atelier-Seaside Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #29a329; +} + +/* Atelier-Seaside Blue */ +.hljs-title, +.hljs-section { + color: #3d62f5; +} + +/* Atelier-Seaside Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #ad2bee; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f4fbf4; + color: #5e6e5e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-sulphurpool-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-sulphurpool-dark.css new file mode 100644 index 000000000000..c2ab7938d843 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-sulphurpool-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Sulphurpool Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Sulphurpool Comment */ +.hljs-comment, +.hljs-quote { + color: #898ea4; +} + +/* Atelier-Sulphurpool Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #c94922; +} + +/* Atelier-Sulphurpool Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #c76b29; +} + +/* Atelier-Sulphurpool Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #ac9739; +} + +/* Atelier-Sulphurpool Blue */ +.hljs-title, +.hljs-section { + color: #3d8fd1; +} + +/* Atelier-Sulphurpool Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6679cc; +} + +.hljs { + display: block; + overflow-x: auto; + background: #202746; + color: #979db4; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-sulphurpool-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-sulphurpool-light.css new file mode 100644 index 000000000000..96c47d086081 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atelier-sulphurpool-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Sulphurpool Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Sulphurpool Comment */ +.hljs-comment, +.hljs-quote { + color: #6b7394; +} + +/* Atelier-Sulphurpool Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #c94922; +} + +/* Atelier-Sulphurpool Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #c76b29; +} + +/* Atelier-Sulphurpool Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #ac9739; +} + +/* Atelier-Sulphurpool Blue */ +.hljs-title, +.hljs-section { + color: #3d8fd1; +} + +/* Atelier-Sulphurpool Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6679cc; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f5f7ff; + color: #5e6687; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atom-one-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atom-one-dark.css new file mode 100644 index 000000000000..1616aafe3157 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atom-one-dark.css @@ -0,0 +1,96 @@ +/* + +Atom One Dark by Daniel Gamage +Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax + +base: #282c34 +mono-1: #abb2bf +mono-2: #818896 +mono-3: #5c6370 +hue-1: #56b6c2 +hue-2: #61aeee +hue-3: #c678dd +hue-4: #98c379 +hue-5: #e06c75 +hue-5-2: #be5046 +hue-6: #d19a66 +hue-6-2: #e6c07b + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #abb2bf; + background: #282c34; +} + +.hljs-comment, +.hljs-quote { + color: #5c6370; + font-style: italic; +} + +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #c678dd; +} + +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e06c75; +} + +.hljs-literal { + color: #56b6c2; +} + +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta-string { + color: #98c379; +} + +.hljs-built_in, +.hljs-class .hljs-title { + color: #e6c07b; +} + +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #d19a66; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #61aeee; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atom-one-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atom-one-light.css new file mode 100644 index 000000000000..d5bd1d2a9a3a --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/atom-one-light.css @@ -0,0 +1,96 @@ +/* + +Atom One Light by Daniel Gamage +Original One Light Syntax theme from https://github.com/atom/one-light-syntax + +base: #fafafa +mono-1: #383a42 +mono-2: #686b77 +mono-3: #a0a1a7 +hue-1: #0184bb +hue-2: #4078f2 +hue-3: #a626a4 +hue-4: #50a14f +hue-5: #e45649 +hue-5-2: #c91243 +hue-6: #986801 +hue-6-2: #c18401 + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #383a42; + background: #fafafa; +} + +.hljs-comment, +.hljs-quote { + color: #a0a1a7; + font-style: italic; +} + +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #a626a4; +} + +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e45649; +} + +.hljs-literal { + color: #0184bb; +} + +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta-string { + color: #50a14f; +} + +.hljs-built_in, +.hljs-class .hljs-title { + color: #c18401; +} + +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #986801; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #4078f2; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/brown-paper.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/brown-paper.css new file mode 100644 index 000000000000..f0197b924c14 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/brown-paper.css @@ -0,0 +1,64 @@ +/* + +Brown Paper style from goldblog.com.ua (c) Zaripov Yura + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background:#b7a68e url(./brown-papersq.png); +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color:#005599; + font-weight:bold; +} + +.hljs, +.hljs-subst { + color: #363c69; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-built_in, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable, +.hljs-link, +.hljs-name { + color: #2c009f; +} + +.hljs-comment, +.hljs-quote, +.hljs-meta, +.hljs-deletion { + color: #802022; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/brown-papersq.png b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/brown-papersq.png new file mode 100644 index 000000000000..3813903dbf9f Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/brown-papersq.png differ diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/codepen-embed.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/codepen-embed.css new file mode 100644 index 000000000000..195c4a07843a --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/codepen-embed.css @@ -0,0 +1,60 @@ +/* + codepen.io Embed Theme + Author: Justin Perry + Original theme - https://github.com/chriskempson/tomorrow-theme +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #222; + color: #fff; +} + +.hljs-comment, +.hljs-quote { + color: #777; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-regexp, +.hljs-meta, +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-params, +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-deletion { + color: #ab875d; +} + +.hljs-section, +.hljs-title, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-type, +.hljs-attribute { + color: #9b869b; +} + +.hljs-string, +.hljs-keyword, +.hljs-selector-tag, +.hljs-addition { + color: #8f9c6c; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/color-brewer.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/color-brewer.css new file mode 100644 index 000000000000..7934d986a7e8 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/color-brewer.css @@ -0,0 +1,71 @@ +/* + +Colorbrewer theme +Original: https://github.com/mbostock/colorbrewer-theme (c) Mike Bostock +Ported by Fabrício Tavares de Oliveira + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fff; +} + +.hljs, +.hljs-subst { + color: #000; +} + +.hljs-string, +.hljs-meta, +.hljs-symbol, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition { + color: #756bb1; +} + +.hljs-comment, +.hljs-quote { + color: #636363; +} + +.hljs-number, +.hljs-regexp, +.hljs-literal, +.hljs-bullet, +.hljs-link { + color: #31a354; +} + +.hljs-deletion, +.hljs-variable { + color: #88f; +} + + + +.hljs-keyword, +.hljs-selector-tag, +.hljs-title, +.hljs-section, +.hljs-built_in, +.hljs-doctag, +.hljs-type, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-strong { + color: #3182bd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-attribute { + color: #e6550d; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/darcula.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/darcula.css new file mode 100644 index 000000000000..be182d0b5044 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/darcula.css @@ -0,0 +1,77 @@ +/* + +Darcula color scheme from the JetBrains family of IDEs + +*/ + + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #2b2b2b; +} + +.hljs { + color: #bababa; +} + +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-quote, +.hljs-link, +.hljs-number, +.hljs-regexp, +.hljs-literal { + color: #6896ba; +} + +.hljs-code, +.hljs-selector-class { + color: #a6e22e; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-section, +.hljs-attribute, +.hljs-name, +.hljs-variable { + color: #cb7832; +} + +.hljs-params { + color: #b9b9b9; +} + +.hljs-string { + color: #6a8759; +} + +.hljs-subst, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-symbol, +.hljs-selector-id, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition { + color: #e0c46c; +} + +.hljs-comment, +.hljs-deletion, +.hljs-meta { + color: #7f7f7f; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/dark.css new file mode 100644 index 000000000000..b4724f5f50d1 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/dark.css @@ -0,0 +1,63 @@ +/* + +Dark style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #444; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-section, +.hljs-link { + color: white; +} + +.hljs, +.hljs-subst { + color: #ddd; +} + +.hljs-string, +.hljs-title, +.hljs-name, +.hljs-type, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-built_in, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #d88; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion, +.hljs-meta { + color: #777; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-title, +.hljs-section, +.hljs-doctag, +.hljs-type, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/darkula.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/darkula.css new file mode 100644 index 000000000000..f4646c3c5dc2 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/darkula.css @@ -0,0 +1,6 @@ +/* + Deprecated due to a typo in the name and left here for compatibility purpose only. + Please use darcula.css instead. +*/ + +@import url('darcula.css'); diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/default.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/default.css new file mode 100644 index 000000000000..f1bfade31e5d --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/default.css @@ -0,0 +1,99 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} + + +/* Base color: saturation 0; */ + +.hljs, +.hljs-subst { + color: #444; +} + +.hljs-comment { + color: #888888; +} + +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} + + +/* User color: hue: 0 */ + +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} + +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} + +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} + + +/* Language color: hue: 90; */ + +.hljs-literal { + color: #78A960; +} + +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} + + +/* Meta color: hue: 200 */ + +.hljs-meta { + color: #1f7199; +} + +.hljs-meta-string { + color: #4d99bf; +} + + +/* Misc effects */ + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/docco.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/docco.css new file mode 100644 index 000000000000..db366be372b8 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/docco.css @@ -0,0 +1,97 @@ +/* +Docco style used in http://jashkenas.github.com/docco/ converted by Simon Madine (@thingsinjars) +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #000; + background: #f8f8ff; +} + +.hljs-comment, +.hljs-quote { + color: #408080; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-subst { + color: #954121; +} + +.hljs-number { + color: #40a070; +} + +.hljs-string, +.hljs-doctag { + color: #219161; +} + +.hljs-selector-id, +.hljs-selector-class, +.hljs-section, +.hljs-type { + color: #19469d; +} + +.hljs-params { + color: #00f; +} + +.hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-variable, +.hljs-template-variable { + color: #008080; +} + +.hljs-regexp, +.hljs-link { + color: #b68; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/dracula.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/dracula.css new file mode 100644 index 000000000000..d591db6801e2 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/dracula.css @@ -0,0 +1,76 @@ +/* + +Dracula Theme v1.2.0 + +https://github.com/zenorocha/dracula-theme + +Copyright 2015, All rights reserved + +Code licensed under the MIT license +http://zenorocha.mit-license.org + +@author Éverton Ribeiro +@author Zeno Rocha + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #282a36; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-section, +.hljs-link { + color: #8be9fd; +} + +.hljs-function .hljs-keyword { + color: #ff79c6; +} + +.hljs, +.hljs-subst { + color: #f8f8f2; +} + +.hljs-string, +.hljs-title, +.hljs-name, +.hljs-type, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #f1fa8c; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion, +.hljs-meta { + color: #6272a4; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-title, +.hljs-section, +.hljs-doctag, +.hljs-type, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/far.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/far.css new file mode 100644 index 000000000000..2b3f87b56235 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/far.css @@ -0,0 +1,71 @@ +/* + +FAR Style (c) MajestiC + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000080; +} + +.hljs, +.hljs-subst { + color: #0ff; +} + +.hljs-string, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-built_in, +.hljs-builtin-name, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition { + color: #ff0; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-section, +.hljs-type, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-variable { + color: #fff; +} + +.hljs-comment, +.hljs-quote, +.hljs-doctag, +.hljs-deletion { + color: #888; +} + +.hljs-number, +.hljs-regexp, +.hljs-literal, +.hljs-link { + color: #0f0; +} + +.hljs-meta { + color: #008080; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-title, +.hljs-section, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/foundation.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/foundation.css new file mode 100644 index 000000000000..f1fe64b3771e --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/foundation.css @@ -0,0 +1,88 @@ +/* +Description: Foundation 4 docs style for highlight.js +Author: Dan Allen +Website: http://foundation.zurb.com/docs/ +Version: 1.0 +Date: 2013-04-02 +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #eee; color: black; +} + +.hljs-link, +.hljs-emphasis, +.hljs-attribute, +.hljs-addition { + color: #070; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong, +.hljs-string, +.hljs-deletion { + color: #d14; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-quote, +.hljs-comment { + color: #998; + font-style: italic; +} + +.hljs-section, +.hljs-title { + color: #900; +} + +.hljs-class .hljs-title, +.hljs-type { + color: #458; +} + +.hljs-variable, +.hljs-template-variable { + color: #336699; +} + +.hljs-bullet { + color: #997700; +} + +.hljs-meta { + color: #3344bb; +} + +.hljs-code, +.hljs-number, +.hljs-literal, +.hljs-keyword, +.hljs-selector-tag { + color: #099; +} + +.hljs-regexp { + background-color: #fff0ff; + color: #880088; +} + +.hljs-symbol { + color: #990073; +} + +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #007700; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/github-gist.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/github-gist.css new file mode 100644 index 000000000000..155f0b9160dd --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/github-gist.css @@ -0,0 +1,71 @@ +/** + * GitHub Gist Theme + * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro + */ + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/github.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/github.css new file mode 100644 index 000000000000..791932b87eaf --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/github.css @@ -0,0 +1,99 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/googlecode.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/googlecode.css new file mode 100644 index 000000000000..884ad63538e2 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/googlecode.css @@ -0,0 +1,89 @@ +/* + +Google Code style (c) Aahan Krish + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; +} + +.hljs-comment, +.hljs-quote { + color: #800; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-section, +.hljs-title, +.hljs-name { + color: #008; +} + +.hljs-variable, +.hljs-template-variable { + color: #660; +} + +.hljs-string, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-regexp { + color: #080; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-meta, +.hljs-number, +.hljs-link { + color: #066; +} + +.hljs-title, +.hljs-doctag, +.hljs-type, +.hljs-attr, +.hljs-built_in, +.hljs-builtin-name, +.hljs-params { + color: #606; +} + +.hljs-attribute, +.hljs-subst { + color: #000; +} + +.hljs-formula { + background-color: #eee; + font-style: italic; +} + +.hljs-selector-id, +.hljs-selector-class { + color: #9B703F +} + +.hljs-addition { + background-color: #baeeba; +} + +.hljs-deletion { + background-color: #ffc8bd; +} + +.hljs-doctag, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/grayscale.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/grayscale.css new file mode 100644 index 000000000000..5376f3406484 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/grayscale.css @@ -0,0 +1,101 @@ +/* + +grayscale style (c) MY Sun + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #fff; +} + +.hljs-comment, +.hljs-quote { + color: #777; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal { + color: #777; +} + +.hljs-string, +.hljs-doctag, +.hljs-formula { + color: #333; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAJ0lEQVQIW2O8e/fufwYGBgZBQUEQxcCIIfDu3Tuwivfv30NUoAsAALHpFMMLqZlPAAAAAElFTkSuQmCC) repeat; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #000; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-class .hljs-title, +.hljs-type, +.hljs-name { + color: #333; + font-weight: bold; +} + +.hljs-tag { + color: #333; +} + +.hljs-regexp { + color: #333; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAPUlEQVQYV2NkQAN37979r6yszIgujiIAU4RNMVwhuiQ6H6wQl3XI4oy4FMHcCJPHcDS6J2A2EqUQpJhohQDexSef15DBCwAAAABJRU5ErkJggg==) repeat; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link { + color: #000; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAKElEQVQIW2NkQAO7d+/+z4gsBhJwdXVlhAvCBECKwIIwAbhKZBUwBQA6hBpm5efZsgAAAABJRU5ErkJggg==) repeat; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #000; + text-decoration: underline; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + color: #fff; + background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAE0lEQVQIW2MMDQ39zzhz5kwIAQAyxweWgUHd1AAAAABJRU5ErkJggg==) repeat; +} + +.hljs-addition { + color: #000; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAALUlEQVQYV2N89+7dfwYk8P79ewZBQUFkIQZGOiu6e/cuiptQHAPl0NtNxAQBAM97Oejj3Dg7AAAAAElFTkSuQmCC) repeat; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/gruvbox-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/gruvbox-dark.css new file mode 100644 index 000000000000..f563811a8627 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/gruvbox-dark.css @@ -0,0 +1,108 @@ +/* + +Gruvbox style (dark) (c) Pavel Pertsev (original style at https://github.com/morhetz/gruvbox) + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #282828; +} + +.hljs, +.hljs-subst { + color: #ebdbb2; +} + +/* Gruvbox Red */ +.hljs-deletion, +.hljs-formula, +.hljs-keyword, +.hljs-link, +.hljs-selector-tag { + color: #fb4934; +} + +/* Gruvbox Blue */ +.hljs-built_in, +.hljs-emphasis, +.hljs-name, +.hljs-quote, +.hljs-strong, +.hljs-title, +.hljs-variable { + color: #83a598; +} + +/* Gruvbox Yellow */ +.hljs-attr, +.hljs-params, +.hljs-template-tag, +.hljs-type { + color: #fabd2f; +} + +/* Gruvbox Purple */ +.hljs-builtin-name, +.hljs-doctag, +.hljs-literal, +.hljs-number { + color: #8f3f71; +} + +/* Gruvbox Orange */ +.hljs-code, +.hljs-meta, +.hljs-regexp, +.hljs-selector-id, +.hljs-template-variable { + color: #fe8019; +} + +/* Gruvbox Green */ +.hljs-addition, +.hljs-meta-string, +.hljs-section, +.hljs-selector-attr, +.hljs-selector-class, +.hljs-string, +.hljs-symbol { + color: #b8bb26; +} + +/* Gruvbox Aqua */ +.hljs-attribute, +.hljs-bullet, +.hljs-class, +.hljs-function, +.hljs-function .hljs-keyword, +.hljs-meta-keyword, +.hljs-selector-pseudo, +.hljs-tag { + color: #8ec07c; +} + +/* Gruvbox Gray */ +.hljs-comment { + color: #928374; +} + +/* Gruvbox Purple */ +.hljs-link_label, +.hljs-literal, +.hljs-number { + color: #d3869b; +} + +.hljs-comment, +.hljs-emphasis { + font-style: italic; +} + +.hljs-section, +.hljs-strong, +.hljs-tag { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/gruvbox-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/gruvbox-light.css new file mode 100644 index 000000000000..ff45468eb2ef --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/gruvbox-light.css @@ -0,0 +1,108 @@ +/* + +Gruvbox style (light) (c) Pavel Pertsev (original style at https://github.com/morhetz/gruvbox) + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fbf1c7; +} + +.hljs, +.hljs-subst { + color: #3c3836; +} + +/* Gruvbox Red */ +.hljs-deletion, +.hljs-formula, +.hljs-keyword, +.hljs-link, +.hljs-selector-tag { + color: #9d0006; +} + +/* Gruvbox Blue */ +.hljs-built_in, +.hljs-emphasis, +.hljs-name, +.hljs-quote, +.hljs-strong, +.hljs-title, +.hljs-variable { + color: #076678; +} + +/* Gruvbox Yellow */ +.hljs-attr, +.hljs-params, +.hljs-template-tag, +.hljs-type { + color: #b57614; +} + +/* Gruvbox Purple */ +.hljs-builtin-name, +.hljs-doctag, +.hljs-literal, +.hljs-number { + color: #8f3f71; +} + +/* Gruvbox Orange */ +.hljs-code, +.hljs-meta, +.hljs-regexp, +.hljs-selector-id, +.hljs-template-variable { + color: #af3a03; +} + +/* Gruvbox Green */ +.hljs-addition, +.hljs-meta-string, +.hljs-section, +.hljs-selector-attr, +.hljs-selector-class, +.hljs-string, +.hljs-symbol { + color: #79740e; +} + +/* Gruvbox Aqua */ +.hljs-attribute, +.hljs-bullet, +.hljs-class, +.hljs-function, +.hljs-function .hljs-keyword, +.hljs-meta-keyword, +.hljs-selector-pseudo, +.hljs-tag { + color: #427b58; +} + +/* Gruvbox Gray */ +.hljs-comment { + color: #928374; +} + +/* Gruvbox Purple */ +.hljs-link_label, +.hljs-literal, +.hljs-number { + color: #8f3f71; +} + +.hljs-comment, +.hljs-emphasis { + font-style: italic; +} + +.hljs-section, +.hljs-strong, +.hljs-tag { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/hopscotch.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/hopscotch.css new file mode 100644 index 000000000000..32e60d230a51 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/hopscotch.css @@ -0,0 +1,83 @@ +/* + * Hopscotch + * by Jan T. Sott + * https://github.com/idleberg/Hopscotch + * + * This work is licensed under the Creative Commons CC0 1.0 Universal License + */ + +/* Comment */ +.hljs-comment, +.hljs-quote { + color: #989498; +} + +/* Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-link, +.hljs-deletion { + color: #dd464c; +} + +/* Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #fd8b19; +} + +/* Yellow */ +.hljs-class .hljs-title { + color: #fdcc59; +} + +/* Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #8fc13e; +} + +/* Aqua */ +.hljs-meta { + color: #149b93; +} + +/* Blue */ +.hljs-function, +.hljs-section, +.hljs-title { + color: #1290bf; +} + +/* Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #c85e7c; +} + +.hljs { + display: block; + background: #322931; + color: #b9b5b8; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/hybrid.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/hybrid.css new file mode 100644 index 000000000000..29735a189042 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/hybrid.css @@ -0,0 +1,102 @@ +/* + +vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid) + +*/ + +/*background color*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #1d1f21; +} + +/*selection color*/ +.hljs::selection, +.hljs span::selection { + background: #373b41; +} + +.hljs::-moz-selection, +.hljs span::-moz-selection { + background: #373b41; +} + +/*foreground color*/ +.hljs { + color: #c5c8c6; +} + +/*color: fg_yellow*/ +.hljs-title, +.hljs-name { + color: #f0c674; +} + +/*color: fg_comment*/ +.hljs-comment, +.hljs-meta, +.hljs-meta .hljs-keyword { + color: #707880; +} + +/*color: fg_red*/ +.hljs-number, +.hljs-symbol, +.hljs-literal, +.hljs-deletion, +.hljs-link { + color: #cc6666 +} + +/*color: fg_green*/ +.hljs-string, +.hljs-doctag, +.hljs-addition, +.hljs-regexp, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #b5bd68; +} + +/*color: fg_purple*/ +.hljs-attribute, +.hljs-code, +.hljs-selector-id { + color: #b294bb; +} + +/*color: fg_blue*/ +.hljs-keyword, +.hljs-selector-tag, +.hljs-bullet, +.hljs-tag { + color: #81a2be; +} + +/*color: fg_aqua*/ +.hljs-subst, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #8abeb7; +} + +/*color: fg_orange*/ +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-quote, +.hljs-section, +.hljs-selector-class { + color: #de935f; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/idea.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/idea.css new file mode 100644 index 000000000000..3bf1892bd4a2 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/idea.css @@ -0,0 +1,97 @@ +/* + +Intellij Idea-like styling (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #000; + background: #fff; +} + +.hljs-subst, +.hljs-title { + font-weight: normal; + color: #000; +} + +.hljs-comment, +.hljs-quote { + color: #808080; + font-style: italic; +} + +.hljs-meta { + color: #808000; +} + +.hljs-tag { + background: #efefef; +} + +.hljs-section, +.hljs-name, +.hljs-literal, +.hljs-keyword, +.hljs-selector-tag, +.hljs-type, +.hljs-selector-id, +.hljs-selector-class { + font-weight: bold; + color: #000080; +} + +.hljs-attribute, +.hljs-number, +.hljs-regexp, +.hljs-link { + font-weight: bold; + color: #0000ff; +} + +.hljs-number, +.hljs-regexp, +.hljs-link { + font-weight: normal; +} + +.hljs-string { + color: #008000; + font-weight: bold; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-formula { + color: #000; + background: #d0eded; + font-style: italic; +} + +.hljs-doctag { + text-decoration: underline; +} + +.hljs-variable, +.hljs-template-variable { + color: #660e7a; +} + +.hljs-addition { + background: #baeeba; +} + +.hljs-deletion { + background: #ffc8bd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/ir-black.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/ir-black.css new file mode 100644 index 000000000000..bd4c755ed8a9 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/ir-black.css @@ -0,0 +1,73 @@ +/* + IR_Black style (c) Vasily Mikhailitchenko +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000; + color: #f8f8f8; +} + +.hljs-comment, +.hljs-quote, +.hljs-meta { + color: #7c7c7c; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-tag, +.hljs-name { + color: #96cbfe; +} + +.hljs-attribute, +.hljs-selector-id { + color: #ffffb6; +} + +.hljs-string, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition { + color: #a8ff60; +} + +.hljs-subst { + color: #daefa3; +} + +.hljs-regexp, +.hljs-link { + color: #e9c062; +} + +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-doctag { + color: #ffffb6; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-variable, +.hljs-template-variable, +.hljs-literal { + color: #c6c5fe; +} + +.hljs-number, +.hljs-deletion { + color:#ff73fd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/kimbie.dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/kimbie.dark.css new file mode 100644 index 000000000000..d139cb5d0c96 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/kimbie.dark.css @@ -0,0 +1,74 @@ +/* + Name: Kimbie (dark) + Author: Jan T. Sott + License: Creative Commons Attribution-ShareAlike 4.0 Unported License + URL: https://github.com/idleberg/Kimbie-highlight.js +*/ + +/* Kimbie Comment */ +.hljs-comment, +.hljs-quote { + color: #d6baad; +} + +/* Kimbie Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-meta { + color: #dc3958; +} + +/* Kimbie Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-deletion, +.hljs-link { + color: #f79a32; +} + +/* Kimbie Yellow */ +.hljs-title, +.hljs-section, +.hljs-attribute { + color: #f06431; +} + +/* Kimbie Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #889b4a; +} + +/* Kimbie Purple */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-function { + color: #98676a; +} + +.hljs { + display: block; + overflow-x: auto; + background: #221a0f; + color: #d3af86; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/kimbie.light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/kimbie.light.css new file mode 100644 index 000000000000..04ff6ed3a2db --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/kimbie.light.css @@ -0,0 +1,74 @@ +/* + Name: Kimbie (light) + Author: Jan T. Sott + License: Creative Commons Attribution-ShareAlike 4.0 Unported License + URL: https://github.com/idleberg/Kimbie-highlight.js +*/ + +/* Kimbie Comment */ +.hljs-comment, +.hljs-quote { + color: #a57a4c; +} + +/* Kimbie Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-meta { + color: #dc3958; +} + +/* Kimbie Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-deletion, +.hljs-link { + color: #f79a32; +} + +/* Kimbie Yellow */ +.hljs-title, +.hljs-section, +.hljs-attribute { + color: #f06431; +} + +/* Kimbie Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #889b4a; +} + +/* Kimbie Purple */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-function { + color: #98676a; +} + +.hljs { + display: block; + overflow-x: auto; + background: #fbebd4; + color: #84613d; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/magula.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/magula.css new file mode 100644 index 000000000000..44dee5e8e104 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/magula.css @@ -0,0 +1,70 @@ +/* +Description: Magula style for highligh.js +Author: Ruslan Keba +Website: http://rukeba.com/ +Version: 1.0 +Date: 2009-01-03 +Music: Aphex Twin / Xtal +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background-color: #f4f4f4; +} + +.hljs, +.hljs-subst { + color: black; +} + +.hljs-string, +.hljs-title, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #050; +} + +.hljs-comment, +.hljs-quote { + color: #777; +} + +.hljs-number, +.hljs-regexp, +.hljs-literal, +.hljs-type, +.hljs-link { + color: #800; +} + +.hljs-deletion, +.hljs-meta { + color: #00e; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-built_in, +.hljs-tag, +.hljs-name { + font-weight: bold; + color: navy; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/mono-blue.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/mono-blue.css new file mode 100644 index 000000000000..884c97c7673f --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/mono-blue.css @@ -0,0 +1,59 @@ +/* + Five-color theme from a single blue hue. +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #eaeef3; +} + +.hljs { + color: #00193a; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-title, +.hljs-section, +.hljs-doctag, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-comment { + color: #738191; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-built_in, +.hljs-literal, +.hljs-type, +.hljs-addition, +.hljs-tag, +.hljs-quote, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #0048ab; +} + +.hljs-meta, +.hljs-subst, +.hljs-symbol, +.hljs-regexp, +.hljs-attribute, +.hljs-deletion, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-bullet { + color: #4c81c9; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/monokai-sublime.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/monokai-sublime.css new file mode 100644 index 000000000000..2864170daf66 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/monokai-sublime.css @@ -0,0 +1,83 @@ +/* + +Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #23241f; +} + +.hljs, +.hljs-tag, +.hljs-subst { + color: #f8f8f2; +} + +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-quote, +.hljs-number, +.hljs-regexp, +.hljs-literal, +.hljs-link { + color: #ae81ff; +} + +.hljs-code, +.hljs-title, +.hljs-section, +.hljs-selector-class { + color: #a6e22e; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-name, +.hljs-attr { + color: #f92672; +} + +.hljs-symbol, +.hljs-attribute { + color: #66d9ef; +} + +.hljs-params, +.hljs-class .hljs-title { + color: #f8f8f2; +} + +.hljs-string, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-selector-id, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition, +.hljs-variable, +.hljs-template-variable { + color: #e6db74; +} + +.hljs-comment, +.hljs-deletion, +.hljs-meta { + color: #75715e; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/monokai.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/monokai.css new file mode 100644 index 000000000000..775d53f91aa8 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/monokai.css @@ -0,0 +1,70 @@ +/* +Monokai style - ported by Luigi Maselli - http://grigio.org +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #272822; color: #ddd; +} + +.hljs-tag, +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-strong, +.hljs-name { + color: #f92672; +} + +.hljs-code { + color: #66d9ef; +} + +.hljs-class .hljs-title { + color: white; +} + +.hljs-attribute, +.hljs-symbol, +.hljs-regexp, +.hljs-link { + color: #bf79db; +} + +.hljs-string, +.hljs-bullet, +.hljs-subst, +.hljs-title, +.hljs-section, +.hljs-emphasis, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #a6e22e; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion, +.hljs-meta { + color: #75715e; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-selector-id { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/obsidian.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/obsidian.css new file mode 100644 index 000000000000..356630fa2345 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/obsidian.css @@ -0,0 +1,88 @@ +/** + * Obsidian style + * ported by Alexander Marenin (http://github.com/ioncreature) + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #282b2e; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-selector-id { + color: #93c763; +} + +.hljs-number { + color: #ffcd22; +} + +.hljs { + color: #e0e2e4; +} + +.hljs-attribute { + color: #668bb0; +} + +.hljs-code, +.hljs-class .hljs-title, +.hljs-section { + color: white; +} + +.hljs-regexp, +.hljs-link { + color: #d39745; +} + +.hljs-meta { + color: #557182; +} + +.hljs-tag, +.hljs-name, +.hljs-bullet, +.hljs-subst, +.hljs-emphasis, +.hljs-type, +.hljs-built_in, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #8cbbad; +} + +.hljs-string, +.hljs-symbol { + color: #ec7600; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion { + color: #818e96; +} + +.hljs-selector-class { + color: #A082BD +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-name, +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/ocean.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/ocean.css new file mode 100644 index 000000000000..5901581b40a7 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/ocean.css @@ -0,0 +1,74 @@ +/* Ocean Dark Theme */ +/* https://github.com/gavsiu */ +/* Original theme - https://github.com/chriskempson/base16 */ + +/* Ocean Comment */ +.hljs-comment, +.hljs-quote { + color: #65737e; +} + +/* Ocean Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #bf616a; +} + +/* Ocean Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #d08770; +} + +/* Ocean Yellow */ +.hljs-attribute { + color: #ebcb8b; +} + +/* Ocean Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #a3be8c; +} + +/* Ocean Blue */ +.hljs-title, +.hljs-section { + color: #8fa1b3; +} + +/* Ocean Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b48ead; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2b303b; + color: #c0c5ce; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/paraiso-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/paraiso-dark.css new file mode 100644 index 000000000000..e7292401c6ef --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/paraiso-dark.css @@ -0,0 +1,72 @@ +/* + Paraíso (dark) + Created by Jan T. Sott (http://github.com/idleberg) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) +*/ + +/* Paraíso Comment */ +.hljs-comment, +.hljs-quote { + color: #8d8687; +} + +/* Paraíso Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-link, +.hljs-meta { + color: #ef6155; +} + +/* Paraíso Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-deletion { + color: #f99b15; +} + +/* Paraíso Yellow */ +.hljs-title, +.hljs-section, +.hljs-attribute { + color: #fec418; +} + +/* Paraíso Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #48b685; +} + +/* Paraíso Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #815ba4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2f1e2e; + color: #a39e9b; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/paraiso-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/paraiso-light.css new file mode 100644 index 000000000000..944857cd8d33 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/paraiso-light.css @@ -0,0 +1,72 @@ +/* + Paraíso (light) + Created by Jan T. Sott (http://github.com/idleberg) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) +*/ + +/* Paraíso Comment */ +.hljs-comment, +.hljs-quote { + color: #776e71; +} + +/* Paraíso Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-link, +.hljs-meta { + color: #ef6155; +} + +/* Paraíso Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-deletion { + color: #f99b15; +} + +/* Paraíso Yellow */ +.hljs-title, +.hljs-section, +.hljs-attribute { + color: #fec418; +} + +/* Paraíso Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #48b685; +} + +/* Paraíso Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #815ba4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #e7e9db; + color: #4f424c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/pojoaque.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/pojoaque.css new file mode 100644 index 000000000000..2e07847b2b29 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/pojoaque.css @@ -0,0 +1,83 @@ +/* + +Pojoaque Style by Jason Tate +http://web-cms-designs.com/ftopict-10-pojoaque-style-for-highlight-js-code-highlighter.html +Based on Solarized Style from http://ethanschoonover.com/solarized + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #dccf8f; + background: url(./pojoaque.jpg) repeat scroll left top #181914; +} + +.hljs-comment, +.hljs-quote { + color: #586e75; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-addition { + color: #b64926; +} + +.hljs-number, +.hljs-string, +.hljs-doctag, +.hljs-regexp { + color: #468966; +} + +.hljs-title, +.hljs-section, +.hljs-built_in, +.hljs-name { + color: #ffb03b; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-class .hljs-title, +.hljs-type, +.hljs-tag { + color: #b58900; +} + +.hljs-attribute { + color: #b89859; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-subst, +.hljs-meta { + color: #cb4b16; +} + +.hljs-deletion { + color: #dc322f; +} + +.hljs-selector-id, +.hljs-selector-class { + color: #d3a60c; +} + +.hljs-formula { + background: #073642; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/pojoaque.jpg b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/pojoaque.jpg new file mode 100644 index 000000000000..9c07d4ab40b6 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/pojoaque.jpg differ diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/purebasic.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/purebasic.css new file mode 100644 index 000000000000..5ce9b9e07109 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/purebasic.css @@ -0,0 +1,96 @@ +/* + +PureBASIC native IDE style ( version 1.0 - April 2016 ) + +by Tristano Ajmone + +Public Domain + +NOTE_1: PureBASIC code syntax highlighting only applies the following classes: + .hljs-comment + .hljs-function + .hljs-keywords + .hljs-string + .hljs-symbol + + Other classes are added here for the benefit of styling other languages with the look and feel of PureBASIC native IDE style. + If you need to customize a stylesheet for PureBASIC only, remove all non-relevant classes -- PureBASIC-related classes are followed by + a "--- used for PureBASIC ... ---" comment on same line. + +NOTE_2: Color names provided in comments were derived using "Name that Color" online tool: + http://chir.ag/projects/name-that-color +*/ + +.hljs { /* Common set of rules required by highlight.js (don'r remove!) */ + display: block; + overflow-x: auto; + padding: 0.5em; + background: #FFFFDF; /* Half and Half (approx.) */ +/* --- Uncomment to add PureBASIC native IDE styled font! + font-family: Consolas; +*/ +} + +.hljs, /* --- used for PureBASIC base color --- */ +.hljs-type, /* --- used for PureBASIC Procedures return type --- */ +.hljs-function, /* --- used for wrapping PureBASIC Procedures definitions --- */ +.hljs-name, +.hljs-number, +.hljs-attr, +.hljs-params, +.hljs-subst { + color: #000000; /* Black */ +} + +.hljs-comment, /* --- used for PureBASIC Comments --- */ +.hljs-regexp, +.hljs-section, +.hljs-selector-pseudo, +.hljs-addition { + color: #00AAAA; /* Persian Green (approx.) */ +} + +.hljs-title, /* --- used for PureBASIC Procedures Names --- */ +.hljs-tag, +.hljs-variable, +.hljs-code { + color: #006666; /* Blue Stone (approx.) */ +} + +.hljs-keyword, /* --- used for PureBASIC Keywords --- */ +.hljs-class, +.hljs-meta-keyword, +.hljs-selector-class, +.hljs-built_in, +.hljs-builtin-name { + color: #006666; /* Blue Stone (approx.) */ + font-weight: bold; +} + +.hljs-string, /* --- used for PureBASIC Strings --- */ +.hljs-selector-attr { + color: #0080FF; /* Azure Radiance (approx.) */ +} + +.hljs-symbol, /* --- used for PureBASIC Constants --- */ +.hljs-link, +.hljs-deletion, +.hljs-attribute { + color: #924B72; /* Cannon Pink (approx.) */ +} + +.hljs-meta, +.hljs-literal, +.hljs-selector-id { + color: #924B72; /* Cannon Pink (approx.) */ + font-weight: bold; +} + +.hljs-strong, +.hljs-name { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/qtcreator_dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/qtcreator_dark.css new file mode 100644 index 000000000000..7aa56a3655f2 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/qtcreator_dark.css @@ -0,0 +1,83 @@ +/* + +Qt Creator dark color scheme + +*/ + + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000000; +} + +.hljs, +.hljs-subst, +.hljs-tag, +.hljs-title { + color: #aaaaaa; +} + +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-quote, +.hljs-number, +.hljs-regexp, +.hljs-literal { + color: #ff55ff; +} + +.hljs-code +.hljs-selector-class { + color: #aaaaff; +} + +.hljs-emphasis, +.hljs-stronge, +.hljs-type { + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-function, +.hljs-section, +.hljs-symbol, +.hljs-name { + color: #ffff55; +} + +.hljs-attribute { + color: #ff5555; +} + +.hljs-variable, +.hljs-params, +.hljs-class .hljs-title { + color: #8888ff; +} + +.hljs-string, +.hljs-selector-id, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition, +.hljs-link { + color: #ff55ff; +} + +.hljs-comment, +.hljs-meta, +.hljs-deletion { + color: #55ffff; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/qtcreator_light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/qtcreator_light.css new file mode 100644 index 000000000000..1efa2c660f05 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/qtcreator_light.css @@ -0,0 +1,83 @@ +/* + +Qt Creator light color scheme + +*/ + + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #ffffff; +} + +.hljs, +.hljs-subst, +.hljs-tag, +.hljs-title { + color: #000000; +} + +.hljs-strong, +.hljs-emphasis { + color: #000000; +} + +.hljs-bullet, +.hljs-quote, +.hljs-number, +.hljs-regexp, +.hljs-literal { + color: #000080; +} + +.hljs-code +.hljs-selector-class { + color: #800080; +} + +.hljs-emphasis, +.hljs-stronge, +.hljs-type { + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-function, +.hljs-section, +.hljs-symbol, +.hljs-name { + color: #808000; +} + +.hljs-attribute { + color: #800000; +} + +.hljs-variable, +.hljs-params, +.hljs-class .hljs-title { + color: #0055AF; +} + +.hljs-string, +.hljs-selector-id, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition, +.hljs-link { + color: #008000; +} + +.hljs-comment, +.hljs-meta, +.hljs-deletion { + color: #008000; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/railscasts.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/railscasts.css new file mode 100644 index 000000000000..008cdc5bf148 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/railscasts.css @@ -0,0 +1,106 @@ +/* + +Railscasts-like style (c) Visoft, Inc. (Damien White) + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #232323; + color: #e6e1dc; +} + +.hljs-comment, +.hljs-quote { + color: #bc9458; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag { + color: #c26230; +} + +.hljs-string, +.hljs-number, +.hljs-regexp, +.hljs-variable, +.hljs-template-variable { + color: #a5c261; +} + +.hljs-subst { + color: #519f50; +} + +.hljs-tag, +.hljs-name { + color: #e8bf6a; +} + +.hljs-type { + color: #da4939; +} + + +.hljs-symbol, +.hljs-bullet, +.hljs-built_in, +.hljs-builtin-name, +.hljs-attr, +.hljs-link { + color: #6d9cbe; +} + +.hljs-params { + color: #d0d0ff; +} + +.hljs-attribute { + color: #cda869; +} + +.hljs-meta { + color: #9b859d; +} + +.hljs-title, +.hljs-section { + color: #ffc66d; +} + +.hljs-addition { + background-color: #144212; + color: #e6e1dc; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #600; + color: #e6e1dc; + display: inline-block; + width: 100%; +} + +.hljs-selector-class { + color: #9b703f; +} + +.hljs-selector-id { + color: #8b98ab; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/rainbow.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/rainbow.css new file mode 100644 index 000000000000..905eb8ef1879 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/rainbow.css @@ -0,0 +1,85 @@ +/* + +Style with support for rainbow parens + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #474949; + color: #d1d9e1; +} + + +.hljs-comment, +.hljs-quote { + color: #969896; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-type, +.hljs-addition { + color: #cc99cc; +} + +.hljs-number, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #f99157; +} + +.hljs-string, +.hljs-doctag, +.hljs-regexp { + color: #8abeb7; +} + +.hljs-title, +.hljs-name, +.hljs-section, +.hljs-built_in { + color: #b5bd68; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-selector-id, +.hljs-class .hljs-title { + color: #ffcc66; +} + +.hljs-section, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-subst, +.hljs-meta, +.hljs-link { + color: #f99157; +} + +.hljs-deletion { + color: #dc322f; +} + +.hljs-formula { + background: #eee8d5; +} + +.hljs-attr, +.hljs-attribute { + color: #81a2be; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/routeros.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/routeros.css new file mode 100644 index 000000000000..ebe23990daae --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/routeros.css @@ -0,0 +1,108 @@ +/* + + highlight.js style for Microtik RouterOS script + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} + +/* Base color: saturation 0; */ + +.hljs, +.hljs-subst { + color: #444; +} + +.hljs-comment { + color: #888888; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} + +.hljs-attribute { + color: #0E9A00; +} + +.hljs-function { + color: #99069A; +} + +.hljs-builtin-name { + color: #99069A; +} + +/* User color: hue: 0 */ + +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} + +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} + +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} + + +/* Language color: hue: 90; */ + +.hljs-literal { + color: #78A960; +} + +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #0C9A9A; +} + + +/* Meta color: hue: 200 */ + +.hljs-meta { + color: #1f7199; +} + +.hljs-meta-string { + color: #4d99bf; +} + + +/* Misc effects */ + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/school-book.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/school-book.css new file mode 100644 index 000000000000..964b51d84148 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/school-book.css @@ -0,0 +1,72 @@ +/* + +School Book style from goldblog.com.ua (c) Zaripov Yura + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 15px 0.5em 0.5em 30px; + font-size: 11px; + line-height:16px; +} + +pre{ + background:#f6f6ae url(./school-book.png); + border-top: solid 2px #d2e8b9; + border-bottom: solid 1px #d2e8b9; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color:#005599; + font-weight:bold; +} + +.hljs, +.hljs-subst { + color: #3e5915; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute, +.hljs-built_in, +.hljs-builtin-name, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable, +.hljs-link { + color: #2c009f; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion, +.hljs-meta { + color: #e60415; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-name, +.hljs-selector-id, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/school-book.png b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/school-book.png new file mode 100644 index 000000000000..956e9790a0e2 Binary files /dev/null and b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/school-book.png differ diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/solarized-dark.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/solarized-dark.css new file mode 100644 index 000000000000..b4c0da1f786b --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/solarized-dark.css @@ -0,0 +1,84 @@ +/* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #002b36; + color: #839496; +} + +.hljs-comment, +.hljs-quote { + color: #586e75; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-addition { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-string, +.hljs-meta .hljs-meta-string, +.hljs-literal, +.hljs-doctag, +.hljs-regexp { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-section, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-class .hljs-title, +.hljs-type { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-symbol, +.hljs-bullet, +.hljs-subst, +.hljs-meta, +.hljs-meta .hljs-keyword, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-link { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-built_in, +.hljs-deletion { + color: #dc322f; +} + +.hljs-formula { + background: #073642; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/solarized-light.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/solarized-light.css new file mode 100644 index 000000000000..fdcfcc72c457 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/solarized-light.css @@ -0,0 +1,84 @@ +/* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fdf6e3; + color: #657b83; +} + +.hljs-comment, +.hljs-quote { + color: #93a1a1; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-addition { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-string, +.hljs-meta .hljs-meta-string, +.hljs-literal, +.hljs-doctag, +.hljs-regexp { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-section, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-class .hljs-title, +.hljs-type { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-symbol, +.hljs-bullet, +.hljs-subst, +.hljs-meta, +.hljs-meta .hljs-keyword, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-link { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-built_in, +.hljs-deletion { + color: #dc322f; +} + +.hljs-formula { + background: #eee8d5; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/sunburst.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/sunburst.css new file mode 100644 index 000000000000..f56dd5e9b619 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/sunburst.css @@ -0,0 +1,102 @@ +/* + +Sunburst-like style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000; + color: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #aeaeae; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #e28964; +} + +.hljs-string { + color: #65b042; +} + +.hljs-subst { + color: #daefa3; +} + +.hljs-regexp, +.hljs-link { + color: #e9c062; +} + +.hljs-title, +.hljs-section, +.hljs-tag, +.hljs-name { + color: #89bdff; +} + +.hljs-class .hljs-title, +.hljs-doctag { + text-decoration: underline; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-number { + color: #3387cc; +} + +.hljs-params, +.hljs-variable, +.hljs-template-variable { + color: #3e87e3; +} + +.hljs-attribute { + color: #cda869; +} + +.hljs-meta { + color: #8996a8; +} + +.hljs-formula { + background-color: #0e2231; + color: #f8f8f8; + font-style: italic; +} + +.hljs-addition { + background-color: #253b22; + color: #f8f8f8; +} + +.hljs-deletion { + background-color: #420e09; + color: #f8f8f8; +} + +.hljs-selector-class { + color: #9b703f; +} + +.hljs-selector-id { + color: #8b98ab; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night-blue.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night-blue.css new file mode 100644 index 000000000000..78e59cc8cb0a --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night-blue.css @@ -0,0 +1,75 @@ +/* Tomorrow Night Blue Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #7285b7; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #ff9da4; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #ffc58f; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #ffeead; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #d1f1a9; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #bbdaff; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #ebbbff; +} + +.hljs { + display: block; + overflow-x: auto; + background: #002451; + color: white; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night-bright.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night-bright.css new file mode 100644 index 000000000000..e05af8ae2458 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night-bright.css @@ -0,0 +1,74 @@ +/* Tomorrow Night Bright Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #d54e53; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #e78c45; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #e7c547; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #b9ca4a; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #7aa6da; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #c397d8; +} + +.hljs { + display: block; + overflow-x: auto; + background: black; + color: #eaeaea; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night-eighties.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night-eighties.css new file mode 100644 index 000000000000..08fd51c742af --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night-eighties.css @@ -0,0 +1,74 @@ +/* Tomorrow Night Eighties Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #999999; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #f2777a; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #f99157; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #ffcc66; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #99cc99; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #6699cc; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #cc99cc; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2d2d2d; + color: #cccccc; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night.css new file mode 100644 index 000000000000..ddd270a4e765 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow-night.css @@ -0,0 +1,75 @@ +/* Tomorrow Night Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #cc6666; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #de935f; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #f0c674; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #b5bd68; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #81a2be; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b294bb; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1d1f21; + color: #c5c8c6; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow.css new file mode 100644 index 000000000000..026a62fe3bee --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/tomorrow.css @@ -0,0 +1,72 @@ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #8e908c; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #c82829; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #f5871f; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #eab700; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #718c00; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #4271ae; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #8959a8; +} + +.hljs { + display: block; + overflow-x: auto; + background: white; + color: #4d4d4c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/vs.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/vs.css new file mode 100644 index 000000000000..c5d07d3115d3 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/vs.css @@ -0,0 +1,68 @@ +/* + +Visual Studio-like style based on original C# coloring by Jason Diamond + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; +} + +.hljs-comment, +.hljs-quote, +.hljs-variable { + color: #008000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-built_in, +.hljs-name, +.hljs-tag { + color: #00f; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-attribute, +.hljs-literal, +.hljs-template-tag, +.hljs-template-variable, +.hljs-type, +.hljs-addition { + color: #a31515; +} + +.hljs-deletion, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-meta { + color: #2b91af; +} + +.hljs-doctag { + color: #808080; +} + +.hljs-attr { + color: #f00; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link { + color: #00b0e8; +} + + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/vs2015.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/vs2015.css new file mode 100644 index 000000000000..d1d9be3caa54 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/vs2015.css @@ -0,0 +1,115 @@ +/* + * Visual Studio 2015 dark style + * Author: Nicolas LLOBERA + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #1E1E1E; + color: #DCDCDC; +} + +.hljs-keyword, +.hljs-literal, +.hljs-symbol, +.hljs-name { + color: #569CD6; +} +.hljs-link { + color: #569CD6; + text-decoration: underline; +} + +.hljs-built_in, +.hljs-type { + color: #4EC9B0; +} + +.hljs-number, +.hljs-class { + color: #B8D7A3; +} + +.hljs-string, +.hljs-meta-string { + color: #D69D85; +} + +.hljs-regexp, +.hljs-template-tag { + color: #9A5334; +} + +.hljs-subst, +.hljs-function, +.hljs-title, +.hljs-params, +.hljs-formula { + color: #DCDCDC; +} + +.hljs-comment, +.hljs-quote { + color: #57A64A; + font-style: italic; +} + +.hljs-doctag { + color: #608B4E; +} + +.hljs-meta, +.hljs-meta-keyword, +.hljs-tag { + color: #9B9B9B; +} + +.hljs-variable, +.hljs-template-variable { + color: #BD63C5; +} + +.hljs-attr, +.hljs-attribute, +.hljs-builtin-name { + color: #9CDCFE; +} + +.hljs-section { + color: gold; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +/*.hljs-code { + font-family:'Monospace'; +}*/ + +.hljs-bullet, +.hljs-selector-tag, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #D7BA7D; +} + +.hljs-addition { + background-color: #144212; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #600; + display: inline-block; + width: 100%; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/xcode.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/xcode.css new file mode 100644 index 000000000000..43dddad84d7d --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/xcode.css @@ -0,0 +1,93 @@ +/* + +XCode style (c) Angel Garcia + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fff; + color: black; +} + +.hljs-comment, +.hljs-quote { + color: #006a00; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color: #aa0d91; +} + +.hljs-name { + color: #008; +} + +.hljs-variable, +.hljs-template-variable { + color: #660; +} + +.hljs-string { + color: #c41a16; +} + +.hljs-regexp, +.hljs-link { + color: #080; +} + +.hljs-title, +.hljs-tag, +.hljs-symbol, +.hljs-bullet, +.hljs-number, +.hljs-meta { + color: #1c00cf; +} + +.hljs-section, +.hljs-class .hljs-title, +.hljs-type, +.hljs-attr, +.hljs-built_in, +.hljs-builtin-name, +.hljs-params { + color: #5c2699; +} + +.hljs-attribute, +.hljs-subst { + color: #000; +} + +.hljs-formula { + background-color: #eee; + font-style: italic; +} + +.hljs-addition { + background-color: #baeeba; +} + +.hljs-deletion { + background-color: #ffc8bd; +} + +.hljs-selector-id, +.hljs-selector-class { + color: #9b703f; +} + +.hljs-doctag, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/xt256.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/xt256.css new file mode 100644 index 000000000000..58df82cb7511 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/xt256.css @@ -0,0 +1,92 @@ + +/* + xt256.css + + Contact: initbar [at] protonmail [dot] ch + : github.com/initbar +*/ + +.hljs { + display: block; + overflow-x: auto; + color: #eaeaea; + background: #000; + padding: 0.5; +} + +.hljs-subst { + color: #eaeaea; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-builtin-name, +.hljs-type { + color: #eaeaea; +} + +.hljs-params { + color: #da0000; +} + +.hljs-literal, +.hljs-number, +.hljs-name { + color: #ff0000; + font-weight: bolder; +} + +.hljs-comment { + color: #969896; +} + +.hljs-selector-id, +.hljs-quote { + color: #00ffff; +} + +.hljs-template-variable, +.hljs-variable, +.hljs-title { + color: #00ffff; + font-weight: bold; +} + +.hljs-selector-class, +.hljs-keyword, +.hljs-symbol { + color: #fff000; +} + +.hljs-string, +.hljs-bullet { + color: #00ff00; +} + +.hljs-tag, +.hljs-section { + color: #000fff; +} + +.hljs-selector-tag { + color: #000fff; + font-weight: bold; +} + +.hljs-attribute, +.hljs-built_in, +.hljs-regexp, +.hljs-link { + color: #ff00ff; +} + +.hljs-meta { + color: #fff; + font-weight: bolder; +} diff --git a/web/src/main/webapp/v2/src/assets/lib/hljs/styles/zenburn.css b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/zenburn.css new file mode 100644 index 000000000000..07be502016b4 --- /dev/null +++ b/web/src/main/webapp/v2/src/assets/lib/hljs/styles/zenburn.css @@ -0,0 +1,80 @@ +/* + +Zenburn style from voldmar.ru (c) Vladimir Epifanov +based on dark.css by Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #3f3f3f; + color: #dcdcdc; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-tag { + color: #e3ceab; +} + +.hljs-template-tag { + color: #dcdcdc; +} + +.hljs-number { + color: #8cd0d3; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-attribute { + color: #efdcbc; +} + +.hljs-literal { + color: #efefaf; +} + +.hljs-subst { + color: #8f8f8f; +} + +.hljs-title, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-section, +.hljs-type { + color: #efef8f; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link { + color: #dca3a3; +} + +.hljs-deletion, +.hljs-string, +.hljs-built_in, +.hljs-builtin-name { + color: #cc9393; +} + +.hljs-addition, +.hljs-comment, +.hljs-quote, +.hljs-meta { + color: #7f9f7f; +} + + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/web/src/main/webapp/v2/src/environments/environment.prod.ts b/web/src/main/webapp/v2/src/environments/environment.prod.ts new file mode 100644 index 000000000000..3612073bc31c --- /dev/null +++ b/web/src/main/webapp/v2/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/web/src/main/webapp/v2/src/environments/environment.ts b/web/src/main/webapp/v2/src/environments/environment.ts new file mode 100644 index 000000000000..00313f16648e --- /dev/null +++ b/web/src/main/webapp/v2/src/environments/environment.ts @@ -0,0 +1,8 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `angular-cli.json`. + +export const environment = { + production: false +}; diff --git a/web/src/main/webapp/v2/src/favicon.ico b/web/src/main/webapp/v2/src/favicon.ico new file mode 100644 index 000000000000..96f343a63adc Binary files /dev/null and b/web/src/main/webapp/v2/src/favicon.ico differ diff --git a/web/src/main/webapp/v2/src/favicon.png b/web/src/main/webapp/v2/src/favicon.png new file mode 100644 index 000000000000..06d6bbb01e69 Binary files /dev/null and b/web/src/main/webapp/v2/src/favicon.png differ diff --git a/web/src/main/webapp/v2/src/globals.d.ts b/web/src/main/webapp/v2/src/globals.d.ts new file mode 100644 index 000000000000..da1c995faf12 --- /dev/null +++ b/web/src/main/webapp/v2/src/globals.d.ts @@ -0,0 +1,324 @@ +// @store +interface IApplication { + applicationName: string; + serviceType: string; + code: number; + key?: string; + equals(target: IApplication): boolean; + getApplicationName(): string; + getServiceType(): string; + getUrlStr(): string; + getKeyStr(): string; + getCode(): number; +} + +interface ISourceInfo { + applicationName: string; + code: number; + serviceType?: string; + serviceTypeCode: number; + isWas: boolean; +} +// @store +interface IResponseTime { + '1s': number; + '3s': number; + '5s': number; + 'Slow': number; + 'Error': number; +} +// @store +interface IHistogram { + key: string; + values: Array>; +} +// @store +interface IResponseMilliSecondTime { + '100ms': number; + '300ms': number; + '500ms': number; + 'Error': number; + 'Slow': number; +} +interface IInstanceStatus { + code: number; + desc: string; +} +interface IAgentList { + [key: string]: IAgent[]; +} +// @store +interface IAgent { + agentId: string; + agentVersion: string; + applicationName: string; + hostName: string; + initialStartTimestamp: number; + ip: string; + jvmInfo: { + gcTypeName: string; + jvmVersion: string; + version: number; + }; + pid: number; + ports: string; + serverMetaData: { + serverInfo: string; + serviceInfos: { + serviceLibs: string[]; + serviceName: string; + }[]; + vmArgs: string[]; + }; + serviceType?: string; + startTimestamp: number; + status: { + agentId: string; + eventTimestamp: number; + state: { + code: number; + desc: string; + } + }; + vmVersion: string; +} +// @store +interface IAgentSelection { + agent: string; + responseSummary: IResponseTime | IResponseMilliSecondTime; + load: IHistogram; +} +interface IInstanceInfo { + hasInspector: boolean; + name: string; + serviceType: string; + status: IInstanceStatus; +} +interface IServerInfo { + instanceList: { [key: string]: IInstanceInfo }; + name: string; + status: any; // 응답 형식을 아직 확인 못함. +} +interface ILinkInfo { + errorCount: number; + filterApplicationName: string; + filterApplicationServiceTypeCode: number; + filterApplicationServiceTypeName: string; + filterTargetRpcList?: any[]; + from: string; + fromAgent?: string[]; + hasAlert: boolean; + histogram: IResponseTime | IResponseMilliSecondTime; + key: string; + slowCount: number; + sourceHistogram?: { [key: string]: IResponseTime | IResponseMilliSecondTime }; + sourceInfo: ISourceInfo; + sourceTimeSeriesHistogram?: { [key: string]: IHistogram }[]; + targetHistogram?: { [key: string]: IResponseTime | IResponseMilliSecondTime }; + targetInfo: ISourceInfo; + timeSeriesHistogram: IHistogram[]; + to: string; + toAgent?: string[]; + totalCount: number; +} +interface INodeInfo { + agentHistogram?: { [key:string]: IResponseTime | IResponseMilliSecondTime }[]; + agentTimeSeriesHistogram?: { [key:string]: IHistogram[] }; + agentIds: string[]; + applicationName: string; + category: string; + errorCount: number; + hasAlert: boolean; + histogram: IResponseTime | IResponseMilliSecondTime; + instanceCount: number; + instanceErrorCount: number; + isAuthorized: boolean; + isQueue: boolean; + isWas: boolean; + key: string; + serverList?: { [key: string]: IServerInfo }; + serviceType: string; + serviceTypeCode: string; + slowCount: number; + timeSeriesHistogram: IHistogram[]; + totalCount: number; +} +interface IQueryRange { + from: number; + to: number; + toDateTime: string; + fromDateTime: string; + range: number; +} +interface IServerMapInfo { + applicationMapData: { + range: IQueryRange; + nodeDataArray: INodeInfo[]; + linkDataArray: ILinkInfo[]; + }; +} + +interface IFilter { + fa: string; + fst: string; + ta: string; + tst: string; + ie: null | boolean; + rf?: number; + rt?: number; + url?: string; + fan?: string; + tan?: string; +} + +interface ISelectedTarget { + endTime: string; + period: string; + isNode?: boolean; + isLink?: boolean; + isMerged: boolean; + isWAS: boolean; + node?: string[]; + link?: string[]; + hasServerList?: boolean; + isAuthorized?: boolean; +} + +interface AjaxExceptionObj { + message: string; + request: {[key: string]: any}; + stacktrace: string +} + +interface AjaxException { + exception: AjaxExceptionObj; +} +// @store +interface IScatterXRange { + from: number; + to: number; +} +// @store +interface IScatterData { + complete: boolean; + currentServerTime: number; + from: number; + resultFrom: number; + resultTo: number; + scatter: { + dotList: number[][], + metadata: { + [key: number]: any[] + } + }; + to: number; + reset?: boolean; +} +// @store +interface IHelpViewerInfo { + key: string; + coordinate: ICoordinate; +} +// @store +interface ICoordinate { + coordX: number; + coordY: number; +} +// @store +interface ITransactionMetaData { + agentId: string; + application: string; + collectorAcceptTime: number; + elapsed: number; + endpoint: string; + exception: number; + remoteAddr: string; + spanId: string; + startTime: number; + traceId: string; +} +// @store +interface ITransactionDetailData { + agentId: string; + applicationId: string; + applicationMapData: any; + applicationName: string; + callStack: any[]; + callStackEnd: number; + callStackIndex: any; + callStackStart: number; + completeState: string; + disableButtonMessage: string; + logButtonName: string; + logLinkEnable: boolean; + logPageUrl: string; + loggingTransactionInfo: boolean; + transactionId: string; +} +// @store +interface IHoveredInfo { + index: number; + time?: number; + offsetX?: number; + offsetY?: number; +} +// @store +interface IServerAndAgentData { + agentId: string; + agentVersion: string; + applicationName: string; + hostName: string; + initialStartTimestamp: number; + ip: string; + jvmInfo: { + gcTypeName: string; + jvmVersion: string; + version: number + }; + pid: number; + ports: string; + serverMetaData: any; + serviceType: string; + startTimestamp: number; + status: { + agentId: string; + eventTimestamp: number; + state: { + code: number; + desc: string; + } + }; + vmVersion: string; +} + +// @store +interface ISyntaxHighlightData { + type: string; + originalContents: string; + bindValue: string; + bindedContents?: string; +} + +// @store +interface IUIState { + [key: string]: boolean; +} + +// @store +interface IServerMapMergeState { + name: string; + state: boolean; +} + +// @store +interface ITransactionMessage { + title: string; + contents: string; +} + +// @store +interface ITimelineInfo { + range: number[]; + selectedTime: number; + selectionRange: number[]; +} diff --git a/web/src/main/webapp/v2/src/index.html b/web/src/main/webapp/v2/src/index.html new file mode 100644 index 000000000000..e6ce5b1025fc --- /dev/null +++ b/web/src/main/webapp/v2/src/index.html @@ -0,0 +1,41 @@ + + + + + PINPOINT + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/web/src/main/webapp/v2/src/main.ts b/web/src/main/webapp/v2/src/main.ts new file mode 100644 index 000000000000..a16023e842fc --- /dev/null +++ b/web/src/main/webapp/v2/src/main.ts @@ -0,0 +1,10 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { enableProdMode } from '@angular/core'; +import { environment } from './environments/environment'; +import { AppModule } from './app/app.module'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/web/src/main/webapp/v2/src/polyfills.ts b/web/src/main/webapp/v2/src/polyfills.ts new file mode 100644 index 000000000000..20d40751a6bc --- /dev/null +++ b/web/src/main/webapp/v2/src/polyfills.ts @@ -0,0 +1,76 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/weak-map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; + + +/** Evergreen browsers require these. **/ +// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. +import 'core-js/es7/reflect'; + + +/** + * Required to support Web Animations `@angular/platform-browser/animations`. + * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation + **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. +/** + * Need to import at least one locale-data with intl. + */ +// import 'intl/locale-data/jsonp/en'; diff --git a/web/src/main/webapp/v2/src/styles.css b/web/src/main/webapp/v2/src/styles.css new file mode 100644 index 000000000000..27cb4642c87c --- /dev/null +++ b/web/src/main/webapp/v2/src/styles.css @@ -0,0 +1,64 @@ +@media screen and (max-width: 1200px) {} +*{margin:0;padding:0;text-decoration:none;box-sizing: border-box;border-collapse: collapse;background:transparent;list-style:none;border:0;font-family:inherit;color:inherit} +html {height: 100%} +legend{display:none} +button{cursor:pointer;background:none} +body{color:#333; font-family: 'Nanum Gothic', '나눔고딕';} +table {border-collapse: collapse;border-spacing: 0;background-color: transparent} +caption {width:0;height:0;overflow:hidden;font-size:0} + + + +#pinpoint {min-width:1290px;height:100%} +header {background:#469ae4;border-bottom:1px solid #4484c3;height:50px;justify-content: space-between;position: relative;top: 0;left: 0;z-index: 100;min-width: 1290px;width: 100%} + +.flex-container {display: flex;} +.flex-container.flex-row {flex-flow: row wrap} +.flex-container.flex-column {flex-flow: column nowrap} +.flex-container.flex-row.reverse {flex-direction: row-reverse} +.flex-item {display: flex;justify-content: center;align-items: center;} + +.font-opensans {font-family: 'Open Sans', sans-serif} +.nanumgothic {font-family: "Nanum Gothic",나눔고딕} + +.btn {font-size:13px;color:#666;height:30px;padding:0 13px;border-radius:1px;font-weight:600} +.btn .icon {font-size:11px} +.btn.active {border:1px solid #4491da;background:#4b99e3;color:#fff} +.btn-sm {font-size:13px;height:28px} +.btn-blue {color:#fff;background-color:#4b99e3;border: 1px solid #4491da} +.btn-black{color:#fff;background-color:#3f506c;border: 1px solid #384c65} +.btn-gray {color:#fff;background-color:#ccc;border: 1px solid #aaa} +.pinpoint-btn {background:#4b99e3;color:#fff;font-family: 'Open Sans', sans-serif;font-weight:600;padding:10px 15px;text-align:center;font-size: 11px;line-height: 1em;border-radius:2px} +.pinpoint-btn span {position:relative;padding:0 0 0 14px} +.pinpoint-btn .fas {left:0;position:absolute;top:50%;transform:translateY(-50%);font-size: 1.5em;} + +.inactive {display:none} +*[hidden] { display: none !important; } + +.table {max-width: 100%;width: 100%;border:1px solid #e5e8f0;background:#fff} +.table th, .table td {height:28px;font-size:12px;font-family:'Open Sans', sans-serif;padding:0 21px} +.table thead th {background:#f6f8fb;height:36px;font-weight:600} +.table tbody th {text-align:left} +.table th {color:#666} +.table td{color:#999} +.table td, .table tbody th {font-weight:400;word-break:break-all} +.table.tr-link tbody tr {cursor:pointer} +.table tr.tr-bg-blue {background-color:#eef9fc} +.table tr.tr-bg-red {background-color:#fff1f1} +.popup { + position: absolute; + z-index: 999999; + background-color: #FFF; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + font-size: 14px; + min-width: 160px; +} + +@keyframes rootFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/test.ts b/web/src/main/webapp/v2/src/test.ts new file mode 100644 index 000000000000..f9d51efd05fa --- /dev/null +++ b/web/src/main/webapp/v2/src/test.ts @@ -0,0 +1,32 @@ +import './polyfills.ts'; + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/web/src/main/webapp/v2/src/tsconfig.app.json b/web/src/main/webapp/v2/src/tsconfig.app.json new file mode 100644 index 000000000000..42bbc7f5d44d --- /dev/null +++ b/web/src/main/webapp/v2/src/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es2016", + "lib": [ + "es2016", + "dom" + ], + "outDir": "../out-tsc/app", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/tsconfig.spec.json b/web/src/main/webapp/v2/src/tsconfig.spec.json new file mode 100644 index 000000000000..d8e95227281c --- /dev/null +++ b/web/src/main/webapp/v2/src/tsconfig.spec.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016", + "dom" + ], + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es2016", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/src/typings.d.ts b/web/src/main/webapp/v2/src/typings.d.ts new file mode 100644 index 000000000000..56970ec163bf --- /dev/null +++ b/web/src/main/webapp/v2/src/typings.d.ts @@ -0,0 +1,7 @@ +// Typings reference file, you can add your own global typings here +// https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html +declare var ga: Function; +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/web/src/main/webapp/v2/tsconfig.json b/web/src/main/webapp/v2/tsconfig.json new file mode 100644 index 000000000000..65d12b6b35e9 --- /dev/null +++ b/web/src/main/webapp/v2/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "baseUrl": "src", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es2015", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2015", + "dom" + ], + "preserveSymlinks": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "strict": false, + "noUnusedLocals": true + }, + "angularCompilerOptions": { + "fullTemplateTypeCheck": true, + "skipMetadataEmit": true + } +} \ No newline at end of file diff --git a/web/src/main/webapp/v2/tslint.json b/web/src/main/webapp/v2/tslint.json new file mode 100644 index 000000000000..69a13bfb0b9d --- /dev/null +++ b/web/src/main/webapp/v2/tslint.json @@ -0,0 +1,128 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": [ + true, + "spaces", + 4 + ], + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [ + false, + 140 + ], + "member-access": false, + "member-ordering": [ + false + ], + "no-arg": true, + "no-bitwise": false, + "no-console": [ + true, + "debug", + "info", + // "time", + // "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + true, + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "directive-selector": [ + true, + "attribute", + "pp", + "camelCase" + ], + "component-selector": [ + true, + "element", + "pp", + "kebab-case" + ], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true + } +} \ No newline at end of file