diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index e36f858cb..357e7f7f1 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -18,14 +18,13 @@ on: jobs: codespell: name: Find and notify about common misspellings - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest # This should not fail the whole workflow run continue-on-error: true steps: - - name: Checkout repository - uses: actions/checkout@v3 - - name: Install codespell - run: pip install "codespell==2.2.4" - - name: Run codespell - run: /home/runner/.local/bin/codespell + - name: "Checkout repository" + uses: actions/checkout@v3 + + - name: "Check codespell" + uses: codespell-project/actions-codespell@v2 diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 1dee5735f..0e6c7bb82 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -62,7 +62,7 @@ jobs: run: bash .github/workflows/scripts/setup-full.sh - name: Install test dependencies - run: pip install pytest-cov Cerberus requests_mock coverage + run: pip install pytest-cov Cerberus requests_mock coverage httpx pycodestyle - name: Install dependencies if: ${{ matrix.type == 'basic' }} diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 000000000..76dce8748 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,19 @@ +Copyright (c) + +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. \ No newline at end of file diff --git a/LICENSES/OFL-1.1.txt b/LICENSES/OFL-1.1.txt new file mode 100644 index 000000000..f2f7be8f8 --- /dev/null +++ b/LICENSES/OFL-1.1.txt @@ -0,0 +1,85 @@ +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. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index c99d0aa44..fc4ba4869 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -15,3 +15,4 @@ exclude .* global-exclude *~ global-exclude *.py[co] global-exclude __pycache__ +recursive-include intelmq/web * diff --git a/contrib/malware_name_mapping/README.md b/contrib/malware_name_mapping/README.md index 192041201..39de2ca38 100644 --- a/contrib/malware_name_mapping/README.md +++ b/contrib/malware_name_mapping/README.md @@ -25,7 +25,7 @@ Additional optional rules ### Malpedia rules -The rules imported from Malpedia can be used optionally with the flag `--include-malpedia`. Note, that this data is CC BY-NC-SA 3.0 and not CC0, so any comercial usage, including usage in commercial organizations, is not allowed. +The rules imported from Malpedia can be used optionally with the flag `--include-malpedia`. Note, that this data is CC BY-NC-SA 3.0 and not CC0, so any commercial usage, including usage in commercial organizations, is not allowed. ### MISP galaxy rules diff --git a/debian/changelog b/debian/changelog index 19c1c38a6..fbd9e680e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -451,6 +451,6 @@ intelmq (1.0.0~dev4~alpha2) UNRELEASED; urgency=low intelmq (1.0.0~dev4~alpha1) UNRELEASED; urgency=low * source package automatically created by stdeb 0.8.5, - with a bunch of manual tweeking. + with a bunch of manual tweaking. -- Sascha Wilde Wed, 23 Mar 2016 18:44:26 +0000 diff --git a/debian/control b/debian/control index b47388c4e..769ac02c6 100644 --- a/debian/control +++ b/debian/control @@ -9,9 +9,12 @@ Build-Depends: debhelper (>= 4.1.16), python3-cerberus, python3-dateutil (>= 2.5), python3-dnspython (>= 2.0.0), + python3-fastapi, python3-psutil (>= 1.2.1), python3-redis (>= 2.10), python3-requests (>= 2.2.0), + python3-typing-extensions, + python3-multipart, python3-requests-mock, python3-ruamel.yaml, python3-setuptools, @@ -40,10 +43,17 @@ Depends: bash-completion, python3-requests (>= 2.2.0), python3-ruamel.yaml, python3-termstyle (>= 0.1.10), + python3-fastapi, + python3-typing-extensions, + python3-multipart, + python3-uvicorn, + python3-uvloop, + python3-httptools, python3-tz, python3-importlib-metadata, redis-server, systemd, + dbconfig-sqlite3 | dbconfig-no-thanks, ${misc:Depends}, ${sphinxdoc:Depends}, ${python3:Depends} diff --git a/debian/rules b/debian/rules index 438e18d6c..499fa9de0 100755 --- a/debian/rules +++ b/debian/rules @@ -52,7 +52,7 @@ override_dh_auto_install: $(BOTDOCS) # remove program not needed for packages rm debian/intelmq/usr/bin/intelmqsetup # create directory layout and empty state file - ROOT_DIR=debian/intelmq/ PYTHONPATH=. python3 intelmq/bin/intelmqsetup.py --skip-ownership --state-file debian/intelmq/var/lib/intelmq/state.json --skip-api --skip-manager + ROOT_DIR=debian/intelmq/ PYTHONPATH=. python3 intelmq/__main__.py setup --skip-ownership --state-file debian/intelmq/var/lib/intelmq/state.json override_dh_install: dh_install diff --git a/docs/overview.md b/docs/overview.md index a52467c94..418d1b7c3 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -92,7 +92,7 @@ intelmq-mailgen](https://github.com/Intevation/intelmq-mailgen) A web application helping CERTs to enable members of their constituency to self-administrate how they get warnings related to their network objects (IP addresses, IP ranges, autonomous systems, domains). *tuency* is developed by [Intevation](https://intevation.de/) for [CERT.at](https://cert.at). -If features organizational hierarchies, contact roles, self-administration and network objects per organization (Autonomous systems, network ranges, (sub)domains, RIPE organization handles). A network object claiming and approval process prevents abuse. An hierarchical rule-system on the network objects allow fine-grained settings. The tagging system for contacts and organization complement the contact-management features of the portal. Authentication is based on keycloak, which enables the re-use of the user accounts in the portal. The integrated API enables IntelMQ to query the portal for the right abuse contact and notification settings with the `intelmq.bots.experts.tuency.expert` expert bot. +If features organizational hierarchies, contact roles, self-administration and network objects per organization (Autonomous systems, network ranges, (sub)domains, RIPE organization handles). A network object claiming and approval process prevents abuse. An hierarchical rule-system on the network objects allow fine-grained settings. The tagging system for contacts and organization complement the contact-management features of the portal. Authentication is based on keycloak, which enables the reuse of the user accounts in the portal. The integrated API enables IntelMQ to query the portal for the right abuse contact and notification settings with the `intelmq.bots.experts.tuency.expert` expert bot. ![Tuency Netobjects Overview](https://gitlab.com/intevation/tuency/tuency/-/raw/64b95ec0/docs/images/netobjects.png) diff --git a/intelmq/__main__.py b/intelmq/__main__.py new file mode 100644 index 000000000..7bb6dbc37 --- /dev/null +++ b/intelmq/__main__.py @@ -0,0 +1,116 @@ +# SPDX-FileCopyrightText: 2023 Filip Pokorný +# SPDX-License-Identifier: AGPL-3.0-or-later + +import argparse +import getpass +import sys +import os +import uvicorn +import termstyle +from pathlib import Path + +from intelmq import STATE_FILE_PATH, __version__ +from intelmq.api.session import SessionStore +from intelmq.lib import utils +from intelmq.lib.setuputils import basic_checks, intelmqsetup_core +# from intelmq.api.config import config + + +def print_version(*args, **kwargs): + print(__version__) + + +def server_start(host: str = None, port: int = None, debug: bool = None, *args, **kwargs): + server_settings = utils.get_server_settings() + host = host if host is not None else server_settings.get("host", "0.0.0.0") + port = int(port) if port is not None else int(server_settings.get("port", 8080)) + debug = debug if debug is not None else server_settings.get("debug", False) + + return uvicorn.run( + "intelmq.server:app", + host=host, + reload=debug, + port=port, + workers=1, + ) + + +def server_adduser(username: str, password: str = None, *args, **kwargs): + from intelmq.api.config import config # this is imported here so other tasks can run if importing the config fails + + if config.session_store is None: + print("Could not add user - no session store configured in configuration!", file=sys.stderr) + exit(1) + + session_store = SessionStore(str(config.session_store), config.session_duration) + password = getpass.getpass() if password is None else password + session_store.add_user(username, password) + print(f"Added user {username} to intelmq session file.") + + +def setup(skip_ownership: bool = False, state_file: Path = None, *args, **kwargs): + if os.geteuid() != 0: + sys.exit("You need to run this command as root!") + + basic_checks(skip_ownership=skip_ownership) + intelmqsetup_core(ownership=not skip_ownership, state_file=state_file) + + +def intelmqsetup(): + """ + This is a leftover function for 'intelmqsetup' script. + TODO Delete this with IntelMQ 4.0 (and remove from console_scripts in setup.py) + """ + print(termstyle.red("Using 'intelmqsetup' is deprecated, use 'intelmq setup' instead!")) + print(termstyle.red("Using 'intelmqsetup' is deprecated, use 'intelmq setup' instead!")) + print(termstyle.red("Using 'intelmqsetup' is deprecated, use 'intelmq setup' instead!")) + + if sys.argv[0].endswith("intelmqsetup"): + sys.argv[0] = sys.argv[0][:-5] + sys.argv.insert(1, "setup") + main() + + +def main(): + parser = argparse.ArgumentParser(prog="intelmq", usage="intelmq [OPTIONS] COMMAND") + parser.set_defaults(func=(lambda *_, **__: parser.print_help())) # wrapper to accept args and kwargs + parser._optionals.title = "Options" + parser.add_argument("-v", "--version", action="store_true", help="print version and exit", default=None) + commands = parser.add_subparsers(metavar="", title="Commands") + + # intelmq server + srv_parser = commands.add_parser("server", help="control IntelMQ server", usage="intelmq server [COMMAND]") + srv_parser.set_defaults(func=(lambda *_, **__: srv_parser.print_help())) # wrapper to accept args and kwargs + srv_parser._optionals.title = "Options" + srv_subcommands = srv_parser.add_subparsers(metavar="", title="Commands") + + # intelmq server start + srv_start = srv_subcommands.add_parser("start", help="start the server", usage="intelmq server start [OPTIONS]") + srv_start.set_defaults(func=server_start) + srv_start._optionals.title = "Options" + srv_start.add_argument("--debug", action="store_true", dest="debug", default=None) + srv_start.add_argument("--host", type=str, dest="host") + srv_start.add_argument("--port", type=int, dest="port") + + # intelmq server adduser + srv_adduser = srv_subcommands.add_parser("adduser", help="adds new user", usage="intelmq server adduser [OPTIONS]") + srv_adduser.set_defaults(func=server_adduser) + srv_adduser._optionals.title = "Options" + srv_adduser.add_argument('--username', required=True, help='the username of the account', type=str) + srv_adduser.add_argument('--password', required=False, help='the password of the account', type=str) + + # intelmq setup + setup_parser = commands.add_parser("setup", help="setup subcommands", usage="intelmq setup [OPTIONS]") + setup_parser.set_defaults(func=setup) # wrapper to accept args and kwargs + setup_parser._optionals.title = "Options" + setup_parser.add_argument('--skip-ownership', action='store_true', help='skip setting file ownership') + setup_parser.add_argument('--state-file', type=Path, metavar="PATH", + help='location of the state file', default=STATE_FILE_PATH) + + args = parser.parse_args() + args.func = print_version if args.version else args.func + return args.func(**vars(args)) + + +if __name__ == "__main__": + main() diff --git a/intelmq/api/__init__.py b/intelmq/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/intelmq/api/config.py b/intelmq/api/config.py new file mode 100644 index 000000000..cdc82ccf3 --- /dev/null +++ b/intelmq/api/config.py @@ -0,0 +1,69 @@ +"""Configuration for IntelMQ Manager + +SPDX-FileCopyrightText: 2020 Intevation GmbH +SPDX-License-Identifier: AGPL-3.0-or-later + +Funding: of initial version by SUNET +Author(s): + * Bernhard Herzog +""" + +from typing import List, Optional +from pathlib import Path +from intelmq.lib import utils + + +class Config: + + """Configuration settings for IntelMQ Manager""" + + intelmq_ctl_cmd: List[str] = ["sudo", "-u", "intelmq", "/usr/local/bin/intelmqctl"] + + allowed_path: Path = Path("/opt/intelmq/var/lib/bots/") + + session_store: Optional[Path] = None + + session_duration: int = 24 * 3600 + + allow_origins: List[str] = ['*'] + + enable_webgui: bool = True + + host: str = "0.0.0.0" + + port: int = 8080 + + debug: bool = False + + def __init__(self): + server_settings = utils.get_server_settings() + + if "intelmq_ctl_cmd" in server_settings: + self.intelmq_ctl_cmd = server_settings["intelmq_ctl_cmd"] + + if "allowed_path" in server_settings: + self.allowed_path = Path(server_settings["allowed_path"]) + + if "session_store" in server_settings: + self.session_store = Path(server_settings["session_store"]) + + if "session_duration" in server_settings: + self.session_duration = int(server_settings["session_duration"]) + + if "allow_origins" in server_settings: + self.allow_origins = server_settings['allow_origins'] + + if "enable_webgui" in server_settings: + self.enable_webgui = server_settings["enable_webgui"] + + if "host" in server_settings: + self.host = server_settings["host"] + + if "port" in server_settings: + self.port = server_settings["port"] + + if "debug" in server_settings: + self.debug = server_settings["debug"] + + +config = Config() \ No newline at end of file diff --git a/intelmq/api/dependencies.py b/intelmq/api/dependencies.py new file mode 100644 index 000000000..ec349f129 --- /dev/null +++ b/intelmq/api/dependencies.py @@ -0,0 +1,62 @@ +"""Dependencies of the API endpoints, in the FastAPI style + +SPDX-FileCopyrightText: 2022 CERT.at GmbH +SPDX-License-Identifier: AGPL-3.0-or-later +""" + +import typing +from typing import Generic, Optional, TypeVar + +from fastapi import Depends, Header, HTTPException, Response, status + +import intelmq.api.config +import intelmq.api.session as session + +T = TypeVar("T") + + +class OneTimeDependency(Generic[T]): + """Allows one-time explicit initialization of the dependency, + and then returning it on every usage. + + It emulates the previous behavior that used global variables""" + + def __init__(self) -> None: + self._value: Optional[T] = None + + def initialize(self, value: T) -> None: + self._value = value + + def __call__(self) -> Optional[T]: + return self._value + + +api_config = OneTimeDependency[intelmq.api.config.Config]() +session_store = OneTimeDependency[session.SessionStore]() + + +def cached_response(max_age: int): + """Adds the cache headers to the response""" + def _cached_response(response: Response): + response.headers["cache-control"] = f"max-age={max_age}" + return _cached_response + + +def token_authorization(authorization: typing.Union[str, None] = Header(default=None), + session: session.SessionStore = Depends(session_store)): + if session is not None: + if not authorization or not session.verify_token(authorization): + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail={ + "Authentication Required": + "Please provide valid Token verification credentials" + }) + + +def startup(config: intelmq.api.config.Config): + """A starting point to one-time initialization of necessary dependencies. This needs to + be called by the application on the startup.""" + api_config.initialize(config) + session_file = config.session_store + if session_file is not None: + session_store.initialize(session.SessionStore(str(session_file), + config.session_duration)) diff --git a/intelmq/api/exceptions.py b/intelmq/api/exceptions.py new file mode 100644 index 000000000..5b7dc32e8 --- /dev/null +++ b/intelmq/api/exceptions.py @@ -0,0 +1,25 @@ +"""Exception handlers for API + +SPDX-FileCopyrightText: 2022 CERT.at GmbH +SPDX-License-Identifier: AGPL-3.0-or-later +""" + +from fastapi import FastAPI, Request, status +from fastapi.responses import JSONResponse +from starlette.exceptions import HTTPException as StarletteHTTPException + +import intelmq.api.runctl as runctl + + +def ctl_error_handler(request: Request, exc: runctl.IntelMQCtlError): + return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=exc.error_dict) + + +def handle_generic_error(request: Request, exc: StarletteHTTPException): + return JSONResponse(status_code=exc.status_code, content={"error": exc.detail}) + + +def register(app: FastAPI): + """A hook to register handlers in the app. Need to be called before startup""" + app.add_exception_handler(runctl.IntelMQCtlError, ctl_error_handler) + app.add_exception_handler(StarletteHTTPException, handle_generic_error) diff --git a/intelmq/api/files.py b/intelmq/api/files.py new file mode 100644 index 000000000..0fd26ce4a --- /dev/null +++ b/intelmq/api/files.py @@ -0,0 +1,79 @@ +"""Direct access to IntelMQ files and directories + +SPDX-FileCopyrightText: 2020 Intevation GmbH +SPDX-License-Identifier: AGPL-3.0-or-later + +Funding: of initial version by SUNET +Author(s): + * Bernhard Herzog + +This module implements the part of the IntelMQ-Manager backend that +allows direct read and write access to some of the files used by +IntelMQ. +""" + +from pathlib import PurePath, Path +from typing import Optional, Tuple, Union, Dict, Any, Iterable, BinaryIO + +from intelmq.api.config import Config + + +def path_starts_with(path: PurePath, prefix: PurePath) -> bool: + """Return whether the path starts with prefix. + + Both arguments must be absolute paths. If not, this function raises + a ValueError. + + This function compares the path components, so it's not a simple + string prefix test. + """ + if not path.is_absolute(): + raise ValueError("{!r} is not absolute".format(path)) + if not prefix.is_absolute(): + raise ValueError("{!r} is not absolute".format(prefix)) + return path.parts[:len(prefix.parts)] == prefix.parts + + +class FileAccess: + + def __init__(self, config: Config): + self.allowed_path = config.allowed_path + + def file_name_allowed(self, filename: str) -> Optional[Tuple[bool, Path]]: + """Determine whether the API should allow access to a file.""" + resolved = Path(filename).resolve() + if not path_starts_with(resolved, self.allowed_path): + return None + + return (False, resolved) + + def load_file_or_directory(self, unvalidated_filename: str, fetch: bool) \ + -> Union[Tuple[str, Union[BinaryIO, Dict[str, Any]]], None]: + allowed = self.file_name_allowed(unvalidated_filename) + if allowed is None: + return None + + content_type = "application/json" + predefined, normalized = allowed + + if predefined or fetch: + if fetch: + content_type = "text/html" + return (content_type, open(normalized, "rb")) + + result = {"files": {}} # type: Dict[str, Any] + if normalized.is_dir(): + result["directory"] = str(normalized) + files = normalized.iterdir() # type: Iterable[Path] + else: + files = [normalized] + + for path in files: + stat = path.stat() + if stat.st_size < 2000: + # FIXME: don't hardwire this size + obj = {"contents": path.read_text()} # type: Dict[str, Any] + else: + obj = {"size": stat.st_size, "path": str(path.resolve())} + result["files"][path.name] = obj + return (content_type, result) diff --git a/intelmq/api/models.py b/intelmq/api/models.py new file mode 100644 index 000000000..65f646688 --- /dev/null +++ b/intelmq/api/models.py @@ -0,0 +1,12 @@ +"""Models used in API + +SPDX-FileCopyrightText: 2023 CERT.at GmbH +SPDX-License-Identifier: AGPL-3.0-or-later +""" + +from pydantic import BaseModel + + +class TokenResponse(BaseModel): + login_token: str + username: str diff --git a/intelmq/api/router.py b/intelmq/api/router.py new file mode 100644 index 000000000..07faf50b5 --- /dev/null +++ b/intelmq/api/router.py @@ -0,0 +1,220 @@ +"""HTTP-API backend of IntelMQ-Manager + +SPDX-FileCopyrightText: 2020 Intevation GmbH +SPDX-License-Identifier: AGPL-3.0-or-later + +Funding: of initial version by SUNET +Author(s): + * Bernhard Herzog + +This module implements the HTTP part of the API backend of +IntelMQ-Manager. The logic itself is in the runctl & files modules. +""" + +import json +import pathlib +import string +import typing + +from fastapi import APIRouter, Depends, Form, HTTPException, Response, status +from fastapi.responses import JSONResponse, PlainTextResponse +from intelmq.lib import utils # type: ignore +from typing_extensions import Literal # Python 3.8+ + +import intelmq.api.config +import intelmq.api.files as files +import intelmq.api.runctl as runctl +import intelmq.api.session as session + +from .dependencies import (api_config, cached_response, session_store, + token_authorization) +from .models import TokenResponse + +router = APIRouter() + + +Levels = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "ALL"] +Actions = Literal["start", "stop", "restart", "reload", "status"] +Groups = Literal["collectors", "parsers", "experts", "outputs", "botnet"] +BotCmds = Literal["get", "pop", "send", "process"] +Pages = Literal["configs", "management", "monitor", "check", "about", "index"] + +ID_CHARS = set(string.ascii_letters + string.digits + "-") + + +def ID(id: str) -> str: + if not set(id) < ID_CHARS: + raise ValueError("Invalid character in {!r}".format(id)) + return id + + +def runner(config: intelmq.api.config.Config = Depends(api_config)): + return runctl.RunIntelMQCtl(config.intelmq_ctl_cmd) + + +def file_access(config: intelmq.api.config.Config = Depends(api_config)): + return files.FileAccess(config) + + +cached = Depends(cached_response(max_age=3)) +authorized = Depends(token_authorization) + + +class JSONFileResponse(JSONResponse): + """Directly pass JSONFile (bytes) with the correct content type to the response""" + + def render(self, content: runctl.JSONFile) -> bytes: + return content + + +@router.get("/") +def api_base_url(): + """Do not rename or delete!""" + return JSONResponse({}) + + +@router.get("/botnet", dependencies=[authorized]) +def botnet(action: Actions, group: typing.Optional[Groups] = None, + runner: runctl.RunIntelMQCtl = Depends(runner)): + return JSONFileResponse(runner.botnet(action, group)) + + +@router.get("/bot", dependencies=[authorized]) +def bot(action: Actions, id: str = Depends(ID), runner: runctl.RunIntelMQCtl = Depends(runner)): + return JSONFileResponse(runner.bot(action, id)) + + +@router.get("/getlog", dependencies=[authorized, cached]) +def get_log(lines: int, id: str = Depends(ID), level: Levels = "DEBUG", + runner: runctl.RunIntelMQCtl = Depends(runner)): + return JSONFileResponse(runner.log(id, lines, level)) + + +@router.get("/queues", dependencies=[authorized, cached]) +def queues(runner: runctl.RunIntelMQCtl = Depends(runner)): + return JSONFileResponse(runner.list("queues")) + + +@router.get("/queues-and-status", dependencies=[authorized, cached]) +def queues_and_status(runner: runctl.RunIntelMQCtl = Depends(runner)): + return JSONFileResponse(runner.list("queues-and-status")) + + +@router.get("/bots", dependencies=[authorized, cached]) +def bots(runner: runctl.RunIntelMQCtl = Depends(runner)): + return JSONFileResponse(runner.list("bots")) + + +@router.get("/version", dependencies=[authorized], response_model=dict) +def version(runner: runctl.RunIntelMQCtl = Depends(runner)): + return runner.version() + + +@router.get("/check", dependencies=[authorized]) +def check(runner: runctl.RunIntelMQCtl = Depends(runner)): + return JSONFileResponse(runner.check()) + + +@router.get("/clear", dependencies=[authorized]) +def clear(id: str = Depends(ID), runner: runctl.RunIntelMQCtl = Depends(runner)): + return JSONFileResponse(runner.clear(id)) + + +@router.post("/run", dependencies=[authorized], response_model=str) +def run(bot: str, cmd: BotCmds, show: bool = False, dry: bool = False, msg: str = Form(default=""), + runner: runctl.RunIntelMQCtl = Depends(runner)): + return runner.run(bot, cmd, show, dry, msg) + + +@router.get("/debug", dependencies=[authorized]) +def debug(runner: runctl.RunIntelMQCtl = Depends(runner)): + return JSONFileResponse(runner.debug()) + + +@router.get("/config", dependencies=[authorized]) +def config(file: str, fetch: bool = False, + file_access: files.FileAccess = Depends(file_access)): + result = file_access.load_file_or_directory(file, fetch) + if result is None: + return ["Unknown resource"] + + content_type, contents = result + return Response(contents, headers={"content-type": content_type}) + + +@router.post("/login", status_code=status.HTTP_200_OK, response_model=TokenResponse) +def login(username: str = Form(...), password: str = Form(...), + session: session.SessionStore = Depends(session_store)): + if session is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Session store is disabled by configuration! No login possible and required.", + ) + else: + known = session.verify_user(username, password) + if known is not None: + token = session.new_session({"username": username}) + return {"login_token": token, + "username": username, + } + else: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid username and/or password.") + + +@router.get("/harmonization", dependencies=[authorized], response_model=dict) +def get_harmonization(runner: runctl.RunIntelMQCtl = Depends(runner)): + harmonization = pathlib.Path('/opt/intelmq/etc/harmonization.conf') + paths = runner.get_paths() + if 'CONFIG_DIR' in paths: + harmonization = pathlib.Path(paths['CONFIG_DIR']) / 'harmonization.conf' + try: + return json.loads(harmonization.read_text()) + except OSError as e: + print(f"Could not read {harmonization}: {str(e)}") + return {} + + +@router.get("/runtime", dependencies=[authorized], response_model=dict) +def get_runtime(): + return utils.get_runtime() + + +@router.post("/runtime", dependencies=[authorized], response_model=str, + response_class=PlainTextResponse) +def post_runtime(body: dict): + try: + utils.set_runtime(body) + return "success" + except Exception as e: + print(f"Could not write runtime {str(e)}") + return str(e) + + +@router.get("/positions", dependencies=[authorized], response_model=dict) +def get_positions(runner: runctl.RunIntelMQCtl = Depends(runner)): + positions = pathlib.Path('/opt/intelmq/etc/manager/positions.conf') + paths = runner.get_paths() + if 'CONFIG_DIR' in paths: + positions = pathlib.Path(paths['CONFIG_DIR']) / 'manager/positions.conf' + try: + return json.loads(positions.read_text()) + except OSError as e: + print(f"Could not read {positions}: {str(e)}") + return {} + + +@router.post("/positions", dependencies=[authorized], response_model=str, + response_class=PlainTextResponse) +def post_positions(body: dict, runner: runctl.RunIntelMQCtl = Depends(runner)): + positions = pathlib.Path('/opt/intelmq/etc/manager/positions.conf') + paths = runner.get_paths() + if 'CONFIG_DIR' in paths: + positions = pathlib.Path(paths['CONFIG_DIR']) / 'manager/positions.conf' + try: + positions.parent.mkdir(exist_ok=True) + positions.write_text(json.dumps(body, indent=4)) + return "success" + except OSError as e: + print(f"Error creating {positions.parent} or writing to {positions}: {str(e)}") + return str(e) diff --git a/intelmq/api/runctl.py b/intelmq/api/runctl.py new file mode 100644 index 000000000..beea02cd0 --- /dev/null +++ b/intelmq/api/runctl.py @@ -0,0 +1,156 @@ +"""Control IntelMQ with the intelmqctl command. + +SPDX-FileCopyrightText: 2020 Intevation GmbH +SPDX-License-Identifier: AGPL-3.0-or-later + +Funding: of initial version by SUNET +Author(s): + * Bernhard Herzog + +This module contains code to run the intelmqctl command as needed by the +manager's API backend. +""" + +import io +import json +import subprocess +from typing import List, Dict, Optional + +from intelmq.api.util import shell_command_for_errors +from intelmq.version import __version__ + +# +# Typing aliases for use with RunIntelMQCtl +# +# Arguments for a subprocess command line are a list of strings. +Args = List[str] + +# JSON output of intelmqctl is returned as bytes and then simply +# passed to the response without (de)serialization. Type alias +# for clear understanding of expected format and content type +JSONFile = bytes + + +class IntelMQCtlError(Exception): + + def __init__(self, error_dict): + self.error_dict = error_dict + + def __str__(self): + return self.error_dict["message"] + + +failure_tips = [ + ("sudo: no tty present and no askpass program specified", + "Is sudoers file or IntelMQ-Manager " + "set up correctly?"), + ("Permission denied: '/opt/intelmq", + "Has the user accessing intelmq folder the read/write permissions?" + " This might be user intelmq or www-data, depending on your configuration," + " ex: sudo chown intelmq.intelmq /opt/intelmq -R" + " && sudo chmod u+rw /opt/intelmq -R"), + ("sqlite3.OperationalError: no such table", + "SQLite database may not have been" + " initialized.") # noqa +] + + +class RunIntelMQCtl: + + def __init__(self, base_cmd: Args): + self.base_cmd = base_cmd + + def _run_intelmq_ctl(self, args: Args) -> subprocess.CompletedProcess: + command = self.base_cmd + args + result = subprocess.run(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + # Detect errors + # + # The logic here follows the original PHP code but it differs in + # some respects. One difference is that intelmqctl can exit with + # an exit code != 0 even if it actually was successful, so we + # cannot actually use the exit code. The PHP code appears to use + # it, but the exit code it examines is not the exit code of + # intelmqctl but of a little shell script that basically ends up + # ignoring intelmqctl's exit code. + if not result.stdout or result.stderr: + message = str(result.stderr, errors="replace") + + if not message: + message = "Failed to execute intelmqctl." + + for msg_fragment, tip in failure_tips: + if msg_fragment in message: + break + else: + tip = "" + + raise IntelMQCtlError({"tip": tip, + "message": message, + "command": shell_command_for_errors(command), + }) + return result + + def _run_json(self, args: Args) -> JSONFile: + completed = self._run_intelmq_ctl(["--type", "json"] + args) + return completed.stdout + + def _run_str(self, args: Args) -> str: + completed = self._run_intelmq_ctl(args) + return str(completed.stdout, "ascii") + + def botnet(self, action: str, group: Optional[str]) -> JSONFile: + args = [action] + if group is not None and group != "botnet": + args.extend(["--group", group]) + return self._run_json(args) + + def bot(self, action: str, bot_id: str) -> JSONFile: + return self._run_json([action, bot_id]) + + def log(self, bot_id: str, lines: int, level: str) -> JSONFile: + if level == "ALL": + level = "DEBUG" + return self._run_json(["log", bot_id, str(lines), level]) + + def list(self, kind: str) -> JSONFile: + return self._run_json(["list", kind]) + + def version(self) -> Dict[str, str]: + return {"intelmq": __version__} + + def check(self) -> JSONFile: + return self._run_json(["check"]) + + def clear(self, queue_name: str) -> JSONFile: + return self._run_json(["clear", queue_name]) + + def run(self, bot_id: str, cmd: str, show: bool, dry: bool, + msg: str) -> str: + args = ["run", bot_id] + if cmd == "get": + args.extend(["message", "get"]) + elif cmd == "pop": + args.extend(["message", "pop"]) + elif cmd == "send": + args.extend(["message", "send", msg]) + elif cmd == "process": + args.append("process") + if show: + args.append("--show-sent") + if dry: + args.append("--dry") + args.extend(["--msg", msg]) + return self._run_str(args) + + def debug(self, get_paths: bool = False) -> JSONFile: + args = ["debug"] + if get_paths: + args.append("--get-paths") + return self._run_json(args) + + def get_paths(self) -> Dict[str, str]: + return dict(json.load(io.BytesIO(self.debug(get_paths=True)))["paths"]) diff --git a/intelmq/api/session.py b/intelmq/api/session.py new file mode 100644 index 000000000..1eb8e067e --- /dev/null +++ b/intelmq/api/session.py @@ -0,0 +1,165 @@ +"""Session support for IntelMQ-Manager + +SPDX-FileCopyrightText: 2020 Intevation GmbH +SPDX-License-Identifier: AGPL-3.0-or-later + +Funding: of initial version by SUNET +Author(s): + * Bernhard Herzog +""" + +import os +from typing import Tuple, Optional, Union +from contextlib import contextmanager +import json +import threading +import hashlib + +import sqlite3 + + +INIT_DB_SQL = """ +BEGIN; +CREATE TABLE version (version INTEGER); +INSERT INTO version (version) VALUES (1); + +CREATE TABLE session ( + session_id TEXT PRIMARY KEY, + modified TIMESTAMP, + data BLOB +); + +CREATE TABLE user( + username TEXT PRIMARY KEY, + password TEXT, + salt TEXT +); + +COMMIT; +""" + +LOOKUP_SESSION_SQL = """ +SELECT data FROM session WHERE session_id = ?; +""" + +STORE_SESSION_SQL = """ +INSERT OR REPLACE INTO session (session_id, modified, data) +VALUES (?, CURRENT_TIMESTAMP, ?); +""" + +EXPIRATION_SQL = """ +DELETE FROM session + WHERE strftime('%s', 'now') - strftime('%s', modified) > ?; +""" + +TOUCH_SESSION_SQL = """ +UPDATE session SET modified = CURRENT_TIMESTAMP WHERE session_id = ?; +""" + +ADD_USER_SQL = """ +INSERT OR REPLACE INTO user (username, password, salt) VALUES (?, ?, ?); +""" + +LOOKUP_USER_SQL = """ +SELECT username, password, salt FROM user WHERE username = ?; +""" + + +class SessionStore: + """Session store based on SQLite + + The SQLite database is used in autocommit mode avoid blocking + connections to the same database from other processes. This ensures + that no transactions are open for very long. The transactions this + class needs to do are all single statements anyway, so autocommit is + no problem. + + Instances of this class can be used by multiple threads + simultaneously. Use of the underlying sqlite connection object is + serialized between threads with a lock. + """ + + def __init__(self, dbname: str, max_duration: int): + self.dbname = dbname + self.max_duration = max_duration + if not os.path.isfile(self.dbname): + self.init_sqlite_db() + self.lock = threading.Lock() + self.connection = self.connect() + + def connect(self) -> sqlite3.Connection: + return sqlite3.connect(self.dbname, check_same_thread=False, + isolation_level=None) + + @contextmanager + def get_con(self): + with self.lock: + yield self.connection + + def init_sqlite_db(self): + with self.connect() as con: + con.executescript(INIT_DB_SQL) + + def execute(self, stmt: str, params: tuple) -> Optional[tuple]: + try: + with self.get_con() as con: + return con.execute(stmt, params).fetchone() + except sqlite3.OperationalError as exc: + print(f"SQLite3-Error ({exc}): Possibly missing write permissions to" + " session file (or the folder it is located in).") + return None + + # + # Methods for session data + # + + def expire_sessions(self): + self.execute(EXPIRATION_SQL, (self.max_duration,)) + + def get(self, session_id: str) -> Optional[dict]: + self.expire_sessions() + row = self.execute(LOOKUP_SESSION_SQL, (session_id,)) + if row is not None: + return json.loads(row[0]) + return None + + def set(self, session_id: str, session_data: dict): + self.execute(STORE_SESSION_SQL, + (session_id, json.dumps(session_data))) + + def new_session(self, session_data: dict) -> str: + token = os.urandom(16).hex() + self.set(token, session_data) + return token + + def verify_token(self, token: str) -> Union[bool, dict]: + session_data = self.get(token) + if session_data is not None: + self.execute(TOUCH_SESSION_SQL, (token,)) + return session_data + return False + + # + # User account methods + # + + def add_user(self, username: str, password: str): + hashed, salt = self.hash_password(password) + self.execute(ADD_USER_SQL, (username, hashed, salt)) + + def verify_user(self, username: str, password: str) -> Optional[dict]: + row = self.execute(LOOKUP_USER_SQL, (username,)) + if row is not None: + username, stored_hash, salt = row + hashed = self.hash_password(password, bytes.fromhex(salt))[0] + if hashed == stored_hash: + return {"username": username} + return None + + def hash_password(self, password: str, + salt: Optional[bytes] = None) -> Tuple[str, str]: + if salt is None: + salt = os.urandom(16) + hashed = hashlib.pbkdf2_hmac("sha256", password.encode("utf8"), salt, + 100000) + return (hashed.hex(), salt.hex()) diff --git a/intelmq/api/util.py b/intelmq/api/util.py new file mode 100644 index 000000000..1e1559f24 --- /dev/null +++ b/intelmq/api/util.py @@ -0,0 +1,54 @@ +"""Helper functions for the API + +SPDX-FileCopyrightText: 2020 Intevation GmbH +SPDX-License-Identifier: AGPL-3.0-or-later + +Funding: of initial version by SUNET +Author(s): + * Bernhard Herzog +""" + +import os +import pwd +import shlex +from typing import List + + +def effective_user_name() -> str: + """Return the name of the effective user""" + return pwd.getpwuid(os.geteuid()).pw_name + + +def format_shell_command(words: List[str]) -> str: + """Format a shell command as a string for use with a shell. + + This function turns a command given as a list of strings as a single + string that could be interpreted by the shell. + + When invoking subprocesses it's usually best to use a list of + strings as the command so that no shell is involved so that one + doesn't have to care about quoting. However, for error messages it's + convenient for the user to see the command as it would be written + for the shell so that it's easy to e.g. test it in an interactive + shell. + + This function is basically identical to shlex.join function that was + added in Python 3.8. + """ + return " ".join(shlex.quote(word) for word in words) + + +def shell_command_for_errors(words: List[str]) -> str: + """Return a formatted shell command for error messages. + + The return value contains the command formatted for use in a shell + with a prefix that uses sudo to execute the command as the users + this API is running as. This is intended primarily for error + messages so that users of the web interface can use the command to + replicate the problems that may be encountered. + + This is particularly interesting for the usual case where we do not + invoke intelmqctl directly but run it via sudo from code executed by + special users like www-data. + """ + return format_shell_command(["sudo", "-u", effective_user_name()] + words) diff --git a/intelmq/bin/intelmqsetup.py b/intelmq/bin/intelmqsetup.py deleted file mode 100755 index 5d4f65d7c..000000000 --- a/intelmq/bin/intelmqsetup.py +++ /dev/null @@ -1,352 +0,0 @@ -""" -© 2019-2021 nic.at GmbH - -SPDX-License-Identifier: AGPL-3.0-or-later - -Sets up an intelmq environment after installation or upgrade by - * creating needed directories - * set intelmq as owner for those - * providing example configuration files if not already existing - -If intelmq-api is installed, the similar steps are performed: - * creates needed directories - * sets the webserver as group for them - * sets group write permissions - -Reasoning: -Pip does not (and cannot) create `/opt/intelmq`/user-given ROOT_DIR, as described in -https://github.com/certtools/intelmq/issues/819 -""" -import argparse -import os -import shutil -import stat -import sys -import pkg_resources - -from grp import getgrnam -from pathlib import Path -from pwd import getpwnam -from subprocess import run, CalledProcessError -from tempfile import NamedTemporaryFile -from typing import Optional - -try: - import intelmq_api - import intelmq_api.version -except ImportError: - intelmq_api = None - -try: - import intelmq_manager -except ImportError: - intelmq_manager = None -else: - try: - import intelmq_manager.build - except ImportError: - intelmq_manager_has_build = False - else: - intelmq_manager_has_build = True - -from termstyle import red -from intelmq import (CONFIG_DIR, DEFAULT_LOGGING_PATH, ROOT_DIR, VAR_RUN_PATH, - VAR_STATE_PATH, STATE_FILE_PATH) -from intelmq.bin.intelmqctl import IntelMQController - - -FILE_OUTPUT_PATH = Path(VAR_STATE_PATH) / 'file-output/' -ETC_INTELMQ = Path('/etc/intelmq/') -ETC_INTELMQ_MANAGER = ETC_INTELMQ / 'manager/' -WEBSERVER_CONFIG_DIR = None # "cache" for the webserver configuration directory -NOTE_WEBSERVER_RELOAD = False # if the webserver needs to be reloaded - - -def basic_checks(skip_ownership): - if os.geteuid() != 0 and not skip_ownership: - sys.exit(red('You need to run this program as root for setting file ownership!')) - if not ROOT_DIR: - sys.exit(red('Not a pip-installation of IntelMQ, nothing to initialize.')) - - if skip_ownership: - return - try: - getpwnam('intelmq') - except KeyError: - sys.exit(red("User 'intelmq' does not exist. Please create it and then re-run this program.")) - try: - getgrnam('intelmq') - except KeyError: - sys.exit(red("Group 'intelmq' does not exist. Please create it and then re-run this program.")) - - -def create_directory(directory: str, octal_mode: int): - directory = Path(directory) - readable_mode = stat.filemode(octal_mode) - if not directory.is_dir(): - directory.mkdir(mode=octal_mode, exist_ok=True, parents=True) - print(f'Created directory {directory!s} with permissions {readable_mode}.') - else: - current_mode = directory.stat().st_mode - if current_mode != octal_mode: - current_mode_readable = stat.filemode(current_mode) - print(f'Fixed wrong permissions of {directory!s}: {current_mode_readable!r} -> {readable_mode!r}.') - directory.chmod(octal_mode) - - -def change_owner(file: str, owner: Optional[str] = None, group: Optional[str] = None, log: bool = True): - if Path(file).as_posix() in ['/', '//']: - # Skip taking ownership over system root path - return - if owner and Path(file).owner() != owner: - if log: - print(f'Fixing owner of {file!s}.') - shutil.chown(file, user=owner) - if group and Path(file).group() != group: - if log: - print(f'Fixing group of {file!s}.') - shutil.chown(file, group=group) - - -def find_webserver_user(): - candidates = ('www-data', 'wwwrun', 'httpd', 'apache') - for candidate in candidates: - try: - getpwnam(candidate) - except KeyError: - pass - else: - print(f'Detected Apache username {candidate!r}.') - return candidate - else: - sys.exit(red("Unable to detect Apache user name. " - "Please re-run this program and give the Apache user name with '--webserver-user'.")) - - -def find_webserver_configuration_directory(): - global WEBSERVER_CONFIG_DIR - if WEBSERVER_CONFIG_DIR: - return WEBSERVER_CONFIG_DIR - webserver_configuration_dir_candidates = (Path('/etc/apache2/conf-available/'), - Path('/etc/apache2/conf.d/'), - Path('/etc/httpd/conf.d/')) - for webserver_configuration_dir_candidate in webserver_configuration_dir_candidates: - if webserver_configuration_dir_candidate.exists(): - print(f'Detected Apache configuration directory {webserver_configuration_dir_candidate!s}.') - WEBSERVER_CONFIG_DIR = webserver_configuration_dir_candidate - webserver_configuration_dir_candidate.as_posix - return webserver_configuration_dir_candidate - else: - sys.exit(red("Unable to detect Apache configuration directory. " - "Please re-run this program and give the Apache configuration directory with '--webserver-configuration-directory'.")) - - -def debian_activate_apache_config(config_name: str): - if 'available' not in WEBSERVER_CONFIG_DIR.as_posix(): - return # not a Debian system - available = WEBSERVER_CONFIG_DIR / config_name - enabled = Path(WEBSERVER_CONFIG_DIR.as_posix().replace('available', 'enabled')) / config_name - if not enabled.exists(): - enabled.symlink_to(available) - print(f'Created symbolic link {enabled!s} pointing to {available!s}.') - - -def intelmqsetup_core(ownership=True, state_file=STATE_FILE_PATH): - create_directory(FILE_OUTPUT_PATH, 0o40755) - create_directory(VAR_RUN_PATH, 0o40755) - create_directory(DEFAULT_LOGGING_PATH, 0o40755) - create_directory(CONFIG_DIR, 0o40775) - - example_path = Path(pkg_resources.resource_filename('intelmq', 'etc')) - example_confs = [example_path / 'runtime.yaml', example_path / 'harmonization.conf'] - for example_conf in example_confs: - fname = Path(example_conf).name - destination_file = Path(CONFIG_DIR) / fname - if destination_file.exists(): - print(f'Not overwriting existing {fname!r} with example.') - log_ownership_change = True - else: - shutil.copy(example_conf, CONFIG_DIR) - print(f'Installing example {fname!r} to {CONFIG_DIR}.') - log_ownership_change = False # For installing the new files, we don't need to inform the admin that the permissions have been "fixed" - if ownership: - change_owner(destination_file, owner='intelmq', group='intelmq', log=log_ownership_change) - - if ownership: - print('Setting intelmq as owner for it\'s directories.') - for obj in (CONFIG_DIR, DEFAULT_LOGGING_PATH, ROOT_DIR, VAR_RUN_PATH, - VAR_STATE_PATH, FILE_OUTPUT_PATH, Path(STATE_FILE_PATH).parent): - change_owner(obj, owner='intelmq') - - print('Calling `intelmqctl upgrade-config` to update/create state file.') - controller = IntelMQController(interactive=False, no_file_logging=True, - drop_privileges=False) - controller.upgrade_conf(state_file=state_file, no_backup=True) - if ownership: - change_owner(STATE_FILE_PATH, owner='intelmq', group='intelmq') - - -def intelmqsetup_api(ownership: bool = True, webserver_user: Optional[str] = None): - intelmq_group = getgrnam('intelmq') - webserver_user = webserver_user or find_webserver_user() - - create_directory(ETC_INTELMQ, 0o40775) - if ownership: - change_owner(CONFIG_DIR, group='intelmq') - change_owner(ETC_INTELMQ, owner='intelmq', group='intelmq') - - # Manager configuration directory - create_directory(ETC_INTELMQ_MANAGER, 0o40775) - if ownership: - change_owner(ETC_INTELMQ_MANAGER, group='intelmq') - - base = Path(pkg_resources.resource_filename('intelmq_api', '')).parent - api_config = base / 'etc/intelmq/api-config.json' - etc_intelmq_config = ETC_INTELMQ / 'api-config.json' - api_sudoers = base / 'etc/intelmq/api-sudoers.conf' - etc_sudoers_api = Path('/etc/sudoers.d/01_intelmq-api') # same path as used in the packages - api_manager_positions = base / 'etc/intelmq/manager/positions.conf' - etc_intelmq_manager_positions = ETC_INTELMQ_MANAGER / 'positions.conf' - - if not base.as_posix().startswith('/usr/'): - # Paths differ in editable installations - print(red("Detected an editable (egg-link) pip-installation of 'intelmq-api'. Some feature of this program may not work.")) - - if api_config.exists() and not etc_intelmq_config.exists(): - shutil.copy(api_config, etc_intelmq_config) - print(f'Copied {api_config!s} to {ETC_INTELMQ!s}.') - elif not api_config.exists() and not etc_intelmq_config.exists(): - print(red(f'Unable to install api-config.json: Neither {api_config!s} nor {etc_intelmq_config!s} exists.')) - if api_sudoers.exists() and not etc_sudoers_api.exists(): - with open(api_sudoers) as sudoers: - original_sudoers = sudoers.read() - sudoers = original_sudoers.replace('www-data', webserver_user) - with NamedTemporaryFile(mode='w') as tmp_file: - tmp_file.write(sudoers) - tmp_file.flush() - try: - run(('visudo', '-c', tmp_file.name)) - except CalledProcessError: - sys.exit(red('Fatal error: Validation of adapted sudoers-file failed. Please report this bug.')) - change_owner(tmp_file.name, owner='root', group='root', log=False) - Path(tmp_file.name).chmod(0o440) - shutil.copy(tmp_file.name, etc_sudoers_api) - print(f'Copied {api_sudoers!s} to {etc_sudoers_api!s}.') - elif not api_sudoers.exists() and not etc_sudoers_api.exists(): - print(red(f'Unable to install api-sudoers.conf: Neither {api_sudoers!s} nor {etc_sudoers_api!s} exists.')) - if api_manager_positions.exists() and not etc_intelmq_manager_positions.exists(): - shutil.copy(api_manager_positions, etc_intelmq_manager_positions) - print(f'Copied {api_manager_positions!s} to {etc_intelmq_manager_positions!s}.') - etc_intelmq_manager_positions.chmod(0o664) - change_owner(etc_intelmq_manager_positions, owner='intelmq', group='intelmq', log=False) - elif not api_manager_positions.exists() and not etc_intelmq_manager_positions.exists(): - print(red(f'Unable to install positions.conf: Neither {api_manager_positions!s} nor {etc_intelmq_manager_positions!s} exists.')) - - if webserver_user not in intelmq_group.gr_mem: - sys.exit(red(f"Webserver user {webserver_user} is not a member of the 'intelmq' group. " - f"Please add it with: 'usermod -aG intelmq {webserver_user}'.")) - - -def intelmqsetup_api_webserver_configuration(webserver_configuration_directory: Optional[str] = None): - webserver_configuration_dir = webserver_configuration_directory or find_webserver_configuration_directory() - api_config = Path(pkg_resources.resource_filename('intelmq_api', '')).parent / 'etc/intelmq/api-apache.conf' - apache_api_config = webserver_configuration_dir / 'api-apache.conf' - if api_config.exists() and not apache_api_config.exists(): - shutil.copy(api_config, apache_api_config) - print(f'Copied {api_config!s} to {ETC_INTELMQ!s}.') - debian_activate_apache_config('api-apache.conf') - - global NOTE_WEBSERVER_RELOAD - NOTE_WEBSERVER_RELOAD = True - elif not api_config.exists() and not apache_api_config.exists(): - print(red(f'Unable to install webserver configuration api-config.conf: Neither {api_config!s} nor {apache_api_config!s} exists.')) - - print('Setup of intelmq-api successful.') - - -def intelmqsetup_manager_webserver_configuration(webserver_configuration_directory: Optional[str] = None): - webserver_configuration_dir = webserver_configuration_directory or find_webserver_configuration_directory() - manager_config_1 = Path(pkg_resources.resource_filename('intelmq_manager', '')).parent / 'etc/intelmq/manager-apache.conf' - # IntelMQ Manager >= 3.1.0 - manager_config_2 = Path(pkg_resources.resource_filename('intelmq_manager', '')) / 'manager-apache.conf' - manager_config = manager_config_2 if manager_config_2.exists() else manager_config_1 - apache_manager_config = webserver_configuration_dir / 'manager-apache.conf' - if manager_config.exists() and not apache_manager_config.exists(): - shutil.copy(manager_config, apache_manager_config) - print(f'Copied {manager_config!s} to {apache_manager_config!s}.') - debian_activate_apache_config('manager-apache.conf') - - global NOTE_WEBSERVER_RELOAD - NOTE_WEBSERVER_RELOAD = True - elif not manager_config.exists() and not apache_manager_config.exists(): - print(red(f'Unable to install webserver configuration manager-config.conf: Neither {manager_config_1!s} nor {manager_config_2!s} nor {apache_manager_config!s} exist.')) - - -def intelmqsetup_manager_generate(): - if not intelmq_manager_has_build: - print('Unable to build intelmq-manager files. Installed version of intelmq-manager is too old, at least version 3.1.0 is required.', - file=sys.stderr) - return - src_dir = Path(pkg_resources.resource_filename('intelmq_manager', '')) - html_dir_destination = Path('/usr/share/intelmq_manager/html') - - if not src_dir.as_posix().startswith('/usr/'): - # Paths differ in editable installations - print(red("Detected an editable (egg-link) pip-installation of intelmq-manager. Some features of this program may not work.")) - - intelmq_manager.build.buildhtml(html_dir_destination) - - -def main(): - parser = argparse.ArgumentParser("Set's up directories and example " - "configurations for IntelMQ.") - parser.add_argument('--skip-ownership', action='store_true', - help='Skip setting file ownership') - parser.add_argument('--state-file', - help='The state file location to use.', - default=STATE_FILE_PATH) - parser.add_argument('--webserver-user', - help='The webserver to use instead of auto-detection.') - parser.add_argument('--webserver-configuration-directory', - help='The webserver configuration directory to use instead of auto-detection.') - parser.add_argument('--skip-api', - help='Skip set-up of intelmq-api.', - action='store_true') - parser.add_argument('--skip-webserver', - help='Skip all operations on the webserver configuration, affects the API and Manager.', - action='store_true') - parser.add_argument('--skip-manager', - help='Skip set-up of intelmq-manager.', - action='store_true') - args = parser.parse_args() - - basic_checks(skip_ownership=args.skip_ownership) - intelmqsetup_core(ownership=not args.skip_ownership, - state_file=args.state_file) - if intelmq_api and not args.skip_api: - print(f'Running setup for intelmq-api (version {intelmq_api.version.__version__}).') - intelmqsetup_api(ownership=not args.skip_ownership, - webserver_user=args.webserver_user) - if not args.skip_webserver: - print('Running webserver setup for intelmq-api.') - intelmqsetup_api_webserver_configuration(webserver_configuration_directory=args.webserver_configuration_directory) - else: - print('Skipping set-up of intelmq-api.') - if intelmq_manager and not args.skip_manager and not args.skip_webserver: - print('Running webserver setup for intelmq-manager.') - intelmqsetup_manager_webserver_configuration(webserver_configuration_directory=args.webserver_configuration_directory) - else: - print('Skipping intelmq-manager configuration.') - if intelmq_manager and not args.skip_manager: - manager_version = pkg_resources.get_distribution('intelmq-manager').version - print(f'Generate and save intelmq-manager (version {manager_version}) static files.') - intelmqsetup_manager_generate() - - if NOTE_WEBSERVER_RELOAD: - print('Reload the webserver to make the changes effective.') - - print("'intelmqsetup' completed.") - - -if __name__ == '__main__': - main() diff --git a/intelmq/etc/runtime.yaml b/intelmq/etc/runtime.yaml index c39c7b248..5275ebc77 100644 --- a/intelmq/etc/runtime.yaml +++ b/intelmq/etc/runtime.yaml @@ -1,3 +1,10 @@ +global: + server: + host: 127.0.0.1 + port: 8080 + enable_webgui: true + intelmq_ctl_cmd: ["sudo", "-u", "intelmq", "/usr/local/bin/intelmqctl"] + cymru-whois-expert: bot_id: cymru-whois-expert description: Cymru Whois (IP to ASN) is the bot responsible to add network information diff --git a/intelmq/lib/setuputils.py b/intelmq/lib/setuputils.py new file mode 100644 index 000000000..af1568ae3 --- /dev/null +++ b/intelmq/lib/setuputils.py @@ -0,0 +1,118 @@ +""" +© 2019-2021 nic.at GmbH + +SPDX-License-Identifier: AGPL-3.0-or-later + +Sets up an intelmq environment after installation or upgrade by + * creating needed directories + * set intelmq as owner for those + * providing example configuration files if not already existing + +Reasoning: +Pip does not (and cannot) create `/opt/intelmq`/user-given ROOT_DIR, as described in +https://github.com/certtools/intelmq/issues/819 +""" +import argparse +import os +import shutil +import stat +import sys +import pkg_resources + +from grp import getgrnam +from pathlib import Path +from pwd import getpwnam +from subprocess import run, CalledProcessError +from tempfile import NamedTemporaryFile +from typing import Optional + + +from termstyle import red +from intelmq import (CONFIG_DIR, DEFAULT_LOGGING_PATH, ROOT_DIR, VAR_RUN_PATH, + VAR_STATE_PATH, STATE_FILE_PATH) +from intelmq.bin.intelmqctl import IntelMQController + + +FILE_OUTPUT_PATH = Path(VAR_STATE_PATH) / 'file-output/' +ETC_INTELMQ = Path('/etc/intelmq/') + + +def basic_checks(skip_ownership): + if os.geteuid() != 0 and not skip_ownership: + sys.exit(red('You need to run this program as root for setting file ownership!')) + if not ROOT_DIR: + sys.exit(red('Not a pip-installation of IntelMQ, nothing to initialize.')) + + if skip_ownership: + return + try: + getpwnam('intelmq') + except KeyError: + sys.exit(red("User 'intelmq' does not exist. Please create it and then re-run this program.")) + try: + getgrnam('intelmq') + except KeyError: + sys.exit(red("Group 'intelmq' does not exist. Please create it and then re-run this program.")) + + +def create_directory(directory: str, octal_mode: int): + directory = Path(directory) + readable_mode = stat.filemode(octal_mode) + if not directory.is_dir(): + directory.mkdir(mode=octal_mode, exist_ok=True, parents=True) + print(f'Created directory {directory!s} with permissions {readable_mode}.') + else: + current_mode = directory.stat().st_mode + if current_mode != octal_mode: + current_mode_readable = stat.filemode(current_mode) + print(f'Fixed wrong permissions of {directory!s}: {current_mode_readable!r} -> {readable_mode!r}.') + directory.chmod(octal_mode) + + +def change_owner(file: str, owner: Optional[str] = None, group: Optional[str] = None, log: bool = True): + if Path(file).as_posix() in ['/', '//']: + # Skip taking ownership over system root path + return + if owner and Path(file).owner() != owner: + if log: + print(f'Fixing owner of {file!s}.') + shutil.chown(file, user=owner) + if group and Path(file).group() != group: + if log: + print(f'Fixing group of {file!s}.') + shutil.chown(file, group=group) + + +def intelmqsetup_core(ownership=True, state_file=STATE_FILE_PATH): + create_directory(FILE_OUTPUT_PATH, 0o40755) + create_directory(VAR_RUN_PATH, 0o40755) + create_directory(DEFAULT_LOGGING_PATH, 0o40755) + create_directory(CONFIG_DIR, 0o40775) + + example_path = Path(pkg_resources.resource_filename('intelmq', 'etc')) + example_confs = [example_path / 'runtime.yaml', example_path / 'harmonization.conf'] + for example_conf in example_confs: + fname = Path(example_conf).name + destination_file = Path(CONFIG_DIR) / fname + if destination_file.exists(): + print(f'Not overwriting existing {fname!r} with example.') + log_ownership_change = True + else: + shutil.copy(example_conf, CONFIG_DIR) + print(f'Installing example {fname!r} to {CONFIG_DIR}.') + log_ownership_change = False # For installing the new files, we don't need to inform the admin that the permissions have been "fixed" + if ownership: + change_owner(destination_file, owner='intelmq', group='intelmq', log=log_ownership_change) + + if ownership: + print('Setting intelmq as owner for it\'s directories.') + for obj in (CONFIG_DIR, DEFAULT_LOGGING_PATH, ROOT_DIR, VAR_RUN_PATH, + VAR_STATE_PATH, FILE_OUTPUT_PATH, Path(STATE_FILE_PATH).parent): + change_owner(obj, owner='intelmq') + + print('Calling `intelmqctl upgrade-config` to update/create state file.') + controller = IntelMQController(interactive=False, no_file_logging=True, + drop_privileges=False) + controller.upgrade_conf(state_file=state_file, no_backup=True) + if ownership: + change_owner(STATE_FILE_PATH, owner='intelmq', group='intelmq') diff --git a/intelmq/lib/utils.py b/intelmq/lib/utils.py index 5701db1af..1021eeb56 100644 --- a/intelmq/lib/utils.py +++ b/intelmq/lib/utils.py @@ -925,6 +925,11 @@ def get_global_settings() -> dict: return runtime_conf.get('global', {}) +def get_server_settings() -> dict: + global_settings = get_global_settings() + return global_settings.get('server', {}) + + def set_runtime(runtime: dict) -> dict: write_configuration(configuration_filepath=RUNTIME_CONF_FILE, content=runtime) return get_runtime() diff --git a/intelmq/server.py b/intelmq/server.py new file mode 100644 index 000000000..8a7a94c5e --- /dev/null +++ b/intelmq/server.py @@ -0,0 +1,43 @@ +"""Main entrypoint for the API application + +SPDX-FileCopyrightText: 2022 CERT.at GmbH +SPDX-License-Identifier: AGPL-3.0-or-later +""" + +import os +import pathlib + +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from fastapi.middleware.cors import CORSMiddleware + +from intelmq.version import __version__ +import intelmq.api.dependencies +import intelmq.api.exceptions + +from intelmq.api.config import config +from intelmq.api.router import router as api_router +from intelmq.web.router import router as web_router + +app = FastAPI( + title="IntelMQ", + version=__version__, + root_path=os.environ.get("ROOT_PATH", "") +) + + +@app.on_event("startup") +def init_app(): + intelmq.api.dependencies.startup(config) + + +app.add_middleware(CORSMiddleware, allow_origins=config.allow_origins, allow_methods=("GET", "POST")) + +app.include_router(api_router, prefix="/api/v1") + +if config.enable_webgui: + static_files = pathlib.Path(__file__).parent / "web" / "static" + app.mount("/static", StaticFiles(directory=static_files), name="static") + app.include_router(web_router) + +intelmq.api.exceptions.register(app) diff --git a/intelmq/tests/api/__init__.py b/intelmq/tests/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/intelmq/tests/api/test_api.py b/intelmq/tests/api/test_api.py new file mode 100644 index 000000000..4e6ff930d --- /dev/null +++ b/intelmq/tests/api/test_api.py @@ -0,0 +1,207 @@ +"""Basic tests for the API endpoints + +SPDX-FileCopyrightText: 2022 CERT.at Gmbh +SPDX-License-Identifier: AGPL-3.0-or-later +""" + +import json +import os +import subprocess +import tempfile +from tempfile import TemporaryDirectory +from typing import Dict, List, Optional +from unittest import TestCase, mock +from unittest.mock import patch, MagicMock +from fastapi.testclient import TestClient + + +with patch("intelmq.lib.utils.get_runtime", MagicMock(return_value={})): + + from intelmq.api import dependencies + from intelmq.api.router import runner + from intelmq.api.dependencies import session_store + from intelmq.api.runctl import RunIntelMQCtl + from intelmq.api.session import SessionStore + from intelmq.api.config import Config + from intelmq.version import __version__ + from intelmq.server import app + from intelmq.lib import utils # type: ignore + + +class DummyConfig(Config): + + def __init__(self): + pass # Prevent loading from file + + +class DummyRunner(RunIntelMQCtl): + + def __init__(self, base_cmd, paths: Optional[dict] = None): + super().__init__(base_cmd) + self._paths = paths + + def _run_intelmq_ctl(self, args: List[str]) -> subprocess.CompletedProcess: + # simulate dummy response from the CLI command + return subprocess.CompletedProcess(args, 0, b'{"some": "json"}') + + def get_paths(self) -> Dict[str, str]: + if self._paths is None: + return super().get_paths() + else: + return self._paths + + +def get_dummy_reader(**kwargs): + def dummy_runner(): + return DummyRunner([], **kwargs) + return dummy_runner + + +class TestApiWithCLI(TestCase): + + def setUp(self) -> None: + self.client = TestClient(app=app) + dependencies.startup(DummyConfig()) + app.dependency_overrides[runner] = get_dummy_reader() + + def tearDown(self) -> None: + app.dependency_overrides = {} + + def test_version(self): + response = self.client.get("/api/v1/version") + self.assertEqual(response.status_code, 200) + self.assertIsInstance(response.json(), dict) + self.assertEqual(response.json()["intelmq"], __version__) + + def test_ensure_response_get_values_and_is_json(self): + json_paths = ["botnet?action=status", "bot?action=status&id=1", + "getlog?lines=1&id=1", "queues", "queues-and-status", + "bots", "check", "debug"] + + for path in json_paths: + with self.subTest(path): + response = self.client.get(f"/api/v1/{path}") + self.assertEqual(response.status_code, 200) + self.assertIsInstance(response.json(), dict) + self.assertEqual(response.json(), {"some": "json"}) + + def test_run_input(self): + response = self.client.post( + "/api/v1/run?bot=feodo-tracker-browse-parser&cmd=get&dry=false&show=false", + data={"msg": "some message"}) + self.assertEqual(response.status_code, 200) + + +class TestApiWithDir(TestCase): + + def setUp(self) -> None: + self.client = TestClient(app=app) + dependencies.startup(DummyConfig()) + self.conf_dir = TemporaryDirectory() + app.dependency_overrides[runner] = get_dummy_reader( + paths={"CONFIG_DIR": self.conf_dir.name}) + + self.save_runtime() + self.save_positions() + + self.path_patcher = mock.patch( + "intelmq.lib.utils.RUNTIME_CONF_FILE", f"{self.conf_dir.name}/runtime.yaml") + self.path_patcher2 = mock.patch( + "intelmq.RUNTIME_CONF_FILE", f"{self.conf_dir.name}/runtime.yaml") + print(f"{self.conf_dir.name}/runtime.yaml") + self.path_patcher.start() + self.path_patcher2.start() + + def save_runtime(self): + with open(f"{self.conf_dir.name}/runtime.yaml", "w+") as f: + json.dump({}, f) + + def save_positions(self): + os.makedirs(f"{self.conf_dir.name}/manager", exist_ok=True) + with open(f"{self.conf_dir.name}/manager/positions.conf", "w+") as f: + json.dump({}, f) + + def tearDown(self) -> None: + app.dependency_overrides = {} + self.path_patcher.stop() + self.conf_dir.cleanup() + + def test_post_runtime(self): + data = { + "some-bot": { + "bot_id": "bot-1", + "description": "Test", + "enabled": True, + "parameters": { + "destination_queues": { + "_default": [ + "file-output-queue" + ] + }, + "overwrite": True, + }, + "run_mode": "continuous" + } + } + response = self.client.post("/api/v1/runtime", json=data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.text, "success") + + self.assertEqual(utils.get_runtime(), data) + + def test_post_positions(self): + data = { + "some-bot": { + "x": 21, + "y": 314 + } + } + response = self.client.post("/api/v1/positions", json=data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.text, "success") + + with open(f"{self.conf_dir.name}/manager/positions.conf", "r") as f: + saved = json.load(f) + self.assertEqual(saved, data) + + +class TestAPILogin(TestCase): + def setUp(self) -> None: + self.client = TestClient(app=app) + dependencies.startup(DummyConfig()) + self.temp_dir = tempfile.TemporaryDirectory() + self.addCleanup(self.temp_dir.cleanup) + + self.session = SessionStore(os.path.join(self.temp_dir.name, 'sessionsb'), 1000000) + self.session.add_user('test', 'pass') + + app.dependency_overrides[session_store] = lambda: self.session + app.dependency_overrides[runner] = get_dummy_reader() + + def tearDown(self) -> None: + app.dependency_overrides = {} + + def test_login(self): + response = self.client.post("/api/v1/login", data={"username": "test", "password": "pass"}) + self.assertEqual(response.status_code, 200) + self.assertIsNotNone(response.json().get("login_token")) + + def test_login_and_call(self): + response = self.client.post("/api/v1/login", data={"username": "test", "password": "pass"}) + self.assertEqual(response.status_code, 200) + + token = response.json().get("login_token") + authorized_response = self.client.get("/api/v1/version", headers={"authorization": token}) + self.assertEqual(authorized_response.status_code, 200) + self.assertEqual(authorized_response.json()["intelmq"], __version__) + + def test_unauthorized_call(self): + response = self.client.get("/api/v1/version") + self.assertEqual(response.status_code, 401) + + def test_bad_token(self): + response = self.client.get( + "/api/v1/version", headers={"authorization": "not-a-valid-token"}) + self.assertEqual(response.status_code, 401) diff --git a/intelmq/tests/api/test_sessionstore.py b/intelmq/tests/api/test_sessionstore.py new file mode 100644 index 000000000..765cab5c6 --- /dev/null +++ b/intelmq/tests/api/test_sessionstore.py @@ -0,0 +1,66 @@ +"""Tests for IntelMQ-Manager + +SPDX-FileCopyrightText: 2020 Intevation GmbH +SPDX-License-Identifier: AGPL-3.0-or-later +""" +import unittest +import tempfile +import os +from pathlib import Path + +from intelmq.api.session import SessionStore + + +class TestSessionStore(unittest.TestCase): + + def setUp(self): + self.temp_dir = tempfile.TemporaryDirectory() + self.addCleanup(self.temp_dir.cleanup) + + def test_basic_usage(self): + store = SessionStore(os.path.join(self.temp_dir.name, "sessiondb"), + 3600) + session_data = {"csrf_token": "very-secret"} + store.set("some-uniqe-id", session_data) + self.assertEqual(store.get("some-uniqe-id"), session_data) + + def test_unknown_session_is_None(self): + store = SessionStore(os.path.join(self.temp_dir.name, "sessiondb"), + 3600) + self.assertIsNone(store.get("some-unknown-id")) + + def test_set_overwriting(self): + store = SessionStore(os.path.join(self.temp_dir.name, "sessiondb"), + 3600) + store.set("just-a-session-id", {"some": "thing"}) + new_data = {"some": "other-thing"} + store.set("just-a-session-id", new_data) + self.assertEqual(store.get("just-a-session-id"), new_data) + + def test_new_session(self): + store = SessionStore(os.path.join(self.temp_dir.name, "sessiondb"), + 3600) + token = store.new_session({"username": "some-user"}) + self.assertEqual(store.verify_token(token), + {"username": "some-user"}) + + def test_verify_unknown_token(self): + store = SessionStore(os.path.join(self.temp_dir.name, "sessiondb"), + 3600) + token = store.new_session({"username": "some-user"}) + # Check that verify_token returns exactly False. This is what + # hug checks for as well in hug.authentication.authenticator + self.assertIs(store.verify_token("wrong-token"), False) + + def test_user_account(self): + store = SessionStore(os.path.join(self.temp_dir.name, "sessiondb"), + 3600) + store.add_user("someone", "some-password") + self.assertEqual(store.verify_user("someone", "some-password"), + {"username": "someone"}) + + def test_user_account_verify_failed(self): + store = SessionStore(os.path.join(self.temp_dir.name, "sessiondb"), + 3600) + store.add_user("someone", "some-password") + self.assertEqual(store.verify_user("someone", "wrong-password"), None) diff --git a/intelmq/tests/bin/test_psql_initdb.py b/intelmq/tests/bin/test_psql_initdb.py index 284664bcd..f8a12ac60 100644 --- a/intelmq/tests/bin/test_psql_initdb.py +++ b/intelmq/tests/bin/test_psql_initdb.py @@ -139,7 +139,7 @@ def test_separated_raws_trigger(self): self.assertIn("CREATE TRIGGER tr_events", generated) # Static, check if added def test_partition_field(self): - """For paritioned table """ + """For partitioned table """ expected_table = """ CREATE TABLE events ( "id" BIGSERIAL, diff --git a/intelmq/tests/bin/test_intelmqsetup.py b/intelmq/tests/lib/test_setuputils.py similarity index 53% rename from intelmq/tests/bin/test_intelmqsetup.py rename to intelmq/tests/lib/test_setuputils.py index ea29d9be4..cf9c74891 100644 --- a/intelmq/tests/bin/test_intelmqsetup.py +++ b/intelmq/tests/lib/test_setuputils.py @@ -1,4 +1,4 @@ -"""Tests for the intelmqsetup +"""Tests for the utils SPDX-FileCopyrightText: 2023 CERT.at GmbH SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,29 +7,29 @@ import unittest from unittest import mock -from intelmq.bin import intelmqsetup +from intelmq.lib import setuputils class TestOwnership(unittest.TestCase): @mock.patch("shutil.chown") def test_skip_changing_root_path_ownership(self, chown_mock): - with mock.patch.object(intelmqsetup.Path, 'owner') as owner_mock: - with mock.patch.object(intelmqsetup.Path, 'group') as group_mock: + with mock.patch.object(setuputils.Path, 'owner') as owner_mock: + with mock.patch.object(setuputils.Path, 'group') as group_mock: owner_mock.return_value = 'root' group_mock.return_value = 'root' - intelmqsetup.change_owner('/', 'intelmq', 'intelmq') - intelmqsetup.change_owner('//', 'intelmq', 'intelmq') - intelmqsetup.change_owner('///', 'intelmq', 'intelmq') + setuputils.change_owner('/', 'intelmq', 'intelmq') + setuputils.change_owner('//', 'intelmq', 'intelmq') + setuputils.change_owner('///', 'intelmq', 'intelmq') chown_mock.assert_not_called() @mock.patch("shutil.chown") def test_change_file_ownership(self, chown_mock): - with mock.patch.object(intelmqsetup.Path, 'owner') as owner_mock: - with mock.patch.object(intelmqsetup.Path, 'group') as group_mock: + with mock.patch.object(setuputils.Path, 'owner') as owner_mock: + with mock.patch.object(setuputils.Path, 'group') as group_mock: owner_mock.return_value = 'root' group_mock.return_value = 'root' - intelmqsetup.change_owner('/the/path', 'intelmq', 'intelmq') + setuputils.change_owner('/the/path', 'intelmq', 'intelmq') chown_mock.assert_any_call('/the/path', user='intelmq') chown_mock.assert_any_call('/the/path', group='intelmq') diff --git a/intelmq/tests/test_conf.py b/intelmq/tests/test_conf.py index 42050ad7d..9b9993e5a 100644 --- a/intelmq/tests/test_conf.py +++ b/intelmq/tests/test_conf.py @@ -15,6 +15,7 @@ import pprint import re import unittest +import pathlib import cerberus import pkg_resources @@ -83,13 +84,16 @@ def test_harmonization(self): getattr(harmonization, value['type']) def test_runtime_syntax(self): - """ Test if runtime.yaml has correct syntax. """ - with open(CONF_FILES['runtime']) as fhandle: - fcontent = fhandle.read() - interpreted = yaml.load(fcontent) - buf = io.BytesIO() - yaml.dump(interpreted, buf) - self.assertEqual(buf.getvalue().decode(), fcontent) + """ Test if runtime.yaml has correct syntax by loading it. """ + try: + raised_exception = None + runtime_path = pathlib.Path(CONF_FILES['runtime']) + yaml.load(runtime_path) + except Exception as e: + raised_exception = e + + if raised_exception: + self.fail("Exception occurred when loading the runtime.yaml file:\n" + str(raised_exception)) class CerberusTests(unittest.TestCase): diff --git a/intelmq/web/__init__.py b/intelmq/web/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/intelmq/web/router.py b/intelmq/web/router.py new file mode 100644 index 000000000..ac7c23367 --- /dev/null +++ b/intelmq/web/router.py @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: 2023 IntelMQ Team +# SPDX-License-Identifier: AGPL-3.0-or-later + +import collections +import pathlib + +from fastapi import APIRouter, Request +from fastapi.responses import HTMLResponse +from fastapi.templating import Jinja2Templates + +from intelmq.api.config import config + +SHOW_LOGIN = True if config.session_store else False + +router = APIRouter(default_response_class=HTMLResponse) +templates_dir = pathlib.Path(__file__).parent / "templates" +templates = Jinja2Templates(directory=templates_dir) +Page = collections.namedtuple("Page", ["name", "title", "url", "icon_url"]) + + +def get_pages(request: Request): + return [ + Page( + name="configs", + title="Configuration", + url=request.url_for('get_configuration'), + icon_url=request.url_for('static', path='images/configuration.png'), + ), + Page( + name="management", + title="Management", + url=request.url_for('get_management'), + icon_url=request.url_for('static', path='images/management.png'), + ), + Page( + name="monitor", + title="Monitor", + url=request.url_for('get_monitor'), + icon_url=request.url_for('static', path='images/monitor.png'), + ), + Page( + name="check", + title="Check", + url=request.url_for('get_check'), + icon_url=request.url_for('static', path='images/check.png'), + ), + Page( + name="about", + title="About", + url=request.url_for('get_about'), + icon_url=request.url_for('static', path='images/about.png'), + ) + ] + + +@router.get("/", include_in_schema=False) +async def get_index(request: Request): + return templates.TemplateResponse("index.html", { + "request": request, + "pages": get_pages(request) + }) + + +@router.get("/configuration", include_in_schema=False) +async def get_configuration(request: Request): + return templates.TemplateResponse("configuration.html", { + "request": request, + "pages": get_pages(request) + }) + + +@router.get("/management", include_in_schema=False) +def get_management(request: Request): + return templates.TemplateResponse("management.html", { + "request": request, + "pages": get_pages(request) + }) + + +@router.get("/monitor", include_in_schema=False) +def get_monitor(request: Request): + return templates.TemplateResponse("monitor.html", { + "request": request, + "pages": get_pages(request) + }) + + +@router.get("/check", include_in_schema=False) +def get_check(request: Request): + return templates.TemplateResponse("check.html", { + "request": request, + "pages": get_pages(request) + }) + + +@router.get("/about", include_in_schema=False) +def get_about(request: Request): + return templates.TemplateResponse("about.html", { + "request": request, + "pages": get_pages(request) + }) diff --git a/intelmq/web/static/css/management.css b/intelmq/web/static/css/management.css new file mode 100644 index 000000000..165912a90 --- /dev/null +++ b/intelmq/web/static/css/management.css @@ -0,0 +1,17 @@ +/*SPDX-FileCopyrightText: 2020 IntelMQ Team */ +/*SPDX-License-Identifier: AGPL-3.0-or-later*/ + +#botnet-panels > div.panel .panel-div { + margin-bottom: 16px; +} +#botnet-status { + padding: 8px; +} +#graph-container { + margin-top: 16px; + overflow: auto; +} +#bot-table-panel { + overflow: auto; +} +/*# sourceMappingURL=management.css.map */ \ No newline at end of file diff --git a/intelmq/web/static/css/management.css.map b/intelmq/web/static/css/management.css.map new file mode 100644 index 000000000..daf0441da --- /dev/null +++ b/intelmq/web/static/css/management.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../less/management.less"],"names":[],"mappings":"AAAA,cAAe,MAAK,MAChB;EACI,mBAAA;;AAIR;EACI,YAAA;;AAGJ;EACI,gBAAA;EACA,cAAA;;AAGJ;EACI,cAAA","file":"management.css"} \ No newline at end of file diff --git a/intelmq/web/static/css/management.css.map.license b/intelmq/web/static/css/management.css.map.license new file mode 100644 index 000000000..5959308b4 --- /dev/null +++ b/intelmq/web/static/css/management.css.map.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2020 IntelMQ Team +# SPDX-License-Identifier: AGPL-3.0-or-later \ No newline at end of file diff --git a/intelmq/web/static/css/sb-admin-2.css b/intelmq/web/static/css/sb-admin-2.css new file mode 100644 index 000000000..899698e07 --- /dev/null +++ b/intelmq/web/static/css/sb-admin-2.css @@ -0,0 +1,435 @@ +/*SPDX-FileCopyrightText: Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com)*/ +/*SPDX-License-Identifier: Apache-2.0*/ + +body { + background-color: #f8f8f8; + overflow: auto; +} +@media (min-width: 768px) { + body { + overflow: hidden; + } +} +#page-wrapper { + margin: 0px; + padding: 0px; + min-height: 568px; + background-color: #fff; +} +#page-wrapper-with-sidebar { + padding: 0px; + min-height: 568px; + background-color: #fff; +} +@media (min-width: 768px) { + #page-wrapper-with-sidebar { + position: inherit; + margin: 0 0 0 250px; + padding: 0px; + border-left: 1px solid #e7e7e7; + } +} +.navbar-top-links li { + display: inline-block; +} +.navbar-top-links li:last-child { + margin-right: 15px; +} +.navbar-top-links li a { + padding: 15px; + min-height: 50px; +} +.navbar-top-links .dropdown-menu li { + display: block; +} +.navbar-top-links .dropdown-menu li:last-child { + margin-right: 0; +} +.navbar-top-links .dropdown-menu li a { + padding: 3px 20px; + min-height: 0; +} +.navbar-top-links .dropdown-menu li a div { + white-space: normal; +} +.navbar-top-links .dropdown-messages, +.navbar-top-links .dropdown-tasks, +.navbar-top-links .dropdown-alerts { + width: 310px; + min-width: 0; +} +.navbar-top-links .dropdown-messages { + margin-left: 5px; +} +.navbar-top-links .dropdown-tasks { + margin-left: -59px; +} +.navbar-top-links .dropdown-alerts { + margin-left: -123px; +} +.navbar-top-links .dropdown-user { + right: 0; + left: auto; +} +.sidebar .sidebar-nav.navbar-collapse { + padding-right: 0; + padding-left: 0; +} +.sidebar .sidebar-search { + padding: 15px; +} +.sidebar ul li { + border-bottom: 1px solid #e7e7e7; +} +.sidebar ul li a.active { + background-color: #eee; +} +.sidebar .arrow { + float: right; +} +.sidebar .fa.arrow:before { + content: "\f104"; +} +.sidebar .active > a > .fa.arrow:before { + content: "\f107"; +} +.sidebar .nav-second-level li, +.sidebar .nav-third-level li { + border-bottom: 0!important; +} +.sidebar .nav-second-level li a { + padding-left: 37px; +} +.sidebar .nav-third-level li a { + padding-left: 52px; +} +#customListItem { + border-bottom: none; + padding-top: 10px; + padding-bottom: 10px; + text-align: center; +} +@media (min-width: 768px) { + .sidebar { + z-index: 1; + position: absolute; + width: 250px; + margin-top: 0px; + } + .navbar-top-links .dropdown-messages, + .navbar-top-links .dropdown-tasks, + .navbar-top-links .dropdown-alerts { + margin-left: auto; + } +} +.btn-outline { + color: inherit; + background-color: transparent; + transition: all .5s; +} +.btn-primary.btn-outline { + color: #428bca; +} +.btn-success.btn-outline { + color: #5cb85c; +} +.btn-info.btn-outline { + color: #5bc0de; +} +.btn-warning.btn-outline { + color: #f0ad4e; +} +.btn-danger.btn-outline { + color: #d9534f; +} +.btn-primary.btn-outline:hover, +.btn-success.btn-outline:hover, +.btn-info.btn-outline:hover, +.btn-warning.btn-outline:hover, +.btn-danger.btn-outline:hover { + color: #fff; +} +.chat { + margin: 0; + padding: 0; + list-style: none; +} +.chat li { + margin-bottom: 10px; + padding-bottom: 5px; + border-bottom: 1px dotted #999; +} +.chat li.left .chat-body { + margin-left: 60px; +} +.chat li.right .chat-body { + margin-right: 60px; +} +.chat li .chat-body p { + margin: 0; +} +.panel .slidedown .glyphicon, +.chat .glyphicon { + margin-right: 5px; +} +.chat-panel .panel-body { + height: 350px; + overflow-y: scroll; +} +.login-panel { + margin-top: 25%; +} +.flot-chart { + display: block; + height: 400px; +} +.flot-chart-content { + width: 100%; + height: 100%; +} +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc { + background: transparent; +} +table.dataTable thead .sorting_asc:after { + content: "\f0de"; + float: right; + font-family: fontawesome; +} +table.dataTable thead .sorting_desc:after { + content: "\f0dd"; + float: right; + font-family: fontawesome; +} +table.dataTable thead .sorting:after { + content: "\f0dc"; + float: right; + font-family: fontawesome; + color: rgba(50, 50, 50, 0.5); +} +.highlightHovering td:first-child:hover { + font-weight: bold; + cursor: pointer; +} +.btn-circle { + width: 30px; + height: 30px; + padding: 6px 0; + border-radius: 15px; + text-align: center; + font-size: 12px; + line-height: 1.428571429; +} +.btn-circle.btn-lg { + width: 50px; + height: 50px; + padding: 10px 16px; + border-radius: 25px; + font-size: 18px; + line-height: 1.33; +} +.btn-circle.btn-xl { + width: 70px; + height: 70px; + padding: 10px 16px; + border-radius: 35px; + font-size: 24px; + line-height: 1.33; +} +.show-grid [class^=col-] { + padding-top: 10px; + padding-bottom: 10px; + border: 1px solid #ddd; + background-color: #eee!important; +} +.show-grid { + margin: 15px 0; +} +.huge { + font-size: 40px; +} +.panel-green { + border-color: #5cb85c; +} +.panel-green .panel-heading { + border-color: #5cb85c; + color: #fff; + background-color: #5cb85c; +} +.panel-green a { + color: #5cb85c; +} +.panel-green a:hover { + color: #3d8b3d; +} +.panel-red { + border-color: #d9534f; +} +.panel-red .panel-heading { + border-color: #d9534f; + color: #fff; + background-color: #d9534f; +} +.panel-red a { + color: #d9534f; +} +.panel-red a:hover { + color: #b52b27; +} +.panel-yellow { + border-color: #f0ad4e; +} +.panel-yellow .panel-heading { + border-color: #f0ad4e; + color: #fff; + background-color: #f0ad4e; +} +.panel-yellow a { + color: #f0ad4e; +} +.panel-yellow a:hover { + color: #df8a13; +} +.jumbotron { + margin-bottom: 15px; + margin-top: 15px; + margin-left: auto; + margin-right: auto; + padding: 0px; + text-align: center; + height: 100%; + max-width: 90%; +} +.jumbotron .page-header-text { + background-color: #000000; + background-size: contain; + max-width: 100%; +} +.jumbotron .page-header-text span { + color: #ffffff; + text-align: center; + height: 100%; +} +.jumbotron-row { + margin-top: 15px; +} +.center-row { + text-align: center; +} +.center-row-content { + overflow: auto; + display: inline-block; + float: none; + margin: auto; +} +.header-img img { + height: 100px; + padding: 5px; +} +.thumbnail img { + width: 128px; + height: 128px; + padding: 15px; +} +.form-group { + margin-bottom: 45px; +} +#bot-table td:first-child:hover { + font-weight: bold; + cursor: pointer; +} +#network-popUp { + display: none; + position: absolute; + top: 15%; + left: 5%; + margin: auto; + z-index: 299; + background-color: #FFFFFF; + border-style: solid; + border-width: 3px; + border-color: #5394ed; + padding: 10px; + width: 90%; + text-align: center; +} +@media (min-width: 768px) { + #network-popUp { + left: 25%; + width: 50%; + } +} +#network-popUp-fields { + background: #FFFFFF; + width: 100%; +} +#network-popUp-fields input { + width: 100%; +} +#network-popUp-fields td { + text-align: left; +} +form { + display: inline-block; + margin-bottom: 10px; +} +#border { + text-align: center !important; + font-weight: bold; +} +#network-popUp-title { + width: 100%; + font-size: 28px; + display: inherit; +} +#network-row { + display: none; + height: 90%; +} +#network-row.col-xs-10 { + height: 100%; +} +#network-row.col-xs-2 { + height: 100%; +} +#network-tab { + height: 100%; +} +.with-bot { + max-height: 75%; + overflow-y: auto; +} +#logs-panel { + margin-top: 15px; +} +#queues-panel { + margin-top: 15px; +} +#queues-panel .width-80 { + width: 80%; +} +#queues-panel .width-20 { + width: 20%; +} +.waiting { + background-image: url('../images/waiting.gif'); + background-repeat: no-repeat; + background-size: 16px; + background-position: right 10px center; +} +.row { + margin: 0px; +} +.navbar-config { + margin-right: 16px; +} +.index-link:hover { + text-decoration: none; + box-shadow: 1px 1px 16px #000000; +} +.index-link { + display: block; + box-shadow: 1px 1px 16px rgba(0, 0, 0, 0.2); +} +/*# sourceMappingURL=sb-admin-2.css.map */ \ No newline at end of file diff --git a/intelmq/web/static/css/sb-admin-2.css.map b/intelmq/web/static/css/sb-admin-2.css.map new file mode 100644 index 000000000..aa7778b12 --- /dev/null +++ b/intelmq/web/static/css/sb-admin-2.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../less/sb-admin-2.less"],"names":[],"mappings":";;;;;AAMA;EACI,yBAAA;EACA,cAAA;;AAGJ,QAAwB;EACpB;IACI,gBAAA;;;AAIR;EACI,WAAA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;;AAGJ;EACI,YAAA;EACA,iBAAA;EACA,sBAAA;;AAGJ,QAAwB;EACpB;IACI,iBAAA;IACA,mBAAA;IACA,YAAA;IACA,8BAAA;;;AAIR,iBAAkB;EACd,qBAAA;;AAGJ,iBAAkB,GAAE;EAChB,kBAAA;;AAGJ,iBAAkB,GAAG;EACjB,aAAA;EACA,gBAAA;;AAGJ,iBAAkB,eAAe;EAC7B,cAAA;;AAGJ,iBAAkB,eAAe,GAAE;EAC/B,eAAA;;AAGJ,iBAAkB,eAAe,GAAG;EAChC,iBAAA;EACA,aAAA;;AAGJ,iBAAkB,eAAe,GAAG,EAAE;EAClC,mBAAA;;AAGJ,iBAAkB;AAClB,iBAAkB;AAClB,iBAAkB;EACd,YAAA;EACA,YAAA;;AAGJ,iBAAkB;EACd,gBAAA;;AAGJ,iBAAkB;EACd,kBAAA;;AAGJ,iBAAkB;EACd,mBAAA;;AAGJ,iBAAkB;EACd,QAAA;EACA,UAAA;;AAGJ,QAAS,aAAY;EACjB,gBAAA;EACA,eAAA;;AAGJ,QAAS;EACL,aAAA;;AAGJ,QAAS,GAAG;EACR,gCAAA;;AAGJ,QAAS,GAAG,GAAG,EAAC;EACZ,sBAAA;;AAGJ,QAAS;EACL,YAAA;;AAGJ,QAAS,IAAG,MAAM;EACd,SAAS,OAAT;;AAGJ,QAAS,QAAO,IAAE,MAAI,MAAM;EACxB,SAAS,OAAT;;AAGJ,QAAS,kBAAkB;AAC3B,QAAS,iBAAiB;EACtB,0BAAA;;AAGJ,QAAS,kBAAkB,GAAG;EAC1B,kBAAA;;AAGJ,QAAS,iBAAiB,GAAG;EACzB,kBAAA;;AAGJ;EACI,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,kBAAA;;AAGJ,QAAwB;EACpB;IACI,UAAA;IACA,kBAAA;IACA,YAAA;IACA,eAAA;;EAGJ,iBAAkB;EAClB,iBAAkB;EAClB,iBAAkB;IACd,iBAAA;;;AAIR;EACI,cAAA;EACA,6BAAA;EACA,mBAAA;;AAGJ,YAAY;EACR,cAAA;;AAGJ,YAAY;EACR,cAAA;;AAGJ,SAAS;EACL,cAAA;;AAGJ,YAAY;EACR,cAAA;;AAGJ,WAAW;EACP,cAAA;;AAGJ,YAAY,YAAY;AACxB,YAAY,YAAY;AACxB,SAAS,YAAY;AACrB,YAAY,YAAY;AACxB,WAAW,YAAY;EACnB,WAAA;;AAGJ;EACI,SAAA;EACA,UAAA;EACA,gBAAA;;AAGJ,KAAM;EACF,mBAAA;EACA,mBAAA;EACA,8BAAA;;AAGJ,KAAM,GAAE,KAAM;EACV,iBAAA;;AAGJ,KAAM,GAAE,MAAO;EACX,kBAAA;;AAGJ,KAAM,GAAG,WAAW;EAChB,SAAA;;AAGJ,MAAO,WAAW;AAClB,KAAM;EACF,iBAAA;;AAGJ,WAAY;EACR,aAAA;EACA,kBAAA;;AAGJ;EACI,eAAA;;AAGJ;EACI,cAAA;EACA,aAAA;;AAGJ;EACI,WAAA;EACA,YAAA;;AAGJ,KAAK,UAAW,MAAM;AACtB,KAAK,UAAW,MAAM;AACtB,KAAK,UAAW,MAAM;EACpB,uBAAA;;AAGF,KAAK,UAAW,MAAM,aAAY;EAC9B,SAAS,OAAT;EACA,YAAA;EACA,wBAAA;;AAGJ,KAAK,UAAW,MAAM,cAAa;EAC/B,SAAS,OAAT;EACA,YAAA;EACA,wBAAA;;AAGJ,KAAK,UAAW,MAAM,SAAQ;EAC1B,SAAS,OAAT;EACA,YAAA;EACA,wBAAA;EACA,4BAAA;;AAGJ,kBAAmB,GAAE,YAAY;EAC7B,iBAAA;EACA,eAAA;;AAGJ;EACI,WAAA;EACA,YAAA;EACA,cAAA;EACA,mBAAA;EACA,kBAAA;EACA,eAAA;EACA,wBAAA;;AAGJ,WAAW;EACP,WAAA;EACA,YAAA;EACA,kBAAA;EACA,mBAAA;EACA,eAAA;EACA,iBAAA;;AAGJ,WAAW;EACP,WAAA;EACA,YAAA;EACA,kBAAA;EACA,mBAAA;EACA,eAAA;EACA,iBAAA;;AAGJ,UAAW;EACP,iBAAA;EACA,oBAAA;EACA,sBAAA;EACA,gCAAA;;AAGJ;EACI,cAAA;;AAGJ;EACI,eAAA;;AAGJ;EACI,qBAAA;;AAGJ,YAAa;EACT,qBAAA;EACA,WAAA;EACA,yBAAA;;AAGJ,YAAa;EACT,cAAA;;AAGJ,YAAa,EAAC;EACV,cAAA;;AAGJ;EACI,qBAAA;;AAGJ,UAAW;EACP,qBAAA;EACA,WAAA;EACA,yBAAA;;AAGJ,UAAW;EACP,cAAA;;AAGJ,UAAW,EAAC;EACR,cAAA;;AAGJ;EACI,qBAAA;;AAGJ,aAAc;EACV,qBAAA;EACA,WAAA;EACA,yBAAA;;AAGJ,aAAc;EACV,cAAA;;AAGJ,aAAc,EAAC;EACX,cAAA;;AAGJ;EACI,mBAAA;EACA,gBAAA;EACA,iBAAA;EACA,kBAAA;EACA,YAAA;EACA,kBAAA;EACA,YAAA;EACA,cAAA;;AAGJ,UAAW;EACP,yBAAA;EACA,wBAAA;EACA,eAAA;;AAGJ,UAAW,kBAAkB;EACzB,cAAA;EACA,kBAAA;EACA,YAAA;;AAGJ;EACI,gBAAA;;AAGJ;EACI,kBAAA;;AAGJ;EACI,cAAA;EACA,qBAAA;EACA,WAAA;EACA,YAAA;;AAGJ,WAAY;EACR,aAAA;EACA,YAAA;;AAGJ,UAAW;EACP,YAAA;EACA,aAAA;EACA,aAAA;;AAGJ;EACI,mBAAA;;AAGJ,UAAW,GAAE,YAAY;EACrB,iBAAA;EACA,eAAA;;AAGJ;EACI,aAAA;EACA,kBAAA;EACA,QAAA;EACA,QAAA;EACA,YAAA;EACA,YAAA;EACA,yBAAA;EACA,mBAAA;EACA,iBAAA;EACA,qBAAA;EACA,aAAA;EACA,UAAA;EACA,kBAAA;;AAGJ,QAAwB;EACpB;IACI,SAAA;IACA,UAAA;;;AAIR;EACI,mBAAA;EACA,WAAA;;AAGJ,qBAAsB;EAClB,WAAA;;AAGJ,qBAAsB;EAClB,gBAAA;;AAGJ;EACI,qBAAA;EACA,mBAAA;;AAGJ;EACI,6BAAA;EACA,iBAAA;;AAGJ;EACI,WAAA;EACA,eAAA;EACA,gBAAA;;AAGJ;EACI,aAAA;EACA,WAAA;;AAGJ,YAAY;EACR,YAAA;;AAGJ,YAAY;EACR,YAAA;;AAGJ;EACI,YAAA;;AAGJ;EACI,eAAA;EACA,gBAAA;;AAOJ;EACI,gBAAA;;AAGJ;EACI,gBAAA;;AAGJ,aAAc;EACZ,UAAA;;AAGF,aAAc;EACZ,UAAA;;AAGF;EACI,sBAAsB,wBAAtB;EACA,4BAAA;EACA,qBAAA;EACA,sCAAA;;AAGJ;EACI,WAAA;;AAGJ;EACI,kBAAA;;AAGJ,WAAW;EACP,qBAAA;EACA,gCAAA;;AAGJ;EACI,cAAA;EACA,2CAAA","file":"sb-admin-2.css"} \ No newline at end of file diff --git a/intelmq/web/static/css/sb-admin-2.css.map.license b/intelmq/web/static/css/sb-admin-2.css.map.license new file mode 100644 index 000000000..5959308b4 --- /dev/null +++ b/intelmq/web/static/css/sb-admin-2.css.map.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2020 IntelMQ Team +# SPDX-License-Identifier: AGPL-3.0-or-later \ No newline at end of file diff --git a/intelmq/web/static/css/style.css b/intelmq/web/static/css/style.css new file mode 100644 index 000000000..c96140c78 --- /dev/null +++ b/intelmq/web/static/css/style.css @@ -0,0 +1,152 @@ +/*SPDX-FileCopyrightText: 2020 IntelMQ Team */ +/*SPDX-License-Identifier: AGPL-3.0-or-later*/ + +/* + * Navigation + */ +nav ul.nav.navbar-top-links li.active { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +/* + * Common elements + */ +#common-templates { + display: none; +} +#wrapper .navbar #log-window { + background-color: black; + color: white; + display: none; + float: right; + margin: 5px 5px; + padding: 5px; + width: 500px; + height: 44px; + overflow: auto; + resize: vertical; + position: absolute; + top: 0; + right: 0; + cursor: pointer; +} +#wrapper .navbar #log-window.extended { + height: auto; + max-height: 100vh; + width: auto; + cursor: unset; + overflow: scroll; +} +#wrapper .navbar #log-window [role=close] { + float: right; + cursor: pointer; +} +#wrapper .navbar #log-window .command { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; + overflow: auto; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +#wrapper .navbar #log-window .alert a { + text-decoration: underline; + display: inline-block; + padding: 4px; + border: 1px solid #ccc; + border-radius: 10px; +} +.fa { + font-family: FontAwesome; +} +.control-buttons [data-url=status] { + display: none; +} +/* + * Management page + */ +#botnet-panels > .panel[data-botnet-group] { + display: none; +} +#botnet-panels > .panel[data-botnet-group][data-botnet-group=botnet] { + display: block; +} +/* + * Monitor page + */ +#botnet-panels .panel .control-buttons [data-role=control-status] { + display: none; +} +#inspect-panel .control-buttons { + float: right; +} +#inspect-panel button[data-role="clear"] { + float: right; +} +#inspect-panel #command-show { + display: none; +} +#inspect-panel textarea { + resize: vertical; +} +/* + * Config page + */ +#templates { + display: none; +} +#network-container .control-buttons { + float: left; +} +#network-container .control-buttons button { + height: 25px; +} +#network-container .control-buttons button span { + top: -2px; +} +#network-container .monitor-button div a { + color: black; +} +#network-container .duplicate-button { + background-image: url('../plugins/vis.js/img/network/addNodeIcon.png'); +} +#network-container .network-right-menu > div { + display: block; +} +#network-container .network-right-menu .vis-live-toggle, +#network-container .network-right-menu .vis-physics-toggle { + border-radius: 10px; + position: absolute; + right: 560px; + top: 35px; + white-space: nowrap; + padding: 5px 0 5px 5px; + cursor: pointer; +} +#network-container .network-right-menu .vis-live-toggle.running, +#network-container .network-right-menu .vis-physics-toggle.running { + background-color: #00D000; +} +#network-container .network-right-menu .vis-live-toggle .icon { + background-image: url("../images/monitor.png"); + background-repeat: no-repeat; + background-size: 30%; + padding-left: 30px; + padding-right: 15px; + position: relative; + z-index: 20; +} +#network-container .network-right-menu .vis-physics-toggle { + right: 480px; +} +/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/intelmq/web/static/css/style.css.map b/intelmq/web/static/css/style.css.map new file mode 100644 index 000000000..743088e70 --- /dev/null +++ b/intelmq/web/static/css/style.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../less/style.less"],"names":[],"mappings":";;;AAGA,GAAI,GAAE,IAAI,iBAAkB,GAAE;EAC5B,WAAA;EACA,eAAA;EACA,sBAAA;EACA,sBAAA;EACA,gCAAA;;;;;AAMF;EACE,aAAA;;AAGF,QAAS,QAAQ;EACf,uBAAA;EACA,YAAA;EACA,aAAA;EACA,YAAA;EACA,eAAA;EACA,YAAA;EACA,YAAA;EACA,YAAA;EACA,cAAA;EACA,gBAAA;EACA,kBAAA;EACA,MAAA;EACA,QAAA;EACA,eAAA;;AAEA,QAhBO,QAAQ,YAgBd;EACC,YAAA;EACA,iBAAA;EACA,WAAA;EACA,aAAA;EACA,gBAAA;;AArBJ,QAAS,QAAQ,YAwBf;EACE,YAAA;EACA,eAAA;;AA1BJ,QAAS,QAAQ,YA6Bf;EACE,cAAA;EACA,cAAA;EACA,gBAAA;EACA,eAAA;EACA,uBAAA;EACA,WAAA;EACA,qBAAA;EACA,qBAAA;EACA,yBAAA;EACA,sBAAA;EACA,kBAAA;EACA,cAAA;EACA,sCAAsC,wBAAtC;;AA1CJ,QAAS,QAAQ,YA6Cb,OAAO;EACH,0BAAA;EACA,qBAAA;EACA,YAAA;EACA,sBAAA;EACA,mBAAA;;AAIR;EACE,wBAAA;;AAGF,gBAAiB;EACf,aAAA;;;;;AAMF,cAAe,SAAQ;EACrB,aAAA;;AACA,cAFa,SAAQ,mBAEpB;EACC,cAAA;;;;;AAOJ,cAAe,OAAO,iBAAiB;EACrC,aAAA;;AAGF,cACE;EACE,YAAA;;AAFJ,cAKE,OAAM;EACJ,YAAA;;AANJ,cASE;EACE,aAAA;;AAVJ,cAaE;EACE,gBAAA;;;;;AAQJ;EACE,aAAA;;AAMF,kBAEE;EACE,WAAA;;AAHJ,kBAEE,iBAGE;EACE,YAAA;;AANN,kBAEE,iBAGE,OAGE;EACE,SAAA;;AATR,kBAcE,gBAGE,IAAI;EACF,YAAA;;AAlBN,kBAsBE;EACE,sBAAsB,gDAAtB;;AAvBJ,kBA0BE,oBACE;EACE,cAAA;;AA5BN,kBA0BE,oBAKE;AA/BJ,kBA0BE,oBAKoB;EAChB,mBAAA;EACA,kBAAA;EACA,YAAA;EACA,SAAA;EACA,mBAAA;EACA,sBAAA;EACA,eAAA;;AAEA,kBAdJ,oBAKE,iBASG;AAAD,kBAdJ,oBAKoB,oBASf;EAGC,yBAAA;;AA3CR,kBA0BE,oBAqBE,iBAAiB;EACf,sBAAsB,wBAAtB;EACA,4BAAA;EACA,oBAAA;EACA,kBAAA;EACA,mBAAA;EACA,kBAAA;EACA,WAAA;;AAtDN,kBA0BE,oBA+BE;EACE,YAAA","file":"style.css"} \ No newline at end of file diff --git a/intelmq/web/static/css/style.css.map.license b/intelmq/web/static/css/style.css.map.license new file mode 100644 index 000000000..5959308b4 --- /dev/null +++ b/intelmq/web/static/css/style.css.map.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2020 IntelMQ Team +# SPDX-License-Identifier: AGPL-3.0-or-later \ No newline at end of file diff --git a/intelmq/web/static/images/1140x319.gif b/intelmq/web/static/images/1140x319.gif new file mode 100644 index 000000000..8df8f1066 Binary files /dev/null and b/intelmq/web/static/images/1140x319.gif differ diff --git a/intelmq/web/static/images/1140x319.gif.license b/intelmq/web/static/images/1140x319.gif.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/1140x319.gif.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/about.png b/intelmq/web/static/images/about.png new file mode 100644 index 000000000..b56971511 Binary files /dev/null and b/intelmq/web/static/images/about.png differ diff --git a/intelmq/web/static/images/about.png.license b/intelmq/web/static/images/about.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/about.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/abouticon-20.png b/intelmq/web/static/images/abouticon-20.png new file mode 100644 index 000000000..8b77935e2 Binary files /dev/null and b/intelmq/web/static/images/abouticon-20.png differ diff --git a/intelmq/web/static/images/abouticon-20.png.license b/intelmq/web/static/images/abouticon-20.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/abouticon-20.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/abouticon-24.png b/intelmq/web/static/images/abouticon-24.png new file mode 100644 index 000000000..3e9f35343 Binary files /dev/null and b/intelmq/web/static/images/abouticon-24.png differ diff --git a/intelmq/web/static/images/abouticon-24.png.license b/intelmq/web/static/images/abouticon-24.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/abouticon-24.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/check.png b/intelmq/web/static/images/check.png new file mode 100644 index 000000000..a3c5dc1e8 Binary files /dev/null and b/intelmq/web/static/images/check.png differ diff --git a/intelmq/web/static/images/check.png.license b/intelmq/web/static/images/check.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/check.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/check.svg b/intelmq/web/static/images/check.svg new file mode 100644 index 000000000..1b8f52969 --- /dev/null +++ b/intelmq/web/static/images/check.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/intelmq/web/static/images/check.svg.license b/intelmq/web/static/images/check.svg.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/check.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/configuration.png b/intelmq/web/static/images/configuration.png new file mode 100644 index 000000000..035ddf573 Binary files /dev/null and b/intelmq/web/static/images/configuration.png differ diff --git a/intelmq/web/static/images/configuration.png.license b/intelmq/web/static/images/configuration.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/configuration.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/logo.png b/intelmq/web/static/images/logo.png new file mode 100644 index 000000000..fad627cc4 Binary files /dev/null and b/intelmq/web/static/images/logo.png differ diff --git a/intelmq/web/static/images/logo.png.license b/intelmq/web/static/images/logo.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/logo.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/logo2.png b/intelmq/web/static/images/logo2.png new file mode 100644 index 000000000..d5836acd5 Binary files /dev/null and b/intelmq/web/static/images/logo2.png differ diff --git a/intelmq/web/static/images/logo2.png.license b/intelmq/web/static/images/logo2.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/logo2.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/logo_no_margin_6.png b/intelmq/web/static/images/logo_no_margin_6.png new file mode 100644 index 000000000..8ea2066b7 Binary files /dev/null and b/intelmq/web/static/images/logo_no_margin_6.png differ diff --git a/intelmq/web/static/images/logo_no_margin_6.png.license b/intelmq/web/static/images/logo_no_margin_6.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/logo_no_margin_6.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/management.png b/intelmq/web/static/images/management.png new file mode 100644 index 000000000..831f3680f Binary files /dev/null and b/intelmq/web/static/images/management.png differ diff --git a/intelmq/web/static/images/management.png.license b/intelmq/web/static/images/management.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/management.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/monitor.png b/intelmq/web/static/images/monitor.png new file mode 100644 index 000000000..d74a2816e Binary files /dev/null and b/intelmq/web/static/images/monitor.png differ diff --git a/intelmq/web/static/images/monitor.png.license b/intelmq/web/static/images/monitor.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/monitor.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/redrawicon-20.png b/intelmq/web/static/images/redrawicon-20.png new file mode 100644 index 000000000..1b8d03d8a Binary files /dev/null and b/intelmq/web/static/images/redrawicon-20.png differ diff --git a/intelmq/web/static/images/redrawicon-20.png.license b/intelmq/web/static/images/redrawicon-20.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/redrawicon-20.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/saveicon-128.png b/intelmq/web/static/images/saveicon-128.png new file mode 100644 index 000000000..b0dfd4daa Binary files /dev/null and b/intelmq/web/static/images/saveicon-128.png differ diff --git a/intelmq/web/static/images/saveicon-128.png.license b/intelmq/web/static/images/saveicon-128.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/saveicon-128.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/saveicon-20.png b/intelmq/web/static/images/saveicon-20.png new file mode 100644 index 000000000..12e3b27c6 Binary files /dev/null and b/intelmq/web/static/images/saveicon-20.png differ diff --git a/intelmq/web/static/images/saveicon-20.png.license b/intelmq/web/static/images/saveicon-20.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/saveicon-20.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/saveicon-24.png b/intelmq/web/static/images/saveicon-24.png new file mode 100644 index 000000000..a013df0de Binary files /dev/null and b/intelmq/web/static/images/saveicon-24.png differ diff --git a/intelmq/web/static/images/saveicon-24.png.license b/intelmq/web/static/images/saveicon-24.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/saveicon-24.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/trashicon-20.png b/intelmq/web/static/images/trashicon-20.png new file mode 100644 index 000000000..7a6db1026 Binary files /dev/null and b/intelmq/web/static/images/trashicon-20.png differ diff --git a/intelmq/web/static/images/trashicon-20.png.license b/intelmq/web/static/images/trashicon-20.png.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/trashicon-20.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/images/waiting.gif b/intelmq/web/static/images/waiting.gif new file mode 100644 index 000000000..8de8135a2 Binary files /dev/null and b/intelmq/web/static/images/waiting.gif differ diff --git a/intelmq/web/static/images/waiting.gif.license b/intelmq/web/static/images/waiting.gif.license new file mode 100644 index 000000000..84bb6e986 --- /dev/null +++ b/intelmq/web/static/images/waiting.gif.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2020 IntelMQ Team +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/intelmq/web/static/js/about.js b/intelmq/web/static/js/about.js new file mode 100644 index 000000000..104198a4c --- /dev/null +++ b/intelmq/web/static/js/about.js @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team +// +// SPDX-License-Identifier: AGPL-3.0-or-later +'use strict'; + +function get_versions() { + let intelmq_version_element = document.getElementById('intelmq-version'); + // let intelmq_api_version_element = document.getElementById('intelmq-api-version'); + // let intelmq_manager_version_element = document.getElementById('intelmq-manager-version'); + + authenticatedGetJson(managementUrl('version')) + .done(function (data) { + intelmq_version_element.innerHTML = data.intelmq; + // intelmq_api_version_element.innerHTML = data['intelmq-api']; + // intelmq_manager_version_element.innerHTML = '3.2.0'; + }) + .fail(function (jqxhr, textStatus, error) { + let err = `${textStatus}, ${error}`; + console.error(`Request Failed: ${err}`); + alert('error getting version'); + }); +} +function get_debug() { + let section_element = document.getElementById('debugging'); + + authenticatedGetJson(managementUrl('debug')) + .done(function (data) { + for (const section in data) { + let section_heading = document.createElement("h3"); + section_heading.innerHTML = section; + section_element.appendChild(section_heading); + let table = document.createElement("table"); + let tbody = document.createElement("table"); + + for (const [key, value] of Object.entries(data[section])) { + let row = tbody.insertRow(-1); + let cell0 = row.insertCell(0); + cell0.innerHTML = `
${key}
`; + let cell1 = row.insertCell(1); + cell1.innerHTML = `
${value}
`; + } + table.appendChild(tbody); + section_element.appendChild(table); + } + $('#debugging-heading').removeClass('waiting'); + }) + .fail(function (jqxhr, textStatus, error) { + let err = `${textStatus}, ${error}`; + console.error(`Request Failed: ${err}`); + alert('Error getting debugging information. Do you have IntelMQ >= 2.2.0?'); + }); +} + +get_versions(); +get_debug(); diff --git a/intelmq/web/static/js/check.js b/intelmq/web/static/js/check.js new file mode 100644 index 000000000..656058df0 --- /dev/null +++ b/intelmq/web/static/js/check.js @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team +// +// SPDX-License-Identifier: AGPL-3.0-or-later +'use strict'; + +let colors = {info: "alert-info", warning: "alert-warning", error: "alert-danger"}; +let statuses = {success: "No error found.", error: "Some issues have been found, please check the output."} + + +function get_check_output() { + let tableEl = document.getElementById('check-output-table'); + + authenticatedGetJson(managementUrl('check')) + .done(data => { + //data = {"status": "error","lines": [["info", "Reading configuration files."], ["info", "Checking runtime configuration."], ["info", "Checking pipeline configuration."], ["warning", "Bot 'cert-bund-avalanche-parser' has no 'description'."], ["warning", "Bot 'mailsend-output-cz' has no 'name'."], ["error", "Misconfiguration: No source queue for 'mailsend-output-cz'."], ["error", "Misconfiguration: No pipeline configuration found for 'vxvault-collector'."], ["error", "Misconfiguration: No pipeline configuration found for 'vxvault-parser'."], ["info", "Checking harmoization configuration."], ["info", "Checking for bots."]]}; + tableEl.innerHTML = `Status${statuses[data.status]}`; + for (let line of data.lines) { + tableEl.innerHTML += `${line[0]}${line[1]}`; + } + }) + .fail((jqxhr, textStatus, error) => { + let err = `${textStatus}, ${error}`; + console.error(`Request Failed: ${err}`); + alert('error getting check command output'); + }); +} + +get_check_output(); diff --git a/intelmq/web/static/js/config.js b/intelmq/web/static/js/config.js new file mode 100644 index 000000000..e69de29bb diff --git a/intelmq/web/static/js/configs.js b/intelmq/web/static/js/configs.js new file mode 100644 index 000000000..aa744dca5 --- /dev/null +++ b/intelmq/web/static/js/configs.js @@ -0,0 +1,1029 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team , 2020 Edvard Rejthar , 2021 Mikk Margus Möll +// +// SPDX-License-Identifier: AGPL-3.0-or-later +'use strict'; + +var NETWORK_OPTIONS = NETWORK_OPTIONS || {}; + +class VisModel { + constructor() { + + + this.defaults = {}; + this.nodes = {}; + this.bots = {}; + + this.network = null; + this.network_container = null; + this.network_data = {}; // we may update existing info in the network on the fly + this.bot_before_altering = null; + + this.positions = null; + this.options = NETWORK_OPTIONS; + } +} + +var app = new VisModel(); + +var popup = null; +var documentation = null; +var span = null; +var table = null; +var disabledKeys = ['group', 'name', 'module']; +var $manipulation, $saveButton; // jQuery of Vis control panel; elements reset with network +var node = null; + +var $EDIT_DEFAULT_BUTTON = $("#editDefaults"); +var BORDER_TYPE_CLASSES = { + DEFAULT: 'info', + GENERIC: 'success', + RUNTIME: 'warning', +} +var BORDER_TYPES = { + DEFAULT: 'default', + GENERIC: 'generic', + RUNTIME: 'runtime', + OTHERS: 'default', +} + +var draggedElement = null; + +var warn_on_close_tab = false; + +$(window).on('hashchange', location.reload); + +$(window).on('beforeunload', () => warn_on_close_tab ? "If you have not saved your work you'll lose the changes you have made. Do you want to continue?" : undefined); + +function resize() { + // Resize body + let network_container = document.getElementById('network-container'); + network_container.style.height = `${window.innerHeight - network_container.offsetTop}px`; + network_container.style.overflowX = "auto"; + network_container.style.overflowY = "auto"; + + if (app.network !== null && app.network !== undefined) { + app.network.redraw(); + } + + load_html_elements(); +} + +function load_html_elements() { + // Load popup, span and table + app.network_container = document.getElementById('network-container'); + app.network_container.addEventListener('drop', handleDrop); + app.network_container.addEventListener('dragover', allowDrop); + popup = document.getElementById("network-popUp"); + documentation = document.getElementById("documentationButton"); + span = document.getElementById('network-popUp-title'); + table = document.getElementById("network-popUp-fields"); +} + + +function load_bots(config) { + // Build side menu + for (let bot_group of Object.keys(config).reverse()) { + let $bot_group = $("#templates > ul.side-menu > li").clone().prependTo("#side-menu").css("border-bottom-color", GROUP_COLORS[bot_group][0]); + $bot_group.find("> a").prepend(bot_group); + let group = config[bot_group]; + for (let bot_name in group) { + let bot = group[bot_name]; + let $bot = $bot_group.find("ul > li:first").clone().appendTo($("ul", $bot_group)) + .attr("title", bot.description) + .attr("data-name", bot_name) + .attr("data-group", bot_group) + .click(() => { + if ($('#network-popUp').is(':visible')) { + // just creating a new bot + fill_bot(undefined, bot_group, bot_name); + return false; + } + + // cycling amongst the bot instances + if (!$bot.data("cycled")) { + $bot.data("cycled", []); + } + let found = null; + for (let bot_node of Object.values(app.nodes)) { + if (bot_node.module === bot.module && $.inArray(bot_node.id, $bot.data("cycled")) === -1) { + $bot.data("cycled").push(bot_node.id); + found = bot_node.id; + break; + } + } + // not found or all bots cycled + if (!found && $bot.data("cycled").length) { + found = $bot.data("cycled")[0]; + $bot.data("cycled", [found]); // reset cycling + } + if (found) { + fitNode(found); + } else { + show_error(`No instance of the ${bot_name} found. Drag the label to the plan to create one.`); + } + return false; + }) + .on('dragstart', event => { // drag to create a new bot instance + app.network.addNodeMode(); + draggedElement = { + bot_name: bot_name, + bot_group: bot_group + }; + // necessary for firefox + event.originalEvent.dataTransfer.setData('text/plain', null); + }) + .find("a").prepend(bot_name); + + + if (app.bots[bot_group] === undefined) { + app.bots[bot_group] = {}; + } + + app.bots[bot_group][bot_name] = { + name: bot_name, + group: bot_group, + module: bot.module, + description: bot.description, + enabled: true, + parameters: bot.parameters, + run_mode: 'continuous' + }; + + for (let [parameter, value] of Object.entries(bot.parameters)) { + app.bots[bot_group][bot_name].parameters[parameter] = value; + } + } + $bot_group.find("ul li").first().remove(); // get rid of the HTML template + } + + $('#side-menu').metisMenu({restart: true}); + $EDIT_DEFAULT_BUTTON.click(e => { + create_form('Edit Defaults', $(e.target).attr("id"), undefined); + fill_editDefault(app.defaults); + }); + + if (getUrlParameter("configuration") !== "new") { + load_configuration(); + } else { + draw(); + resize(); + set_pending_change(); + } +} + +function fill_editDefault(data) { + table.innerHTML = ''; + insertBorder(BORDER_TYPES.DEFAULT); + for (let [key, value] of Object.entries(data)) { + insertKeyValue(key, value, BORDER_TYPES.DEFAULT, true); + } + + // to enable scroll bar + popup.setAttribute('class', "with-bot"); +} + +function handleDrop(event) { + // --- necessary for firefox + if (event.preventDefault) { + event.preventDefault(); + } + if (event.stopPropagation) { + event.stopPropagation(); + } + // --- + + let domPointer = app.network.interactionHandler.getPointer({x: event.clientX, y: event.clientY}); + let canvasPointer = app.network.manipulation.canvas.DOMtoCanvas(domPointer); + + let clickData = { + pointer: { + canvas: { + x: canvasPointer.x, + y: canvasPointer.y + } + } + }; + + app.network.manipulation.temporaryEventFunctions[0].boundFunction(clickData); + + fill_bot(undefined, draggedElement.bot_group, draggedElement.bot_name); +} + +function allowDrop(event) { + event.preventDefault(); +} + + +// Configuration files manipulation + +function save_data_on_files() { + if (!confirm("By clicking 'OK' you are replacing the configuration in your files by the one represented by the network on this page. Do you agree?")) { + return; + } + + app.nodes = remove_defaults(app.nodes); + + let reloadable = 0; + let alert_error = (file, jqxhr, textStatus, error) => { + show_error(`There was an error saving ${file}:\nStatus: ${textStatus}\nError: ${error}`); + }; + let saveSucceeded = (response) => { + if (++reloadable === 4) { + + } + if (response === 'success') { + return true; + } else { + alert(response); + return false; + } + } + + // can't parallelize these due to a race condition from them both touching runtime.yaml; TODO lock file in backend? + authenticatedAjax({type: "POST", url: `${RUNTIME_FILE}`, contentType: "application/json", data: generate_runtime_conf(app.nodes, app.defaults)}) + .done(saveSucceeded) + .fail(() => alert_error('runtime', ...arguments)) + .then(() => + authenticatedAjax({type: "POST", url: `${POSITIONS_FILE}`, contentType: "application/json", data: generate_positions_conf()}) + .done(saveSucceeded) + .fail(() => alert_error('positions', ...arguments) ) + ) + // all files were correctly saved + .then(unset_pending_change); +} + + +// Prepare data from configuration files to be used in Vis + +function convert_edges(nodes) { + let new_edges = [], roundness = {}; + for (let node of Object.values(nodes)) { + let from = node.bot_id; + let edge_map = node.parameters.destination_queues; + for (let path in edge_map) { + for (let to of edge_map[path]) { + let id = to_edge_id(from, to, path); + let new_edge = { + id, + from, + to: to.replace(/-queue$/, ''), + label: path === '_default' ? undefined : path, + }; + + // if there is multiple edges between nodes we have to distinguish them manually, see https://github.com/almende/vis/issues/1957 + let hash = new_edge.from + new_edge.to; + if (hash in roundness) { + roundness[hash] += 0.3; + } else { + roundness[hash] = 0; + } + if (roundness[hash]) { + new_edge.smooth = {type: "curvedCCW", roundness: roundness[hash]}; + } + + new_edges.push(new_edge); + } + } + } + + return new_edges; +} + +function convert_nodes(nodes, includePositions) { + let new_nodes = []; + + for (let node of nodes) { + let new_node = {}; + new_node.id = node.bot_id; + new_node.label = node.bot_id; + new_node.group = node.group; + + if (includePositions === true) { + try { + let {x, y} = app.positions[node.bot_id]; + new_node.x = x; + new_node.y = y; + } catch (err) { + console.error('positions in file are ignored:', err, node); + show_error('Saved positions are not valid or not complete. The configuration has possibly been modified outside of the IntelMQ-Manager.'); + includePositions = false; + } + } + + new_nodes.push(new_node); + } + + return new_nodes; +} + +function fill_bot(id, group, name) { + let bot; + table.innerHTML = ''; + + if (id === undefined) { + bot = app.bots[group][name]; + + name = bot.name.replace(/\ /g, '-').replace(/[^A-Za-z0-9-]/g, ''); + group = bot.group.replace(/\ /g, '-'); + let default_id = gen_new_id(`${name}-${group}`); + bot.bot_id = bot.id = default_id; + bot.defaults = {}; + + for (let [key, value] of Object.entries(app.defaults).filter(([key, value]) => !(key in bot.parameters))) { + bot.defaults[key] = value; + } + } else { + bot = app.nodes[id]; + } + + app.bot_before_altering = bot; + + insertKeyValue('id', bot.bot_id, 'id', false); + insertBorder(BORDER_TYPES.GENERIC); + for (let [key, value] of Object.entries(bot).filter(([key, value]) => STARTUP_KEYS.includes(key))) { + insertKeyValue(key, value, BORDER_TYPES.GENERIC, false); + } + insertBorder(BORDER_TYPES.RUNTIME); + for (let [key, value] of Object.entries(bot.parameters).filter(([key, value]) => key !== 'destination_queues')) { + insertKeyValue(key, value, BORDER_TYPES.RUNTIME, true); + } + + const modulename = bot.module.replace(/\./g, "-").replace(/_/g, "-"); + documentation.href = `https://intelmq.readthedocs.org/en/maintenance/user/bots.html#${modulename}`; + popup.setAttribute('class', "with-bot"); +} + +function insertBorder(border_type) { + let new_row = table.insertRow(-1); + let sectionCell1 = new_row.insertCell(0); + let sectionCell2 = new_row.insertCell(1); + let addButtonCell = new_row.insertCell(2); + + sectionCell1.setAttribute('id', 'border'); + sectionCell2.setAttribute('id', 'border'); + sectionCell1.innerHTML = border_type; + sectionCell2.innerHTML = border_type; + + switch (border_type) { + case BORDER_TYPES.GENERIC: + new_row.setAttribute('class', BORDER_TYPE_CLASSES.GENERIC); + break; + case BORDER_TYPES.RUNTIME: + new_row.setAttribute('class', BORDER_TYPE_CLASSES.RUNTIME); + $(addButtonCell).append($("#templates > .new-key-btn").clone().click(addNewKey)); + new_row.setAttribute('id', border_type); + break; + case BORDER_TYPES.DEFAULT: + new_row.setAttribute('class', BORDER_TYPE_CLASSES.DEFAULT); + $(addButtonCell).append($("#templates > .new-key-btn").clone().click(addNewDefaultKey)); + new_row.setAttribute('id', border_type); + break; + default: + new_row.setAttribute('class', BORDER_TYPE_CLASSES.OTHERS); + } +} + +function insertKeyValue(key, value, section, allowXButtons, insertAt) { + let new_row = table.insertRow(insertAt === undefined ? -1 : insertAt); + + let keyCell = new_row.insertCell(0); + let valueCell = new_row.insertCell(1); + let xButtonCell = new_row.insertCell(2); + let valueInput = document.createElement("input"); + + keyCell.setAttribute('class', 'node-key'); + keyCell.setAttribute('id', section) + valueCell.setAttribute('class', 'node-value'); + valueInput.setAttribute('type', 'text'); + valueInput.setAttribute('id', key); + + if (section === 'generic' && disabledKeys.includes(key) === true) { + valueInput.setAttribute('disabled', "true"); + } + + let parameter_func = (action_function, argument) => action_function(argument); + + if (allowXButtons === true) { + let xButton = document.createElement('button'); + let xButtonSpan = document.createElement('span'); + xButtonSpan.setAttribute('class', 'glyphicon glyphicon-remove-circle'); + xButton.setAttribute('class', 'btn btn-danger'); + xButton.setAttribute('title', 'delete parameter'); + xButton.addEventListener('click', () => parameter_func(deleteParameter, key)); + + xButton.appendChild(xButtonSpan); + xButtonCell.appendChild(xButton); + } + + valueCell.appendChild(valueInput); + + keyCell.innerHTML = key; + if (value !== null && typeof value === "object") { + value = JSON.stringify(value); + } + if (value !== null) { + valueInput.setAttribute('value', value); + } +} + +function resetToDefault(input_id) { + $(`#${input_id}`)[0].value = app.defaults[input_id]; +} + +function deleteParameter(input_id) { + let current_index = $(`#${input_id}`).closest('tr').index(); + table.deleteRow(current_index); +} + +function addNewKey() { + let $el = $("#templates .modal-add-new-key").clone(); + popupModal("Add key", $el, () => { + let current_index = $(`#${BORDER_TYPES.RUNTIME}`).index(); + let $key = $el.find("[name=newKeyInput]"); + let val = $el.find("[name=newValueInput]").val(); + + if (!PARAM_KEY_REGEX.test($key.val())) { + show_error("Parameter names can only be composed of numbers, letters, hiphens and underscores"); + $key.focus(); + return false; + } else { + // inserts new value and focus the field + insertKeyValue($key.val(), val, BORDER_TYPES.RUNTIME, true, current_index + 1); + // a bootstrap guru or somebody might want to rewrite this line without setTimeout + setTimeout(() => $('#network-popUp .new-key-btn').closest("tr").next("tr").find("input").focus(), 300); + } + }); +} +// same as above, with another border type +function addNewDefaultKey() { + let $el = $("#templates .modal-add-new-key").clone(); + popupModal("Add key", $el, () => { + let current_index = $(`#${BORDER_TYPES.RUNTIME}`).index(); + let $key = $el.find("[name=newKeyInput]"); + let val = $el.find("[name=newValueInput]").val(); + + if (!PARAM_KEY_REGEX.test($key.val())) { + show_error("Parameter names can only be composed of numbers, letters, hiphens and underscores"); + $key.focus(); + return false; + } else { + // inserts new value and focus the field + insertKeyValue($key.val(), val, BORDER_TYPES.DEFAULT, true, current_index + 1); + // a bootstrap guru or somebody might want to rewrite this line without setTimeout + setTimeout(() => $('#network-popUp .new-key-btn').closest("tr").next("tr").find("input").focus(), 300); + } + }); +} + +$(document).keydown(function (event) { + if (event.keyCode === 27) { + let $el; + if (($el = $("body > .modal:not([data-hiding])")).length) { + // close the most recent modal + $el.last().attr("data-hiding", true).modal('hide'); + setTimeout(() => $("body > .modal[data-hiding]").first().remove(), 300); + } else if ($('#network-popUp').is(':visible')) { + $('#network-popUp-cancel').click(); + } + } + if (event.keyCode === 13 && $('#network-popUp').is(':visible') && $('#network-popUp :focus').length) { + // till network popup is not unified with the popupModal function that can handle Enter by default, + // let's make it possible to hit "Ok" by Enter as in any standard form + $('#network-popUp-ok').click(); + } +}); + +function saveDefaults_tmp(data, callback) { + app.defaults = {}; + saveFormData(); + set_pending_change(); + clearPopUp(data, callback); +} + +function saveFormData() { + for (let i = 0; i < table.rows.length; i++) { + let keyCell = table.rows[i].cells[0]; + let valueCell = table.rows[i].cells[1]; + let valueInput = valueCell.getElementsByTagName('input')[0]; + + if (valueInput === undefined) + continue; + + let key = keyCell.innerText; + let value = null; + + try { + value = JSON.parse(valueInput.value); + } catch (err) { + value = valueInput.value; + } + + switch (keyCell.id) { + case 'id': + node.bot_id = value; + break; + case 'generic': + node[key] = value; + break; + case 'runtime': + node.parameters[key] = value; + break; + case 'border': + break; + case 'default': + app.defaults[key] = value; + break; + default: + node.defaults[key] = value; + } + } +} + +function saveData(data, callback) { + node = {parameters: {}, defaults: {}}; + + saveFormData(); + + // check inputs being valid + if (node.bot_id === '' && node.group === '') { + show_error('fields id and group must not be empty!'); + return; + } + + if (!BOT_ID_REGEX.test(node.bot_id)) { + show_error("Bot ID's can only be composed of numbers, letters and hyphens"); + return; + } + + let current_id = node.bot_id, old_id = app.bot_before_altering.bot_id; + + let old_bot = app.nodes[old_id]; + node.parameters.destination_queues = old_bot ? old_bot.parameters.destination_queues : {}; + + if (current_id !== old_id) { + if (current_id in app.nodes) { + alert("A bot with this ID already exists, please select a different ID"); + return; + } + + if (old_id in app.nodes) { + if (!confirm("You have edited the bot's ID. Proceed with the operation?")) { + return; + } + + app.positions[current_id] = app.positions[old_id]; + app.nodes[current_id] = node; + delete app.positions[old_id]; + + app.network_data.nodes.add(convert_nodes([node], true)); + + // recreate reverse edges + for (let edge_id of get_reverse_edges(old_id)) { + let [from, to, path] = from_edge_id(edge_id); + let list = app.nodes[from].parameters.destination_queues[path]; + let to_index = list.indexOf(`${old_id}-queue`); + + list[to_index] = `${current_id}-queue`; + + let new_edge_id = to_edge_id(from, current_id, path); + if (path === '_default') { + path = undefined; + } + + app.network_data.edges.remove({id: edge_id}); + app.network_data.edges.add({id: new_edge_id, from, to: current_id, label: path}); + } + + // recreate forward edges + for (let [path, path_l] of Object.entries(node.parameters.destination_queues)) { + for (let to of path_l) { + app.network_data.edges.add({ + id: to_edge_id(current_id, to, path), + from: current_id, + to: to.replace(/-queue$/, ''), + label: path === '_default' ? undefined : path + }); + } + } + + delete app.nodes[old_id]; + app.network_data.nodes.remove(old_id); + } + } + + + // switch parameters and defaults + if ('parameters' in node) { + for (let parameterKey in node.parameters) { + if ( + node.parameters[parameterKey] !== app.bot_before_altering.parameters[parameterKey] + && parameterKey in app.defaults + && node.parameters[parameterKey] === app.defaults[parameterKey] + ) { + swapToDefaults(node, parameterKey); + } + } + } + + if ('defaults' in node) { + for (let defaultsKey in node.defaults) { + if (node.defaults[defaultsKey] !== app.defaults[defaultsKey]) { + swapToParameters(node, defaultsKey); + } + } + } + + data.bot_id = node.bot_id; + data.id = node.bot_id; + data.label = node.bot_id; + data.group = node.group; + data.level = GROUP_LEVELS[data.group]; + data.title = JSON.stringify(node, undefined, 2).replace(/\n/g, '\n
').replace(/ /g, " "); + + app.nodes[node.bot_id] = node; + + set_pending_change(); + clearPopUp(data, callback); +} + +function swapToParameters(node, key) { + node.parameters[key] = node.defaults[key]; + delete node.defaults[key]; +} + +function swapToDefaults(node, key) { + node.defaults[key] = node.parameters[key]; + delete node.parameters[key]; +} + + +/** + * Popups a custom modal window containing the given body. + * @example popupModal("Title", $input, () => {$input.val();}) + */ +function popupModal(title, body, callback) { + let $el = $("#templates > .modal").clone().appendTo("body"); + $(".modal-title", $el).text(title); + $(".modal-body", $el).html(body); + $el.modal({keyboard: false}).on('shown.bs.modal', e => { + let $ee; + if (($ee = $('input,textarea,button', $(".modal-body", e.target)).first())) { + $ee.focus(); + } + }); + return $el.on('submit', 'form', e => { + if (callback() !== false) { + $(e.target).closest(".modal").modal('hide'); + } + return false; + }); +} + +function create_form(title, data, callback) { + span.innerHTML = title; + + let okButton = document.getElementById('network-popUp-ok'); + let cancelButton = document.getElementById('network-popUp-cancel'); + + if (data === $EDIT_DEFAULT_BUTTON.attr("id")) { + okButton.onclick = saveDefaults_tmp.bind(window, data, callback); + } else { + okButton.onclick = saveData.bind(window, data, callback); + } + + cancelButton.onclick = clearPopUp.bind(window, data, callback); + + table.innerHTML = "

Please select one of the bots on the left

"; + popup.style.display = 'block'; + popup.setAttribute('class', "without-bot"); +} + +function clearPopUp(data, callback) { + let okButton = document.getElementById('network-popUp-ok'); + let cancelButton = document.getElementById('network-popUp-cancel'); + okButton.onclick = null; + cancelButton.onclick = null; + + popup.style.display = 'none'; + span.innerHTML = ""; + + for (let i = table.rows.length - 1; i >= 0; i--) { + let position = table.rows[i].rowIndex; + + if (position >= CORE_FIELDS) { + table.deleteRow(position); + } else { + table.rows[i].setAttribute('value', ''); + } + } + + popup.setAttribute('class', "without-bot"); + if ((callback !== undefined) && (data.label !== 'new')) { + callback(data); + } +} + +function redrawNetwork() { + app.options.layout.randomSeed = Math.round(Math.random() * 1000000); + app.network.destroy(); + app.network = null; + initNetwork(false); + set_pending_change(); +} + +function draw() { + load_html_elements(); + + if (getUrlParameter("configuration") === "new") { + app.nodes = {}; + } + initNetwork(); + if (window.location.hash) { + let node = window.location.hash.substr(1); + setTimeout(() => { // doesn't work immediately, I don't know why. Maybe a js guru would bind to visjs onready if that exists or sth. + try { + fitNode(node); + } catch (e) { + show_error(`Bot instance ${node} not found in the current configuration.`); + } + }, 100); + + + } +} + +function fitNode(nodeId) { + app.network.fit({nodes: [nodeId]}); + app.network.selectNodes([nodeId], true); + app.network.manipulation.showManipulatorToolbar(); +} + +function initNetwork(includePositions = true) { + app.network_data = { + nodes: new vis.DataSet(convert_nodes(Object.values(app.nodes), includePositions)), + edges: new vis.DataSet(convert_edges(app.nodes)) + }; + + app.network = new vis.Network(app.network_container, app.network_data, app.options); + $manipulation = $(".vis-manipulation"); + + // rename some menu buttons (because we couldn't do that earlier) + app.network.options.locales.en.addNode = "Add Bot"; + app.network.options.locales.en.addEdge = "Add Queue"; + app.network.options.locales.en.editNode = "Edit Bot"; + app.network.options.locales.en.del = "Delete"; + + // 'Live' button (by default on when botnet is not too big) and 'Physics' button + // initially stopped + let reload_queues = (new Interval(load_live_info, RELOAD_QUEUES_EVERY * 1000, true)).stop(); + app.network.setOptions({physics: false}); + + // + // add custom button to the side menu + // + + $("#templates .network-right-menu").clone().insertAfter($manipulation); + let $nc = $("#network-container"); + $(".vis-live-toggle", $nc).click(e => { + $(e.target).toggleClass("running", !reload_queues.running); + reload_queues.toggle(!reload_queues.running); + }).click(); + let physics_running = true; + $(".vis-physics-toggle", $nc).click(e => { + $(e.target).toggleClass("running"); + app.network.setOptions({physics: (physics_running = !physics_running)}); + }); + + // 'Save Configuration' button blinks and lists all the bots that should be reloaded after successful save. + $saveButton = $("#vis-save", $nc); + $saveButton.children().on('click', save_data_on_files); + $saveButton.data("reloadables", []); + $saveButton.blinkOnce = function() { + $($saveButton).addClass('blinking-once'); + setTimeout(() => $($saveButton).removeClass('blinking-once'), 2000); + } + $saveButton.blinking = function (bot_id = null) { + $($saveButton).addClass('vis-save-blinking') + if (bot_id) { + $($saveButton).data("reloadables").push(bot_id); + } + }; + $saveButton.unblinking = function () { + $($saveButton).removeClass('vis-save-blinking'); + let promises = []; + let bots = $.unique($($saveButton).data("reloadables")); + for (let bot_id of bots) { + let url = managementUrl("bot", `action=reload&id=${bot_id}`); + promises.push(authenticatedGetJson(url)); + } + if (promises.length) { + Promise.all(promises).then(() => { + show_error(`Reloaded bots: ${bots.join(", ")}`); + bots.length = 0; + }); + } + }; + + let allow_blinking_once = false; // Save Configuration button will not blink when a button is clicked now automatically + // list of button callbacks in form ["button/settings name"] => function called when clicked receives true/false according to the clicked state + let callbacks = [ + ["live", val => reload_queues[val ? "start" : "stop"]()], + ["physics", val => app.network.setOptions({physics: val})], + ]; + for (let [name, fn] of callbacks) { + let $el = $(`.vis-${name}-toggle`, $nc).click(e => { + // button click will callback and blinks Save Configuration button few times + fn(settings[name] = !settings[name]); + $(e.target).toggleClass("running", settings[name]); + + if (allow_blinking_once) { + $saveButton.blinkOnce(); + } + }); + // initially turn on/off buttons according to the server-stored settings + settings[name] = !settings[name]; + $el.click(); + } + allow_blinking_once = true; + + // 'Clear Configuration' button + $("#vis-clear").children().on('click', event => window.location.assign('configs.html?configuration=new')); + + // 'Redraw Botnet' button + $("#vis-redraw").children().on('click', event => redrawNetwork()); + + // + // add custom menu buttons + // (done by extending self the visjs function, responsible for menu creation + // so that we are sure our buttons are persistent when vis menu changes) + // + app.network.manipulation._showManipulatorToolbar = app.network.manipulation.showManipulatorToolbar; + app.network.manipulation.showManipulatorToolbar = function () { + // call the parent function that builds the default menu + app.network.manipulation._showManipulatorToolbar.call(app.network.manipulation); + + // enable 'Edit defaults' button + $EDIT_DEFAULT_BUTTON.prop('disabled', false); + + // clicking on 'Add Bot', 'Add Queues' etc buttons disables 'Edit defaults' button + let fn = () => $EDIT_DEFAULT_BUTTON.prop('disabled', true); + $(".vis-add", $manipulation).on("pointerdown", fn); + let $el = $(".vis-edit", $manipulation); + if ($el.length) { // 'Edit Bot' button is visible only when there is a bot selected + $el.on("pointerdown", fn); + } + + // 'Monitor' and 'Duplicate' buttons appear when there is a single node selected + let nodes = app.network.getSelectedNodes(); + if (nodes.length === 1) { // a bot is focused + let bot = nodes[0]; + $("#templates .network-node-menu").clone().appendTo($manipulation); + $(".monitor-button", $manipulation).click((event) => { + return click_link(MONITOR_BOT_URL.format(bot), event); + }).find("a").attr("href", MONITOR_BOT_URL.format(bot)); + $(".duplicate-button", $manipulation).click(() => { + duplicateNode(app, bot); + }).insertBefore($(".vis-add").hide()); + + // insert start/stop buttons + $(".monitor-button", $manipulation).before(generate_control_buttons(bot, false, refresh_color, true)); + } else { + let edges = app.network.getSelectedEdges(); + if (edges.length === 1) { + $("#templates .network-edge-menu").clone().appendTo($manipulation); + $(".vis-edit", $manipulation).click(() => { + editPath(app, edges[0]); + }).insertBefore($(".vis-delete")); + } + } + // refresh shortcuts + // (it is so hard to click on the 'Add Node' button we rather register click event) + // We use 't' for 'Add bot' and 'Duplicate' because that's a common letter. + + let shortcuts = [ + ['t', 'add', 'addNodeMode'], + ['q', 'connect', 'addEdgeMode'], + ['d', 'delete', 'deleteSelected'], + ['e', 'edit', 'editNode'], + ]; + + for (let [letter, tag, callback_name] in shortcuts) { + $(`.vis-${tag} .vis-label`, $manipulation).attr('data-accesskey', letter).click(app.network[callback_name]); + } + + accesskeyfie(); + }; + // redraw immediately so that even the first click on the network is aware of that new monkeypatched function + app.network.manipulation.showManipulatorToolbar(); + + // double click action trigger editation + app.network.on("doubleClick", active => { + if (active.nodes.length === 1) { + let ev = document.createEvent('MouseEvent'); // vis-js button need to be clicked this hard way + ev.initEvent("pointerdown", true, true); + $(".vis-edit", $manipulation).get()[0].dispatchEvent(ev); + } + if (active.edges.length === 1) { + $(".vis-edit", $manipulation).click(); + } + }); + /* right button ready for any feature request: + app.network.on("oncontext", (active)=>{ + let nodeId = app.network.getNodeAt(active.pointer.DOM); + // what this should do? :) + }); + */ + +} + +// INTELMQ + +/* + * Application entry point + */ + +// Dynamically load available bots +load_file(BOTS_FILE, load_bots); + +// Dynamically adapt to fit screen +window.onresize = resize; + +/** + * This function fetches the current info and updates bot nodes on the graph + */ +function refresh_color(bot) { + if (bot_status_previous[bot] !== bot_status[bot]) { // status changed since last time + + // we use light colour if we expect bot will be running + // (when reloading from stopped state bot will not be running) + let col = GROUP_COLORS[app.nodes[bot].group][[ + BOT_STATUS_DEFINITION.running, + BOT_STATUS_DEFINITION.starting, + BOT_STATUS_DEFINITION.restarting, + bot_status_previous[bot] === BOT_STATUS_DEFINITION.running ? BOT_STATUS_DEFINITION.reloading : 0 + ].includes(bot_status[bot]) ? 0 : 1]; + + // change bot color if needed + if (app.network_data.nodes.get([bot])[0].color !== col) { + app.network_data.nodes.update({id: bot, color: col}); + } + + // we dash the border if the status has to be changed (not running or stopping) or is faulty (error, incomplete) + if ([BOT_STATUS_DEFINITION.running, BOT_STATUS_DEFINITION.stopped].indexOf(bot_status[bot]) === -1) { + app.network_data.nodes.update({id: bot, shapeProperties: {borderDashes: [5, 5]}}) + } else if ([BOT_STATUS_DEFINITION.running, BOT_STATUS_DEFINITION.stopped, undefined].indexOf(bot_status_previous[bot]) === -1) { + // we remove dash border since bot has been in a dash-border state and is no more + // (that means that bot wasn't either in a running, stopped or initially undefined state) + app.network_data.nodes.update({id: bot, shapeProperties: {borderDashes: false}}); + } + + bot_status_previous[bot] = bot_status[bot]; + } +} + +function load_live_info() { + $(".navbar").addClass('waiting'); + return authenticatedGetJson(managementUrl('queues-and-status')) + .done(data => { + let bot_queues; + [bot_queues, bot_status] = data; + + for (let [bot, bot_data] of Object.entries(bot_queues)) { + if ("source_queue" in bot_data) { + // we skip bots without source queue (collectors) + // Assume an empty internal queue if no data is given (The AMQP pipeline does not have/need internal queues) + let c = bot_data.source_queue[1] + (bot_data.internal_queue || 0); + let label = (c > 0) ? `${bot}\n${c}✉` : bot; + let appbot = app.network_data.nodes.get(bot); + if (appbot === null) { + show_error(`Non-existent bot ${bot} in pipelines.`); + } else if (label !== appbot.label) { + // update queue count on bot label + app.network_data.nodes.update({id: bot, label}); + } + } else { + // https://github.com/certtools/intelmq-manager/issues/158 + app.network_data.nodes.update({id: bot, label: bot}); + } + } + for (let bot in bot_status) { + // bots that are not running are grim coloured + refresh_color(bot); + } + }) + .fail(ajax_fail_callback('Error loading bot queues information')) + .always(() => { + $(".navbar").removeClass('waiting'); + this.blocking = false; + }); +} + +function set_pending_change(bot_id = null) { + $saveButton.blinking(bot_id); + warn_on_close_tab = true; +} + +function unset_pending_change() { + $saveButton.unblinking(); + warn_on_close_tab = false; +} diff --git a/intelmq/web/static/js/defaults.js b/intelmq/web/static/js/defaults.js new file mode 100644 index 000000000..7b6915833 --- /dev/null +++ b/intelmq/web/static/js/defaults.js @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team , 2020 Edvard Rejthar , 2021 Mikk Margus Möll +// +// SPDX-License-Identifier: AGPL-3.0-or-later +'use strict'; + +function generate_defaults_conf(defaults) { + return JSON.stringify(sortObjectByPropertyName(defaults), undefined, 4); +} + +function read_defaults_conf(config) { + let global = {}; + + for (let key in config.global) { + try { + global[key] = JSON.parse(config.global[key]); + } catch (err) { + global[key] = config.global[key]; + } + } + + return global; +} + +function remove_defaults(nodes) { + for (let id in nodes) { + delete nodes[id].defaults; + } + + return nodes; +} + +function get_reverse_nodes(dest_bot_id) { + let out = []; + let dest_bot = app.nodes[dest_bot_id]; + if (dest_bot === undefined) { + // for example for newly configured bots + return out; + } + + let connected_nodes = app.network.getConnectedNodes(dest_bot_id); + let queue_id = `${dest_bot_id}-queue`; + let reverse_allowed_neighbors = REVERSE_ACCEPTED_NEIGHBORS[dest_bot.group]; + + for (let src_bot of connected_nodes.map(src_bot_id => app.nodes[src_bot_id]).filter(src_bot => reverse_allowed_neighbors.includes(src_bot.group))) { + for (let list of Object.values(src_bot.parameters.destination_queues)) { + if (list.includes(queue_id)) { + out.push(src_bot.bot_id); + break; + } + } + } + + return out; +} + +function get_reverse_edges(dest_bot_id) { + let out = [], queue_id = `${dest_bot_id}-queue`; + for (let edge_id of app.network.getConnectedEdges(dest_bot_id)) { + let [from, to, path] = from_edge_id(edge_id); + if (to === queue_id) { + out.push(edge_id); + } + } + + return out; +} + +function to_edge_id(from, to, path) { // e.g HTTP-Collector|JSON-Parser-queue|_default + return [from, to.replace(/-queue$/, ''), path].map(escape).join('|'); +} + +function from_edge_id(edge_id) { + let [from, to, path] = edge_id.split('|').map(unescape); + return [from, `${to}-queue`, path]; +} + +function gen_new_id(prefix) { + if (!(prefix in app.nodes)) { // no need to add numeric suffix + return prefix; + } + + let i = 1, new_id; + //reserve a new unique name + do { + new_id = `${prefix}-${++i}`; + } while (new_id in app.nodes); + + return new_id; + +} diff --git a/intelmq/web/static/js/management.js b/intelmq/web/static/js/management.js new file mode 100644 index 000000000..f8315ac0c --- /dev/null +++ b/intelmq/web/static/js/management.js @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team +// +// SPDX-License-Identifier: AGPL-3.0-or-later +'use strict'; + +var BOT_STATUS_DEFINITION = BOT_STATUS_DEFINITION || {}; +var BOT_CLASS_DEFINITION = BOT_CLASS_DEFINITION || {}; +var bot_status = bot_status || {}; +var botnet_status = botnet_status || {}; +var reload_interval; + +$('#bot-table').dataTable({ + lengthMenu: [[5, 10, 25, -1], [5, 10, 25, "All"]], + pageLength: -1, + columns: ['bot_id', 'bot_status', 'actions'].map(data => {return {data}}), + createdRow: (row, data) => $("td:eq(2)", row).append(generate_control_buttons(data.bot_id, false, refresh_status)), + +}); + +window.onresize = function () { + $('#bot-table').dataTable().fnAdjustColumnSizing(); + $('#bot-table').dataTable().fnDraw(); +}; + +var $bt = $('#bot-table'); +$(function () { + load_file(RUNTIME_FILE, config => read_runtime_conf(config)); + + $bt.dataTable().fnClearTable(); + + // generate control buttons for every panel + $("#botnet-panels [data-botnet-group]").each(function () { + $(this).find("h4").data().waiting_count = 0; + $(".panel-body .panel-div", $(this)).after(generate_control_buttons(false, $(this).attr("data-botnet-group"), refresh_status, true)); + }); + + // fetch info from server + reload_interval = new Interval(() => { + $('#botnet-panels [data-botnet-group=botnet] [data-url=status]').click(); + }, RELOAD_STATE_EVERY * 1000, true).call_now(); + + // + $bt.on("click", 'tr td:first-child', event => click_link(MONITOR_BOT_URL.format(event.target.innerText), event)); +}); + + +function refresh_status(bot, finished) { + if (reload_interval) { + reload_interval.stop(); + } + + // Refresh bot table + let redraw_table = false; + let pending = false; // any bot is in an unknown state + for (let bot_id in bot_status) { + let class_ = BOT_CLASS_DEFINITION[bot_status[bot_id]]; + let status = bot_status[bot_id]; + let $bot = $(`tr[data-bot-id=${bot_id}]`, $bt); + if ($bot.length) { + // row exist, just update the status + if (!$bot.text() !== status) {// class of this bot changes (note that multiple statuses may share the same class ".warning") + for (let state of Object.values(BOT_CLASS_DEFINITION)) { // remove any other status-class + $bot.removeClass(state); + } + $bot.addClass(class_); + $("td:eq(1)", $bot).text(status); + } + } else { + $bt.dataTable().api().row.add({ + bot_id, + bot_status: status, + actions: "", + DT_RowClass: class_, + DT_RowAttr: {"data-bot-id": bot_id} + }); + redraw_table = true; + } + if (status === BOT_STATUS_DEFINITION.unknown) { + pending = true; + } + + } + if (finished) { + // If there is some unknown bots, we re-ask the server to get current information immediately, else re-start the fetching interval. + // (in case of botnets of 100 bots, intelmqctl returns 'unknown' state when bot couldn't start/stop in time) + if (pending) { + reload_interval.call_now(); + } else { + reload_interval.start(); + } + } + + // If there is a new row in the table, we ll redraw + if (redraw_table) { + $bt.dataTable().fnAdjustColumnSizing(); + $bt.dataTable().fnDraw(); + $('#botnet-panels [data-botnet-group]').show(); // showed on the first run + } + + + // Analyze botnet panels + let atLeastOneStopped = {}; + let atLeastOneRunning = {}; + for (let bot_id in bot_status) { // analyze all bots status + if (bot_status[bot_id] === BOT_STATUS_DEFINITION.stopped || bot_status[bot_id] === BOT_STATUS_DEFINITION.unknown) { + atLeastOneStopped.botnet = atLeastOneStopped[bot_definition[bot_id].groupname] = true; + } else if (bot_status[bot_id] === BOT_STATUS_DEFINITION.running) { + atLeastOneRunning.botnet = atLeastOneRunning[bot_definition[bot_id].groupname] = true; + } + } + let get_group_status = function (stopped, running) { + if (stopped && running || !stopped && !running) { + return BOT_STATUS_DEFINITION.incomplete; + } else if (stopped && !running) { + return BOT_STATUS_DEFINITION.stopped; + } else if (!stopped && running) { + return BOT_STATUS_DEFINITION.running; + } + }; + + // Highlight waiting icon of current panel if any operation is pending (we may click "start" and "stop", waiting both operations resolve) + let $el; + if (bot in botnet_status) { // bot button was clicked: highlight its panel (ex: Parsers) + $el = $(this).closest(".panel").find("h4"); + } else { // panel button was clicked + $el = $(`.panel[data-botnet-group=${bot_definition[bot].groupname}]`).find("h4"); + } + $el.toggleClass("waiting", ($el.data().waiting_count += (finished === 0) ? 1 : -1) > 0); + + // Refresh botnet panels + let waiting_total = 0; + $("#botnet-panels > [data-botnet-group]").each(function () { + waiting_total += $(this).find("h4").data().waiting_count; + let botnet = $(this).attr("data-botnet-group"); + botnet_status[botnet] = get_group_status(atLeastOneStopped[botnet], atLeastOneRunning[botnet]); + $('[data-role=control-status]', this).trigger("update"); + + // due to esthetics, fetch the status-info to the line above + if (($el = $(".control-buttons [data-role=control-status]", $(this)).clone())) { + if ($el.text()) { + $(".panel-div", $(this)).html("Status: " + ($el[0].outerHTML || 'Unknown')); + } + } + }); + + // Highlight "Whole Botnet Status" operation in any panel is pending + $('#botnet-panels [data-botnet-group=botnet] h4').toggleClass('waiting', waiting_total > 0); +} diff --git a/intelmq/web/static/js/monitor.js b/intelmq/web/static/js/monitor.js new file mode 100644 index 000000000..55f748e7a --- /dev/null +++ b/intelmq/web/static/js/monitor.js @@ -0,0 +1,601 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team , 2020 Edvard Rejthar , 2021 Mikk Margus Möll +// +// SPDX-License-Identifier: AGPL-3.0-or-later +'use strict'; + +var ALL_BOTS = 'All Bots'; +var bot_logs = {}; +var bot_queues = {}; +var path_names = {}; +var reload_queues = null; +var reload_logs = null; +var app = app || {}; +var buffered_bot = null; + +var queue_overview = {}; // one-time queue overview to allow traversing +var $dq = $("#destination-queues"); + +load_configuration(() => { + // refresh parameters panel when ready + if (buffered_bot) { + refresh_configuration_info(buffered_bot); + } +}); + + + +$('#log-table').dataTable({ + lengthMenu: [[5, 10, 25, -1], [5, 10, 25, "All"]], + pageLength: 10, + order: [0, 'desc'], + autoWidth: false, + columns: ['date', 'bot_id', 'log_level', 'message', 'actions'].map(data => { return {data};}) +}); + +window.onresize = redraw; + +$(document).keydown(function (event) { + if ($("#message-playground").is(":focus")) { + if ($("[data-role=inject]").attr("data-checked") === "") { + // when entered a char for first time ever, mark the "inject message" checkbox + $("[data-role=inject]").prop("checked", true).attr("data-checked", true); + } + if (event.ctrlKey && event.keyCode === 13) { + // ctrl+enter submits + $("button[data-role=process]").click(); + } + } +}); + +function redraw() { + redraw_logs(); + redraw_queues(); +} + +function redraw_logs() { + $('#log-table').dataTable().fnClearTable(); + + if (bot_logs == {}) { + $('#log-table').dataTable().fnAdjustColumnSizing(); + $('#log-table').dataTable().fnDraw(); + return; + } + + for (let index in bot_logs) { + let log_row = $.extend(true, {}, bot_logs[index]); + let has_button = false; + + if (log_row.extended_message) { + var buttons_cell = ``; + has_button = true; + log_row.actions = buttons_cell; + } else if (log_row.message.length > MESSAGE_LENGTH) { + log_row.message = `${escape_html(log_row.message.slice(0, MESSAGE_LENGTH))}...`; + buttons_cell = ``; + has_button = true; + log_row.actions = buttons_cell; + } else { + log_row.actions = ''; + } + + + log_row.DT_RowClass = LEVEL_CLASS[log_row.log_level]; + + + $('#log-table').dataTable().fnAddData(log_row); + if (has_button) { + var extended_message_func = message_index => show_extended_message(message_index); + document.getElementById(`button-extended-message-${index}`).addEventListener('click', function (index) { + return function () { + extended_message_func(index) + } + }(index)) + } + } + + $('#log-table').dataTable().fnAdjustColumnSizing(); + $('#log-table').dataTable().fnDraw(); +} + +function redraw_queues() { + let bot_id = getUrlParameter('bot_id') || ALL_BOTS; + + let source_queue_element = document.getElementById('source-queue'); + let internal_queue_element = document.getElementById('internal-queue'); + //let destination_queues_element = document.getElementById('destination-queues'); + + source_queue_element.innerHTML = ''; + internal_queue_element.innerHTML = ''; + //destination_queues_element.innerHTML = ''; + + let bot_info = { + source_queues: {}, + destination_queues: {}, + fetched: true + }; + + if (bot_id === ALL_BOTS || !queue_overview.fetched) { + for (let [bot_name, bot] of Object.entries(bot_queues)) { + let source_queue = bot.source_queue; + let destination_queues = bot.destination_queues; + let internal_queue = bot.internal_queue; + + if (source_queue) { + bot_info.destination_queues[source_queue[0]] = source_queue; + bot_info.destination_queues[source_queue[0]].parent = bot_name; + } + + if (internal_queue !== undefined) { + let queue_name = `${bot_name}-queue-internal`; + bot_info.destination_queues[queue_name] = [queue_name, internal_queue]; + bot_info.destination_queues[queue_name].parent = bot_name; + } + } + } + + if (!queue_overview.fetched) { + // we build queue_overview only once; on bot detail, we spare this block + queue_overview = bot_info; + } + + if (bot_id !== ALL_BOTS) { + bot_info = bot_queues[bot_id]; + } + if (bot_info) { + if (bot_info.source_queue) { + let source_queue = source_queue_element.insertRow(); + let cell0 = source_queue.insertCell(0); + cell0.innerText = bot_info.source_queue[0]; + + let cell1 = source_queue.insertCell(1); + cell1.innerText = bot_info.source_queue[1]; + + let buttons_cell = source_queue.insertCell(2); + buttons_cell.appendChild(generateClearQueueButton(bot_info.source_queue[0])); + } + + if (bot_info.internal_queue !== undefined) { + let internal_queue = internal_queue_element.insertRow(); + let cell0 = internal_queue.insertCell(0); + cell0.innerText = 'internal-queue'; + + let cell1 = internal_queue.insertCell(1); + cell1.innerText = bot_info.internal_queue; + + let buttons_cell = internal_queue.insertCell(2); + buttons_cell.appendChild(generateClearQueueButton(`${bot_id}-queue-internal`)); + } + + let dst_queues = Object.values(bot_info.destination_queues).sort(); + + for (let bot of dst_queues) { + let [queue, count] = bot; + if ($(`tr:eq(${bot}) td:eq(0)`, $dq).text() === queue) { + // row exist, just update the count + $(`tr:eq(${bot}) td:eq(2)`, $dq).text(count); + } else { + // for some reason, dst_queues from server changed from the table + // let's find the table row + let o = $("tr td:first-child", $dq).filter(function () { + return $(this).text() === queue; + }); + if (o.length) { // successfully found + o.next().text(count); + } else { // not present in the table + // make unknown queue a new row + let $tr = $("").data("bot-id", queue_overview.destination_queues[queue].parent).appendTo($dq); + $("").appendTo($tr).text(queue).click(function () { + let selectBot = $(this).closest("tr").data("bot-id"); + if (selectBot) { + select_bot(selectBot, true); + } + }); + $("").appendTo($tr).text(""); + $("").appendTo($tr).text(count); + $("").appendTo($tr).html(generateClearQueueButton(queue)); // regenerate thrash button + } + refresh_path_names(); + } + } + } +} + +function generateClearQueueButton(queue_id) { + let spanHolder = document.createElement('span'); + spanHolder.className = 'fa fa-trash-o'; + + let clearQueueButton = document.createElement('button'); + clearQueueButton.queue = queue_id; + clearQueueButton.type = 'submit'; + clearQueueButton.class = 'btn btn-default'; + clearQueueButton.title = 'Clear'; + clearQueueButton.appendChild(spanHolder); + clearQueueButton.addEventListener("click", function (event) { + clearQueue(this.queue); + }); + + return clearQueueButton; +} + +function clearQueue(queue_id) { + authenticatedGetJson(managementUrl('clear', `id=${queue_id}`)) + .done(function (data) { + redraw_queues(); + $('#queues-panel-title').removeClass('waiting'); + }) + .fail(ajax_fail_callback(`Error clearing queue ${queue_id}`)); +} + +function load_bot_log() { + $('#logs-panel-title').addClass('waiting'); + + let number_of_lines = LOAD_X_LOG_LINES; + + let bot_id = getUrlParameter('bot_id') || ALL_BOTS; + let level = document.getElementById('log-level-indicator').value; + if(bot_id === ALL_BOTS) { + return; + } + // NOTE: The URL to fetch the log used to be "...?scope=log&...". + // It's now ".../getlog" instead of ".../log" because for some + // reason, the client (at least the Firefox versions I tested) did + // not even try to fetch the URL in the latter case. Switching from + // "log" to "getlog" made it work. + authenticatedGetJson(managementUrl('getlog', `id=${bot_id}&lines=${number_of_lines}&level=${level}`)) + .done(function (data) { + if(JSON.stringify(data) != JSON.stringify(bot_logs)) { // redraw only if content changed + bot_logs = data; + redraw_logs(); + } + }) + .fail(ajax_fail_callback('Error loading bot log information')) + .always(() => { + $('#logs-panel-title').removeClass('waiting'); + if (this instanceof Interval) { + this.blocking = false; + } + }); +} + +function load_bot_queues() { + $('#queues-panel-title').addClass('waiting'); + authenticatedGetJson(managementUrl('queues')) + .done(function (data) { + bot_queues = data; + redraw_queues(); + $('#queues-panel-title').removeClass('waiting'); + }) + .fail(ajax_fail_callback('Error loading bot queues information')) + .always(() => { + if (this instanceof Interval) { + this.blocking = false; + } + }); +} + +function select_bot(bot_id, history_push = false) { + if (history_push) { + window.history.pushState(null, null, MONITOR_BOT_URL.format(bot_id)); + } + + $("tr", $dq).remove(); // make destination table rebuild itself + + if (reload_queues) { + reload_queues.stop(); + } + + if (reload_logs) { + reload_logs.stop(); + } + + $('#monitor-target').text(bot_id); + + load_bot_queues(); + + reload_queues = new Interval(load_bot_queues, RELOAD_QUEUES_EVERY * 1000, true); + + $("#destination-queues-table").addClass('highlightHovering'); + if (bot_id !== ALL_BOTS) { + $("#logs-panel, #inspect-panel, #parameters-panel").css('display', 'block'); + $("#source-queue-table-div").css('display', 'block'); + $("#internal-queue-table-div").css('display', 'block'); + //$("#destination-queues-table").removeClass('highlightHovering'); + $("#destination-queues-table-div").removeClass().addClass('col-md-4'); // however, will be reset in refresh_path_names + $("#destination-queue-header").text("Destination Queues"); + + load_bot_log(); + reload_logs = new Interval(load_bot_log, RELOAD_LOGS_EVERY * 1000, true); + + // control buttons in inspect panel + $("#inspect-panel .panel-heading .control-buttons").remove(); + $("#inspect-panel .panel-heading").prepend(generate_control_buttons(bot_id, false, load_bot_log, true)); + + // connect to configuration panel + $('#monitor-target').append(` `); + + } else { + $("#logs-panel, #inspect-panel, #parameters-panel").css('display', 'none'); + $("#source-queue-table-div").css('display', 'none'); + $("#internal-queue-table-div").css('display', 'none'); + //$("#destination-queues-table").addClass('highlightHovering'); + $("#destination-queues-table-div").removeClass().addClass('col-md-12'); + $("#destination-queue-header").text("Queue"); + } + // refresh additional information + refresh_configuration_info(bot_id); +} + +function refresh_path_names() { + let parent = $dq.parent(); + if ($.isEmptyObject(path_names)) { + // expand the columns + //parent.find("col:eq(1)").css("visibility", "collapse"); + parent.find("col:eq(1)").css("display", "none"); + $("td:nth-child(2), th:nth-child(2)", parent).css("display", "none"); + + parent.find("th:eq(0)").removeClass().addClass("width-80"); + parent.find("th:eq(1)").removeClass(); + if ($("#destination-queues-table-div").hasClass('col-md-12')) { + // in full width display of all bots, there is no need of another hassling + return; + } + $("#destination-queues-table-div").removeClass("col-md-5").addClass("col-md-4"); + $("#internal-queue-table-div").removeClass("col-md-3").addClass("col-md-4"); + return; + } + + // fold the columns to make more space on the line due to the Path column + //parent.find("col:eq(1)").css("visibility", "inherit"); + //parent.find("col:eq(1)").css("display", "inherit"); + $("td:nth-child(2), th:nth-child(2)", parent).css("display", "revert"); + + + parent.find("th:eq(0)").removeClass().addClass("width-60"); + parent.find("th:eq(1)").addClass("width-20"); + $("#destination-queues-table-div").removeClass("col-md-4").addClass("col-md-5"); + $("#internal-queue-table-div").removeClass("col-md-4").addClass("col-md-3"); + + $("tr td:first-child", $dq).each(function () { + let path = path_names[$(this).text()] || null; + let $el = $(this).next("td"); + $el.text(path || "_default"); + if (!path) { + $el.css({color: "gray", "font-style": "italic"}); + } + }); +} + +/** + * Refresh information dependent on the loaded config files: parameters panel + named queues + * Only when configuration has already been loaded. + * @param {type} bot_id + * @returns {undefined} + */ +function refresh_configuration_info(bot_id) { + if (!app.nodes) { + // we're not yet ready, buffer the bot for later + buffered_bot = bot_id; + return; + } + + // search for named queue paths + path_names = {}; + + let bots = bot_id === ALL_BOTS ? Object.values(app.nodes) : [app.nodes[bot_id]]; + + for (let node of bots) { + for (let path in node.parameters.destination_queues) { + if (path !== '_default') { + for (let to of node.parameters.destination_queues[path]) { + path_names[to] = path; + } + } + } + } + + refresh_path_names(); + + // refresh parameters panel + let $panel = $("#parameters-panel .panel-body"); + $panel.text(""); + if (!app.nodes[bot_id] || !app.nodes[bot_id].parameters) { + $panel.text("Failed to fetch the information."); + return; + } + let params = app.nodes[bot_id].parameters; + for (let [key, param] of Object.entries(params)) { + if (typeof param !== 'string') { // display json/list instead of "[Object object]" + param = JSON.stringify(param); + } + let $el = $(`
  • ${escape_html(key)}: ${escape_html(param)}
  • `); + if (param && param.indexOf && param.indexOf(ALLOWED_PATH) === 0) { + let url = `${LOAD_CONFIG_SCRIPT}?file=${param}`; + authenticatedGetJson(url, data => { + let html = ""; + if (data.directory) { + html += `

    Directory ${escape_html(data.directory)}

    `; + } + + for (let file in data.files) { + let size = data.files[file].size ? `fetch ${escape_html(data.files[file].size)} B` : ""; + html += `

    File ${file}

    ${size}`; + if (data.files[file].contents) { + html += `
    ${escape_html(data.files[file].contents)}
    `; + } + } + $("
    ", {html: html}).appendTo($el); + }); + } + $el.appendTo($panel); + } + if (!Object.keys(params).length) { + $panel.text("No parameters."); + } +} +$("#parameters-panel").on("click", "a[data-role=fetchlink]", function () { + $.get($(this).attr("href"), data => { + $(this).after(`
    ${escape_html(data)}
    `).remove(); + }); + return false; +}); + +function show_extended_message(index) { + let modal_body = document.getElementById('modal-body'); + + let message = bot_logs[index].message; + + if (bot_logs[index].extended_message) { + message += '
    \n' + + bot_logs[index].extended_message.replace(/\n/g, '
    \n').replace(/ /g, ' '); + } + + modal_body.innerHTML = message; +} + +authenticatedGetJson(managementUrl('botnet', 'action=status')) + .done(function (data) { + let sidemenu = document.getElementById('side-menu'); + + let select_bot_func = function (bot_id) { + return function (event) { + event.preventDefault(); + select_bot(bot_id, true); + return false; + }; + }; + + // Insert link for special item 'All Bots' + let li_element = document.createElement('li'); + let link_element = document.createElement('a'); + link_element.innerText = ALL_BOTS; + link_element.setAttribute('href', `#${MONITOR_BOT_URL.format(ALL_BOTS)}`); + link_element.addEventListener('click', select_bot_func(ALL_BOTS)); + + li_element.appendChild(link_element); + sidemenu.appendChild(li_element); + + // Insert link for every bot + bot_status = data; + $(".control-buttons [data-role=control-status]").trigger("update"); + let bots_ids = Object.keys(data); + bots_ids.sort(); + + for (let index in bots_ids) { + let bot_id = bots_ids[index]; + li_element = document.createElement('li'); + link_element = document.createElement('a'); + + link_element.innerText = bot_id; + link_element.setAttribute('href', `#${MONITOR_BOT_URL.format(bot_id)}`); + link_element.addEventListener('click', select_bot_func(bot_id)); + + li_element.appendChild(link_element); + sidemenu.appendChild(li_element); + } + }) + .fail(ajax_fail_callback('Error loading botnet status')); + + + +$(document).ready(popState); +window.addEventListener("popstate", popState); +document.addEventListener('DOMContentLoaded', function () { + document.getElementById('log-level-indicator').addEventListener('change', load_bot_log); + + // Inspect panel functionality + let $insp = $("#inspect-panel"); + $("button[data-role=clear]", $insp).click(function () { + $("#message-playground").val(""); + $("#run-log").attr("rows", 3).val(""); + }); + $("button[data-role=get]", $insp).click(() => run_command("message get", "get")); + $("button[data-role=pop]", $insp).click(() => run_command("message pop", "pop")); + $("button[data-role=send]", $insp).click(() => run_command("message send", "send", $("#message-playground").val())); + $("button[data-role=process]", $insp).click(function () { + let msg; + if ($("[data-role=inject]", $insp).prop("checked")) { + if (!$("#message-playground").val()) { + show_error("Can't inject message from above – you didn't write any message"); + $("#message-playground").focus(); + return false; + } + msg = $("#message-playground").val(); + } + let dry = $("[data-role=dry]", $insp).prop("checked"); + let show = $("[data-role=show-sent]", $insp).prop("checked"); + run_command("process" + (show ? " --show-sent" : "") + (dry ? " --dryrun" : "") + (msg ? " --msg" : ""), "process", msg, dry, show); + }); +}); + +/** + * For purpose of better learning curve, we build intelmq command here at client + * (however we won't upload it on server, we prefer have a whitelisted set of commands due to security + * @param {string} bot + * @param {string} cmd + * @param {type} msg + * @param {type} dry + * @returns {undefined} + */ +function run_command(display_cmd, cmd, msg = "", dry = false, show = false) { + let bot_id = getUrlParameter('bot_id') || ALL_BOTS; + let tmp = msg ? `'${msg.replaceAll("'", "'\\''")}'` : ""; + $("#command-show").show().text(`${CONTROLLER_CMD} run ${bot_id} ${display_cmd} ${tmp}`); //XX dry are not syntax-correct + $("#run-log").val("loading..."); + $('#inspect-panel-title').addClass('waiting'); + let call = authenticatedAjax({ + method: "post", + data: {msg}, + url: managementUrl('run', `bot=${bot_id}&cmd=${cmd}&dry=${dry}&show=${show}`), + }).done(function (data) { + // Parses the received data to message part and to log-only part + let logs = []; + let msg = []; + let logging = logs; + for (let line of data.split("\n")) { + if (logging === logs) { + if (line === "{") { + logging = msg; + } + } else { + if (line === "}") { + msg.push(line); + logging = logs; + continue; + } + } + + logging.push(line); //write either to logs or msgs + } + if (msg.length) { // we won't rewrite an old message if nothing came + $("#message-playground").attr("rows", msg.length).val(msg.join("\n")); + } + $("#run-log").attr("rows", logs.length).val(logs.join("\n")); + }).fail(ajax_fail_callback('Error getting message')) + .always(() => { + $('#inspect-panel-title').removeClass('waiting'); + $("#run-log").data("call", null); + }); + + // informate user if there is a lag + $("#run-log").data("call", call); + setTimeout(() => { + if ($("#run-log").data("call") === call) { + $("#run-log").val("loading... or timeouting..."); + } + }, 3000); +} + + +/** + * Select correct bot when browsing in history or coming from an external link etc. + */ +function popState() { + $("#run-log").val(""); + let bot_id = getUrlParameter('bot_id') || ALL_BOTS; + if (typeof (bot_id) !== 'undefined') { + //window.history.replaceState(null, null, MONITOR_BOT_URL.format(bot_id)); + select_bot(bot_id); + } else { + select_bot(ALL_BOTS); + } +} diff --git a/intelmq/web/static/js/network-configuration.js b/intelmq/web/static/js/network-configuration.js new file mode 100644 index 000000000..4361cc116 --- /dev/null +++ b/intelmq/web/static/js/network-configuration.js @@ -0,0 +1,283 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team , 2020 Edvard Rejthar , 2021 Mikk Margus Möll +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +/** + * Big variable options, passed to vis library. + * There are also all the manipulation methods. + */ +'use strict'; + +var NETWORK_OPTIONS = { + physics: { + hierarchicalRepulsion: { + nodeDistance: 200, + springLength: 200 + }, + stabilization: { + enabled: true, + fit: true + }, + solver: 'hierarchicalRepulsion' + }, + interaction: { + tooltipDelay: 1000, + navigationButtons: true, + keyboard: { + bindToWindow: false + } + }, + nodes: { + font: { + size: 14, // px + face: 'arial', + align: 'center' + } + }, + edges: { + length: 200, + arrows: { + to: {enabled: true, scaleFactor: 1, type: 'arrow'} + }, + physics: true, + font: { + size: 14, // px + face: 'arial', + }, + color: { + inherit: false + }, + smooth: { + enabled: true, + type: 'continuous' + } + }, + groups: { + Collector: { + shape: 'box', + color: GROUP_COLORS['Collector'][0], + }, + Parser: { + shape: 'box', + color: GROUP_COLORS['Parser'][0] + }, + Expert: { + shape: 'box', + color: GROUP_COLORS['Expert'][0], + fontColor: "#FFFFFF" + }, + Output: { + shape: 'box', + color: GROUP_COLORS['Output'][0] + } + }, + + manipulation: { + enabled: true, + initiallyActive: true, + editEdge: false, + + addNode: (data, callback) => create_form("Add Node", data, callback), + editNode: function (data, callback) { + create_form("Edit Node", data, callback); + fill_bot(data.id, undefined, undefined); + }, + deleteNode: function (data, callback) { + callback(data); + let node_set = new Set(data.nodes); + + for (let edge_index of data.edges) { + let [from, to, path] = from_edge_id(edge_index); + if (!node_set.has(from)) { // otherwise handled by node deletion below + remove_edge(from, to, path); + } + } + + for (let node_name of data.nodes) { + delete app.nodes[node_name]; + } + set_pending_change(); + }, + addEdge: function (data, callback) { + if (data.from === data.to) { + show_error('This action would cause an infinite loop'); + return; + } + + if (data.path === undefined) + data.path = '_default'; + + let edit_needed = false; // there is path name clash + let occupied_values = new Set(); // prevent edges from overlapping + let roundness = 0; + + let edge_id = to_edge_id(data.from, data.to, data.path); + let source_paths = app.nodes[data.from].parameters.destination_queues; + for (let path_id in source_paths) { + if (source_paths[path_id].includes(`${data.to}-queue`)) { + let smooth = app.network_data.edges.get(edge_id).smooth; + occupied_values.add(smooth ? smooth.roundness : 0); + + if(path_id === data.path) { + show_error('There is already a link between those bots with the same path, rename.'); + edit_needed = true; + } + } + } + + if (occupied_values.size) { + while(occupied_values.has(roundness)) { + roundness += 0.3; + } + data.smooth = {type: 'curvedCCW', roundness}; + } + + let group_from = app.nodes[data.from].group; + let group_to = app.nodes[data.to].group; + let neighbors = ACCEPTED_NEIGHBORS[group_from]; + let available_neighbor = false; + + if (neighbors.includes(group_to)) { + data.id = edge_id; + callback(data); + available_neighbor = true; + let cautious = CAUTIOUS_NEIGHBORS[group_from] ?? []; + if (cautious.includes(group_to)) { + show_error(`Node type ${group_from} can connect to the ${group_to}, however it's not so common.`); + } + } + + if (!available_neighbor) { + if (neighbors.length === 0) { + show_error(`Node type ${group_from} can't connect to other nodes`); + } else { + show_error(`Node type ${group_from} can only connect to nodes of types: ${neighbors.join()}`); + } + return; + } + + add_edge(data.from, data.to, data.path); + + set_pending_change(data.from); + if (edit_needed) { + editPath(app, data.id, true); + } + }, + deleteEdge: function (data, callback) { + let [from, to, path] = from_edge_id(data.edges[0]); + let queue = app.nodes[from].parameters.destination_queues[path]; + remove_edge(from, to, path); + + set_pending_change(from); + callback(data); + } + }, + layout: { + hierarchical: false, + randomSeed: undefined + } +}; + +/** + * Setting path name of a queue. If path already exists between bots, dialog re-appears. + * If cancelled, previous path name is restored, or queue is deleted (if was just being added). + * As this is not a standard-vis function, it has to be a separate method. + * + * @param app + * @param edge id of the edge + * @param adding True if edge is just being added (and shall be removed if we fail to provide a unique path name). + */ +function editPath(app, edge, adding=false) { + let ok_clicked = false; + let [from, to, original_path] = from_edge_id(edge); + let nondefault_path = original_path === '_default' ? undefined : original_path; + let new_path, nondefault_new_path; + + let $input = $("", {placeholder: "_default", val: nondefault_path}); + popupModal("Set the edge name", $input, () => { + let in_val = $input.val(); + [new_path, nondefault_new_path] = (in_val && in_val !== '_default') ? [in_val, in_val] : ['_default', undefined]; + if (original_path === new_path) { + return; + } + + ok_clicked = true; + set_pending_change(); + }).on("hide.bs.modal", () => { + let from_queues = app.nodes[from].parameters.destination_queues[new_path] ?? []; + let duplicate_edge = from_queues.includes(to); + + if (duplicate_edge) { + if (ok_clicked) { + show_error(`Could not add the queue ${new_path}, there already is such queue.`); + return editPath(app, edge, adding); + } else if(adding) { + show_error(`Removing duplicate edge ${new_path}.`); + } else { + show_error("Keeping original path name."); + return; + } + } + + if (ok_clicked) { + let new_id = to_edge_id(from, to, new_path); + + remove_edge(from, to, original_path); + app.network_data.edges.remove({id: edge}); + + add_edge(from, to, new_path); + app.network_data.edges.add({id: new_id, from, to: to.replace(/-queue$/, ''), label: nondefault_new_path}); + } + }); +} + +/** + * As this is not a standard-vis function, it has to be a separate method. + */ +function duplicateNode(app, bot) { + let new_id = gen_new_id(bot); + + // deep copy old bot information + let node = $.extend(true, {}, app.nodes[bot]); + app.positions[new_id] = app.positions[bot]; + node.id = new_id; + node.bot_id = new_id; + app.nodes[new_id] = node; + // add to the Vis and focus + app.network_data.nodes.add(convert_nodes([node], true)); + for (let edge of app.network.getConnectedEdges(bot).map(edge => app.network_data.edges.get(edge))) { + let [old_from, old_to, path] = from_edge_id(edge.id); + if (edge.from === bot) { + edge.from = new_id; + } + else if (edge.to === bot) { + edge.to = new_id; + } + edge.id = to_edge_id(edge.from, edge.to, path); + app.network_data.edges.add(edge); + } + + app.network.selectNodes([new_id]); + app.network.focus(new_id); + set_pending_change(); +} + +function remove_edge(from, to, path) { + let queues = app.nodes[from].parameters.destination_queues; + let queue = queues[path]; + let to_index = queue.indexOf(to); + if (to_index !== -1) + queue.splice(to_index, 1); + + if (queue.length === 0) + delete queues[path]; +} + +function add_edge(from, to, path) { + if (!to.endsWith('-queue')) { + to += '-queue'; + } + let queues = app.nodes[from].parameters.destination_queues; + let queue = path in queues ? queues[path] : (queues[path] = []); + queue.push(to); +} diff --git a/intelmq/web/static/js/positions.js b/intelmq/web/static/js/positions.js new file mode 100644 index 000000000..b8236a35d --- /dev/null +++ b/intelmq/web/static/js/positions.js @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +var app = app || {}; + +function generate_positions_conf() { + var new_positions = app.network.getPositions(); + new_positions = sortObjectByPropertyName(new_positions); + + new_positions.settings = settings; + return JSON.stringify(new_positions, undefined, 4); +} + +function read_positions_conf(config) { + if("settings" in config) { // reload settings + settings = config.settings; + if (settings.physics === null) { + settings.physics = Object.keys(app.nodes).length < 40; // disable physics by default when there are more then 40 bots + } + delete config.settings; + } + return config; +} diff --git a/intelmq/web/static/js/runtime.js b/intelmq/web/static/js/runtime.js new file mode 100644 index 000000000..5c447867a --- /dev/null +++ b/intelmq/web/static/js/runtime.js @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team +// +// SPDX-License-Identifier: AGPL-3.0-or-later +'use strict'; + +var app = {}; // will be later redefined as a VisModel object or any other object (used in Configuration and Monitor tab) + +//TODO: add global +function generate_runtime_conf(nodes, defaults) { + + let tmp_nodes = nodes; + tmp_nodes.global = defaults; + + sortObjectByPropertyName(tmp_nodes); + for (let id in tmp_nodes) { + let node = tmp_nodes[id]; + delete node.id; + if ('parameters' in node) { + sortObjectByPropertyName(node.parameters); + } + sortObjectByPropertyName(node); + } + + return JSON.stringify(tmp_nodes, undefined, 4); +} + +function read_runtime_conf(config) { + bot_definition = config; + let nodes = {}; + for (let bot_id in config) { + if (bot_id != 'global') { + bot_definition[bot_id].groupname = GROUPNAME_TO_GROUP[bot_definition[bot_id].group]; // translate ex: `Parser` to `parsers` + let bot = config[bot_id]; + bot.bot_id = bot_id; + + if (!('enabled' in bot)) { + bot.enabled = true; + } + + if (!('run_mode' in bot)) { + bot.run_mode = 'continuous'; + } + + if (bot.parameters.destination_queues === undefined) { + bot.parameters.destination_queues = {}; + } + + nodes[bot_id] = bot; + } + } + + return nodes; +} + +function load_file(url, callback) { + let escaped_url = escape_html(url); + authenticatedGetJson(url) + .done(function (json) { + try { + callback(json); + } + catch(e) { + // don't bother to display error, I think the problem will be clearly seen with the resource itself, not within the processing + console.log(e); + show_error(`Failed to load config file properly ${escaped_url}.`, true); + } + }) + .fail(function (jqxhr, textStatus, error) { + let err = escape_html(`${textStatus}, ${error}`); + show_error(`Get an error ${err} when trying to obtain config file properly ${escaped_url}.`, true); + callback({}); + }); +} + + +// Configuration files fetching +function load_configuration(callback = () => {}) { + load_file(RUNTIME_FILE, (config) => { + app.defaults = read_defaults_conf(config); + app.nodes = read_runtime_conf(config); + if (typeof read_positions_conf !== "undefined") { // skipped on Monitor tab + load_file(POSITIONS_FILE, (config) => { + app.positions = read_positions_conf(config); + draw(); + resize(); + + callback(); + }); + } else { + callback(); + } + }); +} diff --git a/intelmq/web/static/js/sb-admin-2.js b/intelmq/web/static/js/sb-admin-2.js new file mode 100644 index 000000000..b4677aba4 --- /dev/null +++ b/intelmq/web/static/js/sb-admin-2.js @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team +// +// SPDX-License-Identifier: AGPL-3.0-or-later +'use strict'; + +$(() => $('#side-menu').metisMenu()); + +//Loads the correct sidebar on window load, +//collapses the sidebar on window resize. +// Sets the min-height of #page-wrapper to window size + +function resize_handler() { + var window_height = (this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height; + var window_width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width; + + // Resize body + var body = document.getElementsByTagName('body')[0]; + body.style.height = `${window_height}px`; + body.style.width = `${window_width}px`; + + var container = document.getElementById('page-wrapper-with-sidebar') || document.getElementById('page-wrapper'); + container.style.height = `${window_height - container.offsetTop}px`; + container.style.width = `${window_width - container.offsetLeft}px`; + container.style.overflowX = "auto"; + container.style.overflowY = "auto"; + + var title_height = (window_height * 0.10); + $('.page-header-text').css('font-size', `${title_height}px`); + $('.page-header-text').css('line-height', `${title_height * 2}px`); + + let topOffset = 50; + let width = window_width; + if (width < 768) { + $('div.navbar-collapse').addClass('collapse') + topOffset = 100; // 2-row-menu + } else { + $('div.navbar-collapse').removeClass('collapse') + } + + let height = window_height - topOffset; + if (height < 1) height = 1; + if (height > topOffset) { + $("#page-wrapper").css("min-height", `${height}px`); + } + + $('#side-menu').css('max-height', `${height}px`); + $('#side-menu').css('overflow', 'auto'); +} + +$(window).bind("load resize", resize_handler); diff --git a/intelmq/web/static/js/static.js b/intelmq/web/static/js/static.js new file mode 100644 index 000000000..432bf772b --- /dev/null +++ b/intelmq/web/static/js/static.js @@ -0,0 +1,600 @@ +// SPDX-FileCopyrightText: 2020 IntelMQ Team , 2020 Edvard Rejthar , 2021 Mikk Margus Möll +// +// SPDX-License-Identifier: AGPL-3.0-or-later +'use strict'; + +var CORE_FIELDS = 5; + +var ACCEPTED_NEIGHBORS = { + Collector: ['Parser', 'Expert', 'Output'], + Parser: ['Expert', 'Output'], + Expert: ['Parser', 'Expert', 'Output'], + Output: [] +} + +var REVERSE_ACCEPTED_NEIGHBORS = Object.fromEntries(Object.keys(ACCEPTED_NEIGHBORS).map(key => [key, []])); + +for (let [from, to_list] of Object.entries(ACCEPTED_NEIGHBORS)) { + for (let to of to_list) { + REVERSE_ACCEPTED_NEIGHBORS[to].push(from); + } +} + +var CAUTIOUS_NEIGHBORS = { + Collector: ['Expert'], + Expert: ['Parser'] +} + +var GROUP_LEVELS = { + Collector: 0, + Parser: 1, + Expert: 2, + Output: 3 +}; +var GROUPNAME_TO_GROUP = { + Collector: "collectors", + Parser: "parsers", + Expert: "experts", + Output: "outputs" +}; + +/** + * 1st value is default color of running bot, latter of a stopped bot + */ +var GROUP_COLORS = { + Collector: ['#ff6666', '#cc6666'], + Parser: ['#66ff66', '#66cc66'], + Expert: ['#66a3ff', '#66a3aa'], + Output: ['#ffff66', '#cccc66'] +} + +var LEVEL_CLASS = { + DEBUG: 'success', + INFO: 'info', + WARNING: 'warning', + ERROR: 'danger', + CRITICAL: 'danger' +} + +var STARTUP_KEYS = ['group', 'name', 'module', 'description', 'enabled', 'run_mode']; + +var BOT_ID_REGEX = /^[0-9a-zA-Z.-]+$/; +var PARAM_KEY_REGEX = /^[0-9a-zA-Z._-]+$/; + +var LOAD_CONFIG_SCRIPT = API + "config"; +var MANAGEMENT_SCRIPT = API + "controller"; + +var BOTS_FILE = API + "bots"; +var HARMONIZATION_FILE = API + "harmonization"; +var RUNTIME_FILE = API + "runtime"; +var POSITIONS_FILE = API + "positions"; + +var RELOAD_QUEUES_EVERY = 1; /* 1 seconds */ +var RELOAD_LOGS_EVERY = 3; /* 3 seconds */ +var RELOAD_STATE_EVERY = 3; /* 3 seconds */ +var LOAD_X_LOG_LINES = 30; + +var MESSAGE_LENGTH = 200; + +var MONITOR_BOT_URL = "monitor?bot_id={0}"; + +var page_is_exiting = false; + +var settings = { + physics: null, // by default, physics is on depending on bot count + live: true, // by default on +}; + +$(window).on('unload', () => page_is_exiting = true); + +function sortObjectByPropertyName(obj) { + return Object.keys(obj).sort().reduce((c, d) => (c[d] = obj[d], c), {}); +} + +// String formatting function usage "string {0}".format("1") => "string 1" +if (!String.prototype.format) { + String.prototype.format = function () { + let args = arguments; + return this.replace(/{(\d+)}/g, (match, number) => typeof args[number] === 'undefined' ? match : args[number]); + }; +} + +/* + * error reporting + */ +let lw_tips = new Set(); +$(function () { + let $lw = $("#log-window"); + let closeFn = () => { + $lw.hide(); + $(".contents", $lw).html(""); + lw_tips.clear(); // no tips displayed + return false; + }; + + $lw.on("click", e => { // clicking enlarges but not shrinks so that we may copy the text + let btn = $(e.target); + if (!btn.hasClass("extended")) { + btn.toggleClass("extended"); + + //$(".alert", this).prependTo(btn); + + $(document).on('keydown.close-log-window', event => { + if (event.key == "Escape") { + $(document).off('keydown.close-log-window'); + $lw.removeClass("extended"); + } + }); + } + }); + $("#log-window [role=close]").click(closeFn); +}); + +function show_error(string, permit_html=false) { + if (!permit_html) { + string = escape_html(string); + } + + let d = new Date(); + let time = new Date().toLocaleTimeString().replace(/:\d+ /, ' '); + let $lwc = $("#log-window .contents"); + let $el = $(`

    ${time} ${string}

    `); + let found = false; + $("p", $lwc).each((i, v) => { + if ($("span:eq(2)", $(v)).text() === $("span:eq(2)", $el).text()) { + // we've seen this message before + found = true; + // put it in front of the other errors + // only if the error window is not expanded (so that it does not shuffle when the user read the details) + if (!$(v).closest("#log-window").hasClass("extended")) { + $(v).prependTo($lwc); + } + //blink + let blink_e = v.children[0]; + $(blink_e, $(v)).text(time).stop().animate({opacity: 0.1}, 100, () => { + $(blink_e).animate({opacity: 1}, 100); + }); + // increment 'seen' counter + let counter = parseInt($("span:eq(1)", $(v)).text()) || 1; + $("span:eq(1)", $(v)).text(`${counter + 1}×`); + return false; + } + }); + if (!found) { + $("#log-window").show().find(".contents").prepend($el); + } + /*if(!page_is_exiting) { + alert(string); + }*/ +} + + +function ajax_fail_callback(str) { + return function (jqXHR, textStatus, message) { + if (textStatus === "timeout") { + // this is just a timeout, no other info needed + show_error(`${str} timeout`); + return; + } + if (jqXHR.status === 0) { // page refreshed before ajax finished + return; + } + + let command = "", tip = "", report = ""; + try { + let data = JSON.parse(jqXHR.responseText); + report = data.message; + command = ` ${escape_html(data.command)}`; + if (data.tip && !lw_tips.has(data.tip)) { + // display the tip if not yet displayed on the screen + lw_tips.add(data.tip); + tip = `
    TIP: ${escape_html(data.ip)}
    `; + } + if (message === "Internal Server Error") { + message = ""; // this is expected since we generated this in PHP when an error was spot, ignore + } + } catch (e) { + report = jqXHR.responseText; + } + if (report) { + // include full report but truncate the length to 2000 chars + // (since '.' is not matching newline characters, we're using '[\s\S]' so that even multiline string is shortened) + let report_text = escape_html(report.replace(/^(.{2000})[\s\S]+/, "$1...")); + report_text = report_text.replace(/(?:\r\n|\r|\n)/g, '
    '); + report = ` ${report_text}`; + } + + if (typeof message === 'object') { + message = JSON.stringify(message); + } + + show_error(`${str}:${report}${command}${tip} ${escape_html(message)}`, true); + }; +} + + +/** + * Handy interval class, waiting till AJAX request finishes (won't flood server if there is a lag). + */ +class Interval { + /** + * Class for managing intervals. + * Auto-delaying/boosting depending on server lag. + * Faking intervals by timeouts. + * + * @param {type} fn + * @param {type} delay + * @param {bool} blocking If true, the fn is an AJAX call. The fn will not be called again unless it calls `this.blocking = false` when AJAX is finished. + * You may want to include `.always(() => {this.blocking = false;})` after the AJAX call. (In 'this' should be instance of the Interval object.) + * + * (Note that we preferred that.blocking setter over method unblock() because interval function + * can be called from other sources than this class (ex: at first run) and a non-existent method would pose a problem.) + * @returns {Interval} + */ + constructor(fn, delay, ajax_wait) { + this.fn = fn; + this.delay = this._delay = delay; + this._delayed = function () { + this.time1 = +new Date(); + this.fn.call(this); + if (ajax_wait !== true && this.running) { + this.start(); + } + }.bind(this); + this.start(); + } + + start() { + this.stop(); + this.running = true; + this.instance = setTimeout(this._delayed, this._delay); + return this; + } + + stop() { + clearTimeout(this.instance); + this.running = false; + return this; + } + + /** + * Launch callback function now, reset and start the interval. + * @return {Interval} + */ + call_now() { + this.stop(); + this._delayed(); + this.start(); + return this; + } + + /** + * Start if stopped or vice versa. + * @param start If defined, true to be started or vice versa. + */ + toggle(start = null) { + if (start === null) { + this.toggle(!this.running); + } else if (start) { + this.start(); + } else { + this.stop(); + } + return this; + } + + set blocking(b) { + if (b === false) { + let rtt = +new Date() - this.time1; + if (rtt > this._delay / 3) { + if (this._delay < this.delay * 10) { + this._delay += 100; + } + } else if (rtt < this._delay / 4 && this._delay >= this.delay) { + this._delay -= 100; + } + if (this.running) { + this.start(); + } + } + } +} + +/** + * JS-click on a link that supports Ctrl+clicking for opening in a new tab. + * @param {string} url + * @returns {Boolean} False so that js-handled click is not followed further by the browser. + */ +function click_link(url, event) { + if (event && event.ctrlKey) { // we want open a new tab + let win = window.open(url, '_blank'); + if (win) { + win.focus(); + } else { // popups disabled + window.location = url; + } + } else { + window.location = url; + } + return false; +} + + +/** + * Control buttons to start/stop/... a bot, group or whole botnet + */ +var BOT_CLASS_DEFINITION = { + starting: 'warning', + running: 'success', + stopping: 'warning', + stopped: 'danger', + reloading: 'warning', + restarting: 'warning', + incomplete: 'warning', + error: 'danger', + disabled: 'ligth', + unknown: 'warning' +}; +var BOT_STATUS_DEFINITION = { + starting: 'starting', + running: 'running', + stopping: 'stopping', + stopped: 'stopped', + reloading: 'reloading', + restarting: 'restarting', + incomplete: 'incomplete', + error: 'error', + unknown: 'unknown' +}; + +var botnet_status = {}; // {group | true (for whole botnet) : BOT_STATUS_DEFINITION} +var bot_status = {}; // {bot-id : BOT_STATUS_DEFINITION} +var bot_status_previous = {}; // we need a shallow copy of bot_status, it's too slow to ask `app` every time +var bot_definition = {}; // {bot-id : runtime information (group, ...)}; only management.js uses this in time + +$(document).on("click", ".control-buttons button", e => { + let btn = $(e.target); + + let parent = btn.parent(); + if (parent.hasClass('btn')) { // clicked on glyphicon, shift up by one level + btn = parent; + parent = parent.parent(); + } + + let bot = parent.attr("data-bot-id"); + let botnet = parent.attr("data-botnet-group"); + let callback_fn = parent.data("callback_fn"); + let url; + if (bot) { + bot_status[bot] = btn.attr("data-status-definition"); + url = managementUrl("bot", `action=${btn.attr("data-url")}&id=${bot}`); + } else { + botnet_status[botnet] = btn.attr("data-status-definition"); + url = managementUrl('botnet', `action=${btn.attr("data-url")}&group=${botnet}`); + for (let bot_d of Object.values(bot_definition)) { + if (bot_d.groupname === botnet) { + bot_status[bot_d.bot_id] = btn.attr("data-status-definition"); + } + } + + } + + callback_fn.call(e.target, bot || botnet, 0); + btn.siblings("[data-role=control-status]").trigger("update"); + + authenticatedGetJson(url) + .done(data => { + if (bot) { // only restarting action returns an array of two values, the latter is important; otherwise, this is a string + bot_status[bot] = Array.isArray(data) ? data.slice(-1)[0] : data; + } else { // we received a {bot => status} object + Object.assign(bot_status, data); // merge to current list + } + }) + .fail(() => { + ajax_fail_callback(`Error ${bot_status[bot] || botnet_status[botnet]} bot${!bot ? "net" : ""}`).apply(null, arguments); + bot_status[bot] = BOT_STATUS_DEFINITION.error; + }).always(() => { + btn.siblings("[data-role=control-status]").trigger("update"); + callback_fn.call(e.target, bot || botnet, 1); + }); +}); + +/** + * Public method to include control buttons to DOM. + * @param {string|null} bot id + * @param {string|null} botnet Manipulate the whole botnet or a group. Possible values: "botnet", "collectors", "parsers", ... Parameter bot_id should be null. + * @param {bool} status_info If true, dynamic word containing current status is inserted. + * @param {fn} Fn (this = button clicked, bot-id|botnet, finished = 0|1) + * Launched when a button is clicked (finished 0) and callback after AJAX completed (finished 1). + * @returns {$jQuery} + */ +function generate_control_buttons(bot = null, botnet = null, callback_fn = null, status_info = false) { + let $el = $("#common-templates .control-buttons").clone() + .data("callback_fn", callback_fn || (() => { + })); + if (bot) { + $el.attr("data-bot-id", bot); + $el.attr("data-botnet-group", bot in bot_definition ? bot_definition[bot].groupname : null); // specify group (ignore in Monitor, not needed and might not be ready) + } else { + $el.attr("data-botnet-group", botnet); + } + if (status_info) { + $("", {"data-role": "control-status"}).bind("update", e => { + let btn = $(e.target); + let bot = btn.closest(".control-buttons").attr("data-bot-id"); + let botnet = btn.closest(".control-buttons").attr("data-botnet-group"); + let status = bot ? bot_status[bot] : botnet_status[botnet]; + btn.text(status).removeClass().addClass(`bg-${BOT_CLASS_DEFINITION[status]}`); + }).prependTo($el).trigger("update"); + } + return $el; +} + +/** + * Reads the parameter from URL + */ +function getUrlParameter(sParam) { + let sPageURL = decodeURIComponent(window.location.search.substring(1)), sURLVariables = sPageURL.split('&'), sParameterName, i; + for (let i = 0; i < sURLVariables.length; i++) { + let sParameterName = sURLVariables[i].split('='); + if (sParameterName[0] === sParam) { + return sParameterName[1] === undefined ? true : sParameterName[1]; + } + } +} + +/** + * Accesskeyfie + * Turns visible [data-accesskey] to elements with accesskey and shows the accesskey with an underscore if possible. + */ +function accesskeyfie() { + let seen = new Set(); + $("[data-accesskey]").attr("accesskey", ""); // reset all accesskeys. In Chrome, there might be only one accesskey 'e' on page. + $("[data-accesskey]:visible").each((i, v) => { + let btn = $(v); + let key = btn.attr("data-accesskey"); + if (seen.has(key)) { + return false; // already defined at current page state + } + seen.add(key); + btn.attr("accesskey", key); + // add underscore to the accesskeyed letter if possible (can work badly with elements having nested DOM children) + let t1 = escape_html(btn.text()); + let t2 = t1.replace(new RegExp(key, "i"), match => `${match}`); + if (t1 !== t2) { + btn.html(t2); + } + }); +} + + +/** + * Determine the URL for management commands. + */ +function managementUrl(cmd, params) { + let url = API + cmd; + if (params !== undefined) { + url += "?" + params; + } + return url; +} + + +/** + * Login/session handling + */ + + +function authenticatedGetJson(url) { + return authenticatedAjax({ + dataType: "json", + url, + }); +} + +function authenticatedAjax(settings) { + let token = sessionStorage.getItem("login_token"); + if (token !== null) { + settings.headers = { + Authorization: token + }; + } + return $.ajax(settings); +} + + + +// Intercept the login submit and send an Ajax request instead. +$(document).ready(function() { + updateLoginStatus(); + + $('#loginForm').submit(function(e) { + e.preventDefault(); + $.ajax({ + type: 'POST', + url: managementUrl("login"), + // Specifies exactly which data is sent. + data: { + username: $('#loginForm #username').val(), + password: $('#loginForm #password').val(), + }, + // Specifies which formart is expected as response. + dataType: "json", + // sets timeout to 3 seconds + timeout: 3000, + // Deletes the content of the password field when the request is + // finished. (after success and error callbacks are executed) + complete: () => $('#loginForm #password').val(""), + // Executes this if the request was successful. + }).done(data => { + // Check if login_token and username came back and store them in + // sessionStorage. + if (typeof data.login_token !== 'undefined' && + typeof data.username !== 'undefined') { + sessionStorage.setItem("login_token", data.login_token); + sessionStorage.setItem("username", data.username); + + $('#loginErrorField').text("") + $('#modalLoginForm').modal('hide'); + updateLoginStatus(); + window.location.reload(); + } else if (typeof data.error !== 'undefined') { + // If authentication failed, the returned error message is displayed. + $('#loginErrorField').text(data.error); + } else { + // Other error, display the response for easier debugging. + $('#loginErrorField').text("Login failed, server response was " + data); + } + }) + .fail(function(jqXHR, textStatus) { + if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.errors !== 'undefined') { + let concatenated = ""; + for (let key in jqXHR.responseJSON.errors) { + concatenated += jqXHR.responseJSON.errors[key] + ". " + } + $('#loginErrorField').text("Login failed, server response was: " + concatenated); + } else { + $('#loginErrorField').text("Login failed with unknown reason. Please report this bug."); + console.log(jqXHR.responseText) + console.log(jqXHR.responseJson) + } + }); + }); + + $('#logOut').click(logout); +}); + +function logout() { + sessionStorage.removeItem("login_token"); + sessionStorage.removeItem("username"); + + updateLoginStatus(); +} + +function updateLoginStatus() { + let status = document.getElementById('login-status'); + let loginButton = document.getElementById('signUp'); + let logoutButton = document.getElementById('logOut'); + let username = sessionStorage.getItem("username"); + if (username !== null) { + status.textContent = `Logged in as: ${username}`; + loginButton.style.display = "none"; + logoutButton.style.removeProperty("display"); + } else { + status.textContent = "Not logged in"; + loginButton.style.removeProperty("display"); + logoutButton.style.display = "none"; + } +} + +var html_characters = [ + ['&', '&'], + ['<', '<'], + ['>', '>'], + ['"', '"'], + ["'", "'"], +]; + +function escape_html(text) { + return html_characters.reduce((s, [character, replacement]) => s.replaceAll(character, replacement), text); +} diff --git a/intelmq/web/static/less/management.less b/intelmq/web/static/less/management.less new file mode 100644 index 000000000..8728731f7 --- /dev/null +++ b/intelmq/web/static/less/management.less @@ -0,0 +1,21 @@ +/*SPDX-FileCopyrightText: 2020 IntelMQ Team */ +/*SPDX-License-Identifier: AGPL-3.0-or-later*/ + +#botnet-panels > div.panel { + .panel-div { + margin-bottom: 16px; + } +} + +#botnet-status { + padding: 8px; +} + +#graph-container { + margin-top: 16px; + overflow: auto; +} + +#bot-table-panel { + overflow: auto; +} \ No newline at end of file diff --git a/intelmq/web/static/less/sb-admin-2.less b/intelmq/web/static/less/sb-admin-2.less new file mode 100644 index 000000000..f127fdaba --- /dev/null +++ b/intelmq/web/static/less/sb-admin-2.less @@ -0,0 +1,539 @@ +/*SPDX-FileCopyrightText: Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com)*/ +/*SPDX-License-Identifier: Apache-2.0*/ + +body { + background-color: #f8f8f8; + overflow: auto; +} + +@media(min-width:768px) { + body { + overflow: hidden; + } +} + +#page-wrapper { + margin: 0px; + padding: 0px; + min-height: 568px; + background-color: #fff; +} + +#page-wrapper-with-sidebar { + padding: 0px; + min-height: 568px; + background-color: #fff; +} + +@media(min-width:768px) { + #page-wrapper-with-sidebar { + position: inherit; + margin: 0 0 0 250px; + padding: 0px; + border-left: 1px solid #e7e7e7; + } +} + +.navbar-top-links li { + display: inline-block; +} + +.navbar-top-links li:last-child { + margin-right: 15px; +} + +.navbar-top-links li a { + padding: 15px; + min-height: 50px; +} + +.navbar-top-links .dropdown-menu li { + display: block; +} + +.navbar-top-links .dropdown-menu li:last-child { + margin-right: 0; +} + +.navbar-top-links .dropdown-menu li a { + padding: 3px 20px; + min-height: 0; +} + +.navbar-top-links .dropdown-menu li a div { + white-space: normal; +} + +.navbar-top-links .dropdown-messages, +.navbar-top-links .dropdown-tasks, +.navbar-top-links .dropdown-alerts { + width: 310px; + min-width: 0; +} + +.navbar-top-links .dropdown-messages { + margin-left: 5px; +} + +.navbar-top-links .dropdown-tasks { + margin-left: -59px; +} + +.navbar-top-links .dropdown-alerts { + margin-left: -123px; +} + +.navbar-top-links .dropdown-user { + right: 0; + left: auto; +} + +.sidebar .sidebar-nav.navbar-collapse { + padding-right: 0; + padding-left: 0; +} + +.sidebar .sidebar-search { + padding: 15px; +} + +.sidebar ul li { + border-bottom: 1px solid #e7e7e7; +} + +.sidebar ul li a.active { + background-color: #eee; +} + +.sidebar .arrow { + float: right; +} + +.sidebar .fa.arrow:before { + content: "\f104"; +} + +.sidebar .active>a>.fa.arrow:before { + content: "\f107"; +} + +.sidebar .nav-second-level li, +.sidebar .nav-third-level li { + border-bottom: 0!important; +} + +.sidebar .nav-second-level li a { + padding-left: 37px; +} + +.sidebar .nav-third-level li a { + padding-left: 52px; +} + +#customListItem{ + border-bottom: none; + padding-top: 10px; + padding-bottom: 10px; + text-align: center; +} + +@media(min-width:768px) { + .sidebar { + z-index: 1; + position: absolute; + width: 250px; + margin-top: 0px; + } + + .navbar-top-links .dropdown-messages, + .navbar-top-links .dropdown-tasks, + .navbar-top-links .dropdown-alerts { + margin-left: auto; + } +} + +.btn-outline { + color: inherit; + background-color: transparent; + transition: all .5s; +} + +.btn-primary.btn-outline { + color: #428bca; +} + +.btn-success.btn-outline { + color: #5cb85c; +} + +.btn-info.btn-outline { + color: #5bc0de; +} + +.btn-warning.btn-outline { + color: #f0ad4e; +} + +.btn-danger.btn-outline { + color: #d9534f; +} + +.btn-primary.btn-outline:hover, +.btn-success.btn-outline:hover, +.btn-info.btn-outline:hover, +.btn-warning.btn-outline:hover, +.btn-danger.btn-outline:hover { + color: #fff; +} + +.chat { + margin: 0; + padding: 0; + list-style: none; +} + +.chat li { + margin-bottom: 10px; + padding-bottom: 5px; + border-bottom: 1px dotted #999; +} + +.chat li.left .chat-body { + margin-left: 60px; +} + +.chat li.right .chat-body { + margin-right: 60px; +} + +.chat li .chat-body p { + margin: 0; +} + +.panel .slidedown .glyphicon, +.chat .glyphicon { + margin-right: 5px; +} + +.chat-panel .panel-body { + height: 350px; + overflow-y: scroll; +} + +.login-panel { + margin-top: 25%; +} + +.flot-chart { + display: block; + height: 400px; +} + +.flot-chart-content { + width: 100%; + height: 100%; +} + +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc { + background: transparent; +} + +table.dataTable thead .sorting_asc:after { + content: "\f0de"; + float: right; + font-family: fontawesome; +} + +table.dataTable thead .sorting_desc:after { + content: "\f0dd"; + float: right; + font-family: fontawesome; +} + +table.dataTable thead .sorting:after { + content: "\f0dc"; + float: right; + font-family: fontawesome; + color: rgba(50,50,50,.5); +} + +.highlightHovering td:first-child:hover { + font-weight: bold; + cursor: pointer; +} + +.btn-circle { + width: 30px; + height: 30px; + padding: 6px 0; + border-radius: 15px; + text-align: center; + font-size: 12px; + line-height: 1.428571429; +} + +.btn-circle.btn-lg { + width: 50px; + height: 50px; + padding: 10px 16px; + border-radius: 25px; + font-size: 18px; + line-height: 1.33; +} + +.btn-circle.btn-xl { + width: 70px; + height: 70px; + padding: 10px 16px; + border-radius: 35px; + font-size: 24px; + line-height: 1.33; +} + +.show-grid [class^=col-] { + padding-top: 10px; + padding-bottom: 10px; + border: 1px solid #ddd; + background-color: #eee!important; +} + +.show-grid { + margin: 15px 0; +} + +.huge { + font-size: 40px; +} + +.panel-green { + border-color: #5cb85c; +} + +.panel-green .panel-heading { + border-color: #5cb85c; + color: #fff; + background-color: #5cb85c; +} + +.panel-green a { + color: #5cb85c; +} + +.panel-green a:hover { + color: #3d8b3d; +} + +.panel-red { + border-color: #d9534f; +} + +.panel-red .panel-heading { + border-color: #d9534f; + color: #fff; + background-color: #d9534f; +} + +.panel-red a { + color: #d9534f; +} + +.panel-red a:hover { + color: #b52b27; +} + +.panel-yellow { + border-color: #f0ad4e; +} + +.panel-yellow .panel-heading { + border-color: #f0ad4e; + color: #fff; + background-color: #f0ad4e; +} + +.panel-yellow a { + color: #f0ad4e; +} + +.panel-yellow a:hover { + color: #df8a13; +} + +.jumbotron { + margin-bottom: 15px; + margin-top: 15px; + margin-left: auto; + margin-right: auto; + padding: 0px; + text-align: center; + height: 100%; + max-width: 90%; +} + +.jumbotron .page-header-text { + background-color: #000000; + background-size: contain; + max-width: 100%; +} + +.jumbotron .page-header-text span { + color: #ffffff; + text-align: center; + height: 100%; +} + +.jumbotron-row { + margin-top: 15px; +} + +.center-row { + text-align: center; +} + +.center-row-content { + overflow: auto; + display: inline-block; + float: none; + margin: auto; +} + +.header-img img { + height: 100px; + padding: 5px; +} + +.thumbnail img { + width: 128px; + height: 128px; + padding: 15px; +} + +.form-group { + margin-bottom: 45px; +} + +#bot-table td:first-child:hover { + font-weight: bold; + cursor: pointer; +} + +#network-popUp { + display:none; + position:absolute; + top:15%; + left:5%; + margin: auto; + z-index:299; + background-color: #FFFFFF; + border-style:solid; + border-width:3px; + border-color: #5394ed; + padding:10px; + width:90%; + text-align: center; +} + +@media(min-width:768px) { + #network-popUp { + left: 25%; + width: 50%; + } +} + +#network-popUp-fields { + background: #FFFFFF; + width: 100%; +} + +#network-popUp-fields input { + width: 100%; +} + +#network-popUp-fields td { + text-align: left; +} + +form { + display: inline-block; + margin-bottom: 10px; +} + +#border{ + text-align: center !important; + font-weight: bold; +} + +#network-popUp-title { + width: 100%; + font-size:28px; + display: inherit; +} + +#network-row { + display: none; + height: 90%; +} + +#network-row.col-xs-10 { + height: 100%; +} + +#network-row.col-xs-2 { + height: 100%; +} + +#network-tab { + height: 100%; +} + +.with-bot { + max-height: 75%; + overflow-y: auto; +} + +.without-bot { + +} + +#logs-panel { + margin-top: 15px; +} + +#queues-panel { + margin-top: 15px; +} + +#queues-panel .width-80 { + width: 80%; +} + +#queues-panel .width-20 { + width: 20%; +} + +.waiting { + background-image: url('../images/waiting.gif'); + background-repeat: no-repeat; + background-size: 16px; + background-position: right 10px center; +} + +.row { + margin: 0px; +} + +.navbar-config { + margin-right: 16px; +} + +.index-link:hover { + text-decoration: none; + box-shadow: 1px 1px 16px rgba(0, 0, 0, 1); +} + +.index-link { + display: block; + box-shadow: 1px 1px 16px rgba(0, 0, 0, 0.20); +} diff --git a/intelmq/web/static/less/style.less b/intelmq/web/static/less/style.less new file mode 100644 index 000000000..dc3dd7504 --- /dev/null +++ b/intelmq/web/static/less/style.less @@ -0,0 +1,191 @@ +/*SPDX-FileCopyrightText: 2020 IntelMQ Team */ +/*SPDX-License-Identifier: AGPL-3.0-or-later*/ + +/* + * Navigation + */ +nav ul.nav.navbar-top-links li.active { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent +} + +/* + * Common elements + */ +#common-templates { + display: none; +} + +#wrapper .navbar #log-window { + background-color: black; + color: white; + display: none; + float: right; + margin: 5px 5px; + padding: 5px; + width: 500px; + height: 44px; + overflow: auto; + resize: vertical; + position: absolute; + top: 0; + right: 0; + cursor: pointer; + + &.extended { + height: auto; + max-height: 100vh; + width: auto; + cursor: unset; + overflow:scroll; + } + + [role=close] { + float: right; + cursor: pointer; + } + + .command { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; + overflow: auto; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + } + + .alert a{ + text-decoration: underline; + display: inline-block; + padding: 4px; + border: 1px solid #ccc; + border-radius: 10px; + } +} + +.fa { + font-family: FontAwesome; +} + +.control-buttons [data-url=status] { + display: none; // this button is normally hidden, used only in script; may be revealed if found useful +} + +/* + * Management page + */ +#botnet-panels > .panel[data-botnet-group] { + display: none; // initially, all other panels are hidden + &[data-botnet-group=botnet] { + display: block; + } +} + +/* + * Monitor page + */ +#botnet-panels .panel .control-buttons [data-role=control-status] { + display: none; // this info is fetched out to line above +} + +#inspect-panel { + .control-buttons { + float: right; + } + + button[data-role="clear"] { + float: right; + } + + #command-show { + display: none; + } + + textarea { + resize: vertical; + } +} + +/* + * Config page + */ + +#templates { + display: none; +} + +#side-menu { +} + +#network-container { + + .control-buttons { + float: left; + + button { + height: 25px; + + span { + top: -2px; + } + } + } + + .monitor-button { + //background-image: url('../plugins/vis.js/img/network/rightArrow.png'); + //background-size: 24%; + div a { + color: black; + } + } + + .duplicate-button { + background-image: url('../plugins/vis.js/img/network/addNodeIcon.png'); + } + + .network-right-menu { + > div { + display: block; + } + + .vis-live-toggle, .vis-physics-toggle { + border-radius: 10px; + position: absolute; + right: 560px; + top: 35px; + white-space: nowrap; + padding: 5px 0 5px 5px; + cursor: pointer; + + &.running { + //padding: 4px 0 4px 4px; + //border:1px solid green; + background-color: #00D000; + } + } + + .vis-live-toggle .icon { + background-image: url("../images/monitor.png"); + background-repeat: no-repeat; + background-size: 30%; // this is a big image + padding-left: 30px; + padding-right: 15px; + position: relative; + z-index: 20; + } + + .vis-physics-toggle { + right: 480px; + } + } +} diff --git a/intelmq/web/static/plugins/bootstrap/bootstrap.css b/intelmq/web/static/plugins/bootstrap/bootstrap.css new file mode 100644 index 000000000..9ea3db0d7 --- /dev/null +++ b/intelmq/web/static/plugins/bootstrap/bootstrap.css @@ -0,0 +1,6832 @@ +/*SPDX-FileCopyrightText: 2011-2019 Twitter, Inc.*/ +/*SPDX-License-Identifier: MIT*/ +/*Bootstrap v3.4.1 (https://getbootstrap.com/)*/ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: none; + text-decoration: underline; + -webkit-text-decoration: underline dotted; + -moz-text-decoration: underline dotted; + text-decoration: underline dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: "Glyphicons Halflings"; + src: url("../fonts/glyphicons-halflings-regular.eot"); + src: url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"), url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"), url("../fonts/glyphicons-halflings-regular.woff") format("woff"), url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"), url("../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg"); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: "Glyphicons Halflings"; + font-style: normal; + font-weight: 400; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eeeeee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: 400; + line-height: 1; + color: #777777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777777; +} +.text-primary { + color: #337ab7; +} +a.text-primary:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover, +a.text-danger:focus { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eeeeee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + list-style: none; + margin-left: -5px; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: 700; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eeeeee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: "\2014 \00A0"; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eeeeee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ""; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: "\00A0 \2014"; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.row-no-gutters { + margin-right: 0; + margin-left: 0; +} +.row-no-gutters [class*="col-"] { + padding-right: 0; + padding-left: 0; +} +.col-xs-1, +.col-sm-1, +.col-md-1, +.col-lg-1, +.col-xs-2, +.col-sm-2, +.col-md-2, +.col-lg-2, +.col-xs-3, +.col-sm-3, +.col-md-3, +.col-lg-3, +.col-xs-4, +.col-sm-4, +.col-md-4, +.col-lg-4, +.col-xs-5, +.col-sm-5, +.col-md-5, +.col-lg-5, +.col-xs-6, +.col-sm-6, +.col-md-6, +.col-lg-6, +.col-xs-7, +.col-sm-7, +.col-md-7, +.col-lg-7, +.col-xs-8, +.col-sm-8, +.col-md-8, +.col-lg-8, +.col-xs-9, +.col-sm-9, +.col-md-9, +.col-lg-9, +.col-xs-10, +.col-sm-10, +.col-md-10, +.col-lg-10, +.col-xs-11, +.col-sm-11, +.col-md-11, +.col-lg-11, +.col-xs-12, +.col-sm-12, +.col-md-12, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11, +.col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0%; +} +@media (min-width: 768px) { + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11, + .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 992px) { + .col-md-1, + .col-md-2, + .col-md-3, + .col-md-4, + .col-md-5, + .col-md-6, + .col-md-7, + .col-md-8, + .col-md-9, + .col-md-10, + .col-md-11, + .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 1200px) { + .col-lg-1, + .col-lg-2, + .col-lg-3, + .col-lg-4, + .col-lg-5, + .col-lg-6, + .col-lg-7, + .col-lg-8, + .col-lg-9, + .col-lg-10, + .col-lg-11, + .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0%; + } +} +table { + background-color: transparent; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + min-height: 0.01%; + overflow-x: auto; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: 700; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #eeeeee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: 400; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: 400; + vertical-align: middle; + cursor: pointer; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +.form-control-static { + min-height: 34px; + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 46px; + line-height: 46px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + min-height: 38px; + padding: 11px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 11px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + filter: alpha(opacity=65); + opacity: 0.65; + -webkit-box-shadow: none; + box-shadow: none; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:focus, +.btn-default.focus { + color: #333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + background-image: none; + border-color: #adadad; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333; + background-color: #d4d4d4; + border-color: #8c8c8c; +} +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:focus, +.btn-primary.focus { + color: #fff; + background-color: #286090; + border-color: #122b40; +} +.btn-primary:hover { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #286090; + background-image: none; + border-color: #204d74; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #fff; + background-color: #204d74; + border-color: #122b40; +} +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #337ab7; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus, +.btn-success.focus { + color: #fff; + background-color: #449d44; + border-color: #255625; +} +.btn-success:hover { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + background-image: none; + border-color: #398439; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #fff; + background-color: #398439; + border-color: #255625; +} +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus, +.btn-info.focus { + color: #fff; + background-color: #31b0d5; + border-color: #1b6d85; +} +.btn-info:hover { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + background-image: none; + border-color: #269abc; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #fff; + background-color: #269abc; + border-color: #1b6d85; +} +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:focus, +.btn-warning.focus { + color: #fff; + background-color: #ec971f; + border-color: #985f0d; +} +.btn-warning:hover { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + background-image: none; + border-color: #d58512; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #fff; + background-color: #d58512; + border-color: #985f0d; +} +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:focus, +.btn-danger.focus { + color: #fff; + background-color: #c9302c; + border-color: #761c19; +} +.btn-danger:hover { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + background-image: none; + border-color: #ac2925; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #fff; + background-color: #ac2925; + border-color: #761c19; +} +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: 400; + color: #337ab7; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; + -webkit-transition-duration: 0.35s; + -o-transition-duration: 0.35s; + transition-duration: 0.35s; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: 400; + line-height: 1.42857143; + color: #333333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #337ab7; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group .form-control:focus { + z-index: 3; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: 400; + line-height: 1; + color: #555555; + text-align: center; + background-color: #eeeeee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} +.nav > li.disabled > a { + color: #777777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eeeeee; + border-color: #337ab7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #337ab7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-right: 15px; + margin-top: 8px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + padding: 10px 15px; + margin-right: -15px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 8px; + margin-bottom: 8px; +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #777777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eeeeee; + border-color: #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: 0.2em 0.6em 0.3em; + font-size: 75%; + font-weight: 700; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25em; +} +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: middle; + background-color: #777777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #337ab7; + background-color: #fff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eeeeee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + padding-right: 15px; + padding-left: 15px; + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border 0.2s ease-in-out; + -o-transition: border 0.2s ease-in-out; + transition: border 0.2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7; +} +.thumbnail .caption { + padding: 9px; + color: #333333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 0.6s ease; + -o-transition: width 0.6s ease; + transition: width 0.6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777777; + cursor: not-allowed; + background-color: #eeeeee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +a.list-group-item, +button.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success, +button.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info, +button.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-right: 15px; + padding-left: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #337ab7; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #337ab7; +} +.panel-primary > .panel-heading .badge { + color: #337ab7; + background-color: #fff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #337ab7; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: 0.2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: 0.5; +} +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out, -o-transform 0.3s ease-out; +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + outline: 0; +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: 0.5; +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: 400; + line-height: 1.42857143; + line-break: auto; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + font-size: 12px; + filter: alpha(opacity=0); + opacity: 0; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: 0.9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + right: 5px; + bottom: 0; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: 400; + line-height: 1.42857143; + line-break: auto; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + font-size: 14px; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: 0.6s ease-in-out left; + -o-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform 0.6s ease-in-out; + -o-transition: -o-transform 0.6s ease-in-out; + transition: -webkit-transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out, -o-transform 0.6s ease-in-out; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + left: 0; + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + left: 0; + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + left: 0; + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + background-color: rgba(0, 0, 0, 0); + filter: alpha(opacity=50); + opacity: 0.5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + outline: 0; + filter: alpha(opacity=90); + opacity: 0.9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + margin-top: -10px; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + font-family: serif; + line-height: 1; +} +.carousel-control .icon-prev:before { + content: "\2039"; +} +.carousel-control .icon-next:before { + content: "\203a"; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -10px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -10px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -10px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-header:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ \ No newline at end of file diff --git a/intelmq/web/static/plugins/bootstrap/bootstrap.js b/intelmq/web/static/plugins/bootstrap/bootstrap.js new file mode 100644 index 000000000..0e5012093 --- /dev/null +++ b/intelmq/web/static/plugins/bootstrap/bootstrap.js @@ -0,0 +1,2578 @@ +/*SPDX-FileCopyrightText: 2011-2019 Twitter, Inc.*/ +/*SPDX-License-Identifier: MIT*/ +/*Bootstrap v3.4.1 (https://getbootstrap.com/)*/ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#transitions + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: https://modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // https://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#alerts + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.4.1' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + selector = selector === '#' ? [] : selector + var $parent = $(document).find(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#buttons + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.4.1' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d).prop(d, true) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d).prop(d, false) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') + } + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target).closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { + // Prevent double click on radios, and the double selections (so cancellation) on checkboxes + e.preventDefault() + // The target component still receive the focus + if ($btn.is('input,button')) $btn.trigger('focus') + else $btn.find('input:visible,button:visible').first().trigger('focus') + } + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#carousel + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.4.1' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + if (typeof $next === 'object' && $next.length) { + $next[0].offsetWidth // force reflow + } + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var $this = $(this) + var href = $this.attr('href') + if (href) { + href = href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + } + + var target = $this.attr('data-target') || href + var $target = $(document).find(target) + + if (!$target.hasClass('carousel')) return + + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#collapse + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + +/* jshint latedef: false */ + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.4.1' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(document).find(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(document).find(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.4.1' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector !== '#' ? $(document).find(selector) : null + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger($.Event('shown.bs.dropdown', relatedTarget)) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#modals + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + this.fixedContent = '.navbar-fixed-top, .navbar-fixed-bottom' + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.4.1' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (document !== e.target && + this.$element[0] !== e.target && + !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + var scrollbarWidth = this.scrollbarWidth + if (this.bodyIsOverflowing) { + this.$body.css('padding-right', bodyPad + scrollbarWidth) + $(this.fixedContent).each(function (index, element) { + var actualPadding = element.style.paddingRight + var calculatedPadding = $(element).css('padding-right') + $(element) + .data('padding-right', actualPadding) + .css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px') + }) + } + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + $(this.fixedContent).each(function (index, element) { + var padding = $(element).data('padding-right') + $(element).removeData('padding-right') + element.style.paddingRight = padding ? padding : '' + }) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var target = $this.attr('data-target') || + (href && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + + var $target = $(document).find(target) + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + ++function ($) { + 'use strict'; + + var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn'] + + var uriAttrs = [ + 'background', + 'cite', + 'href', + 'itemtype', + 'longdesc', + 'poster', + 'src', + 'xlink:href' + ] + + var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i + + var DefaultWhitelist = { + // Global attributes allowed on any supplied element below. + '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], + a: ['target', 'href', 'title', 'rel'], + area: [], + b: [], + br: [], + col: [], + code: [], + div: [], + em: [], + hr: [], + h1: [], + h2: [], + h3: [], + h4: [], + h5: [], + h6: [], + i: [], + img: ['src', 'alt', 'title', 'width', 'height'], + li: [], + ol: [], + p: [], + pre: [], + s: [], + small: [], + span: [], + sub: [], + sup: [], + strong: [], + u: [], + ul: [] + } + + /** + * A pattern that recognizes a commonly useful subset of URLs that are safe. + * + * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts + */ + var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi + + /** + * A pattern that matches safe data URLs. Only matches image, video and audio types. + * + * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts + */ + var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i + + function allowedAttribute(attr, allowedAttributeList) { + var attrName = attr.nodeName.toLowerCase() + + if ($.inArray(attrName, allowedAttributeList) !== -1) { + if ($.inArray(attrName, uriAttrs) !== -1) { + return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN)) + } + + return true + } + + var regExp = $(allowedAttributeList).filter(function (index, value) { + return value instanceof RegExp + }) + + // Check if a regular expression validates the attribute. + for (var i = 0, l = regExp.length; i < l; i++) { + if (attrName.match(regExp[i])) { + return true + } + } + + return false + } + + function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) { + if (unsafeHtml.length === 0) { + return unsafeHtml + } + + if (sanitizeFn && typeof sanitizeFn === 'function') { + return sanitizeFn(unsafeHtml) + } + + // IE 8 and below don't support createHTMLDocument + if (!document.implementation || !document.implementation.createHTMLDocument) { + return unsafeHtml + } + + var createdDocument = document.implementation.createHTMLDocument('sanitization') + createdDocument.body.innerHTML = unsafeHtml + + var whitelistKeys = $.map(whiteList, function (el, i) { return i }) + var elements = $(createdDocument.body).find('*') + + for (var i = 0, len = elements.length; i < len; i++) { + var el = elements[i] + var elName = el.nodeName.toLowerCase() + + if ($.inArray(elName, whitelistKeys) === -1) { + el.parentNode.removeChild(el) + + continue + } + + var attributeList = $.map(el.attributes, function (el) { return el }) + var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []) + + for (var j = 0, len2 = attributeList.length; j < len2; j++) { + if (!allowedAttribute(attributeList[j], whitelistedAttributes)) { + el.removeAttribute(attributeList[j].nodeName) + } + } + } + + return createdDocument.body.innerHTML + } + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.4.1' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + }, + sanitize : true, + sanitizeFn : null, + whiteList : DefaultWhitelist + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $(document).find($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } + + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') + } + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + var dataAttributes = this.$element.data() + + for (var dataAttr in dataAttributes) { + if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) { + delete dataAttributes[dataAttr] + } + } + + options = $.extend({}, this.getDefaults(), dataAttributes, options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } + } + + if (options.sanitize) { + options.template = sanitizeHtml(options.template, options.whiteList, options.sanitizeFn) + } + + return options + } + + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true + } + + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return + } + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true + } + + return false + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false + } + + if (self.isInStateTrue()) return + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo($(document).find(this.options.container)) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) + + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) + } + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + if (this.options.html) { + if (this.options.sanitize) { + title = sanitizeHtml(title, this.options.whiteList, this.options.sanitizeFn) + } + + $tip.find('.tooltip-inner').html(title) + } else { + $tip.find('.tooltip-inner').text(title) + } + + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + } + callback && callback() + } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element + + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var isSvg = window.SVGElement && el instanceof window.SVGElement + // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3. + // See https://github.com/twbs/bootstrap/issues/20280 + var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset()) + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) + } + + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } + + return delta + } + + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } + + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') + } + } + return this.$tip + } + + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } + + Tooltip.prototype.enable = function () { + this.enabled = true + } + + Tooltip.prototype.disable = function () { + this.enabled = false + } + + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } + + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + } + + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + that.$element = null + }) + } + + Tooltip.prototype.sanitizeHtml = function (unsafeHtml) { + return sanitizeHtml(unsafeHtml, this.options.whiteList, this.options.sanitizeFn) + } + + // TOOLTIP PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tooltip + + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip + + + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: popover.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#popovers + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') + + Popover.VERSION = '3.4.1' + + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }) + + + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS + } + + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() + + if (this.options.html) { + var typeContent = typeof content + + if (this.options.sanitize) { + title = this.sanitizeHtml(title) + + if (typeContent === 'string') { + content = this.sanitizeHtml(content) + } + } + + $tip.find('.popover-title').html(title) + $tip.find('.popover-content').children().detach().end()[ + typeContent === 'string' ? 'html' : 'append' + ](content) + } else { + $tip.find('.popover-title').text(title) + $tip.find('.popover-content').children().detach().end().text(content) + } + + $tip.removeClass('fade top bottom left right in') + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } + + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } + + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options + + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) + } + + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } + + + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + this.$body = $(document.body) + this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.4.1' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var that = this + var offsetMethod = 'offset' + var offsetBase = 0 + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + that.offsets.push(this[0]) + that.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) + } + + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) + && this.activate(targets[i]) + } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') + } + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this + } + + + // SCROLLSPY DATA-API + // ================== + + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tab.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#tabs + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + // jscs:disable requireDollarBeforejQueryAssignment + this.element = $(element) + // jscs:enable requireDollarBeforejQueryAssignment + } + + Tab.VERSION = '3.4.1' + + Tab.TRANSITION_DURATION = 150 + + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + if ($this.parent('li').hasClass('active')) return + + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(document).find(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if (element.parent('.dropdown-menu').length) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + } + + callback && callback() + } + + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') + } + + + // TAB PLUGIN DEFINITION + // ===================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tab + + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab + + + // TAB NO CONFLICT + // =============== + + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this + } + + + // TAB DATA-API + // ============ + + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } + + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: affix.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#affix + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + var target = this.options.target === Affix.DEFAULTS.target ? $(this.options.target) : $(document).find(this.options.target) + + this.$target = target + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = null + this.unpin = null + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.4.1' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = Math.max($(document).height(), $(document.body).height()) + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); diff --git a/intelmq/web/static/plugins/bootstrap/bootstrap.min.css b/intelmq/web/static/plugins/bootstrap/bootstrap.min.css new file mode 100644 index 000000000..cc44e3d3a --- /dev/null +++ b/intelmq/web/static/plugins/bootstrap/bootstrap.min.css @@ -0,0 +1,8 @@ +/*SPDX-FileCopyrightText: 2011-2019 Twitter, Inc.*/ +/*SPDX-License-Identifier: MIT*/ +/*! + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:"Glyphicons Halflings";src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format("embedded-opentype"),url(../fonts/glyphicons-halflings-regular.woff2) format("woff2"),url(../fonts/glyphicons-halflings-regular.woff) format("woff"),url(../fonts/glyphicons-halflings-regular.ttf) format("truetype"),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:"Glyphicons Halflings";font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:""}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:"\00A0 \2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*=col-]{padding-right:0;padding-left:0}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],.input-group-sm input[type=time],input[type=date].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm,input[type=time].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],.input-group-lg input[type=time],input[type=date].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg,input[type=time].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:.65;-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;background-image:none;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;background-image:none;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;background-image:none;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;background-image:none;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;background-image:none;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;background-image:none;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out,-o-transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out,-o-transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);left:0}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);left:0}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/intelmq/web/static/plugins/bootstrap/bootstrap.min.js b/intelmq/web/static/plugins/bootstrap/bootstrap.min.js new file mode 100644 index 000000000..e7fad8e81 --- /dev/null +++ b/intelmq/web/static/plugins/bootstrap/bootstrap.min.js @@ -0,0 +1,8 @@ +/*SPDX-FileCopyrightText: 2011-2019 Twitter, Inc.*/ +/*SPDX-License-Identifier: MIT*/ +/*! + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");!function(t){"use strict";var e=jQuery.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1==e[0]&&9==e[1]&&e[2]<1||3this.$items.length-1||t<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){e.to(t)}):i==t?this.pause().cycle():this.slide(idocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&t?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!t?this.scrollbarWidth:""})},s.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},s.prototype.checkScrollbar=function(){var t=window.innerWidth;if(!t){var e=document.documentElement.getBoundingClientRect();t=e.right-Math.abs(e.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0},sanitize:!0,sanitizeFn:null,whiteList:t},m.prototype.init=function(t,e,i){if(this.enabled=!0,this.type=t,this.$element=g(e),this.options=this.getOptions(i),this.$viewport=this.options.viewport&&g(document).find(g.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var o=this.options.trigger.split(" "),n=o.length;n--;){var s=o[n];if("click"==s)this.$element.on("click."+this.type,this.options.selector,g.proxy(this.toggle,this));else if("manual"!=s){var a="hover"==s?"mouseenter":"focusin",r="hover"==s?"mouseleave":"focusout";this.$element.on(a+"."+this.type,this.options.selector,g.proxy(this.enter,this)),this.$element.on(r+"."+this.type,this.options.selector,g.proxy(this.leave,this))}}this.options.selector?this._options=g.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},m.prototype.getDefaults=function(){return m.DEFAULTS},m.prototype.getOptions=function(t){var e=this.$element.data();for(var i in e)e.hasOwnProperty(i)&&-1!==g.inArray(i,o)&&delete e[i];return(t=g.extend({},this.getDefaults(),e,t)).delay&&"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),t.sanitize&&(t.template=n(t.template,t.whiteList,t.sanitizeFn)),t},m.prototype.getDelegateOptions=function(){var i={},o=this.getDefaults();return this._options&&g.each(this._options,function(t,e){o[t]!=e&&(i[t]=e)}),i},m.prototype.enter=function(t){var e=t instanceof this.constructor?t:g(t.currentTarget).data("bs."+this.type);if(e||(e=new this.constructor(t.currentTarget,this.getDelegateOptions()),g(t.currentTarget).data("bs."+this.type,e)),t instanceof g.Event&&(e.inState["focusin"==t.type?"focus":"hover"]=!0),e.tip().hasClass("in")||"in"==e.hoverState)e.hoverState="in";else{if(clearTimeout(e.timeout),e.hoverState="in",!e.options.delay||!e.options.delay.show)return e.show();e.timeout=setTimeout(function(){"in"==e.hoverState&&e.show()},e.options.delay.show)}},m.prototype.isInStateTrue=function(){for(var t in this.inState)if(this.inState[t])return!0;return!1},m.prototype.leave=function(t){var e=t instanceof this.constructor?t:g(t.currentTarget).data("bs."+this.type);if(e||(e=new this.constructor(t.currentTarget,this.getDelegateOptions()),g(t.currentTarget).data("bs."+this.type,e)),t instanceof g.Event&&(e.inState["focusout"==t.type?"focus":"hover"]=!1),!e.isInStateTrue()){if(clearTimeout(e.timeout),e.hoverState="out",!e.options.delay||!e.options.delay.hide)return e.hide();e.timeout=setTimeout(function(){"out"==e.hoverState&&e.hide()},e.options.delay.hide)}},m.prototype.show=function(){var t=g.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(t);var e=g.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(t.isDefaultPrevented()||!e)return;var i=this,o=this.tip(),n=this.getUID(this.type);this.setContent(),o.attr("id",n),this.$element.attr("aria-describedby",n),this.options.animation&&o.addClass("fade");var s="function"==typeof this.options.placement?this.options.placement.call(this,o[0],this.$element[0]):this.options.placement,a=/\s?auto?\s?/i,r=a.test(s);r&&(s=s.replace(a,"")||"top"),o.detach().css({top:0,left:0,display:"block"}).addClass(s).data("bs."+this.type,this),this.options.container?o.appendTo(g(document).find(this.options.container)):o.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var l=this.getPosition(),h=o[0].offsetWidth,d=o[0].offsetHeight;if(r){var p=s,c=this.getPosition(this.$viewport);s="bottom"==s&&l.bottom+d>c.bottom?"top":"top"==s&&l.top-dc.width?"left":"left"==s&&l.left-ha.top+a.height&&(n.top=a.top+a.height-l)}else{var h=e.left-s,d=e.left+s+i;ha.right&&(n.left=a.left+a.width-d)}return n},m.prototype.getTitle=function(){var t=this.$element,e=this.options;return t.attr("data-original-title")||("function"==typeof e.title?e.title.call(t[0]):e.title)},m.prototype.getUID=function(t){for(;t+=~~(1e6*Math.random()),document.getElementById(t););return t},m.prototype.tip=function(){if(!this.$tip&&(this.$tip=g(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},m.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},m.prototype.enable=function(){this.enabled=!0},m.prototype.disable=function(){this.enabled=!1},m.prototype.toggleEnabled=function(){this.enabled=!this.enabled},m.prototype.toggle=function(t){var e=this;t&&((e=g(t.currentTarget).data("bs."+this.type))||(e=new this.constructor(t.currentTarget,this.getDelegateOptions()),g(t.currentTarget).data("bs."+this.type,e))),t?(e.inState.click=!e.inState.click,e.isInStateTrue()?e.enter(e):e.leave(e)):e.tip().hasClass("in")?e.leave(e):e.enter(e)},m.prototype.destroy=function(){var t=this;clearTimeout(this.timeout),this.hide(function(){t.$element.off("."+t.type).removeData("bs."+t.type),t.$tip&&t.$tip.detach(),t.$tip=null,t.$arrow=null,t.$viewport=null,t.$element=null})},m.prototype.sanitizeHtml=function(t){return n(t,this.options.whiteList,this.options.sanitizeFn)};var e=g.fn.tooltip;g.fn.tooltip=function i(o){return this.each(function(){var t=g(this),e=t.data("bs.tooltip"),i="object"==typeof o&&o;!e&&/destroy|hide/.test(o)||(e||t.data("bs.tooltip",e=new m(this,i)),"string"==typeof o&&e[o]())})},g.fn.tooltip.Constructor=m,g.fn.tooltip.noConflict=function(){return g.fn.tooltip=e,this}}(jQuery),function(n){"use strict";var s=function(t,e){this.init("popover",t,e)};if(!n.fn.tooltip)throw new Error("Popover requires tooltip.js");s.VERSION="3.4.1",s.DEFAULTS=n.extend({},n.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),((s.prototype=n.extend({},n.fn.tooltip.Constructor.prototype)).constructor=s).prototype.getDefaults=function(){return s.DEFAULTS},s.prototype.setContent=function(){var t=this.tip(),e=this.getTitle(),i=this.getContent();if(this.options.html){var o=typeof i;this.options.sanitize&&(e=this.sanitizeHtml(e),"string"===o&&(i=this.sanitizeHtml(i))),t.find(".popover-title").html(e),t.find(".popover-content").children().detach().end()["string"===o?"html":"append"](i)}else t.find(".popover-title").text(e),t.find(".popover-content").children().detach().end().text(i);t.removeClass("fade top bottom left right in"),t.find(".popover-title").html()||t.find(".popover-title").hide()},s.prototype.hasContent=function(){return this.getTitle()||this.getContent()},s.prototype.getContent=function(){var t=this.$element,e=this.options;return t.attr("data-content")||("function"==typeof e.content?e.content.call(t[0]):e.content)},s.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var t=n.fn.popover;n.fn.popover=function e(o){return this.each(function(){var t=n(this),e=t.data("bs.popover"),i="object"==typeof o&&o;!e&&/destroy|hide/.test(o)||(e||t.data("bs.popover",e=new s(this,i)),"string"==typeof o&&e[o]())})},n.fn.popover.Constructor=s,n.fn.popover.noConflict=function(){return n.fn.popover=t,this}}(jQuery),function(s){"use strict";function n(t,e){this.$body=s(document.body),this.$scrollElement=s(t).is(document.body)?s(window):s(t),this.options=s.extend({},n.DEFAULTS,e),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",s.proxy(this.process,this)),this.refresh(),this.process()}function e(o){return this.each(function(){var t=s(this),e=t.data("bs.scrollspy"),i="object"==typeof o&&o;e||t.data("bs.scrollspy",e=new n(this,i)),"string"==typeof o&&e[o]()})}n.VERSION="3.4.1",n.DEFAULTS={offset:10},n.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},n.prototype.refresh=function(){var t=this,o="offset",n=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),s.isWindow(this.$scrollElement[0])||(o="position",n=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=s(this),e=t.data("target")||t.attr("href"),i=/^#./.test(e)&&s(e);return i&&i.length&&i.is(":visible")&&[[i[o]().top+n,e]]||null}).sort(function(t,e){return t[0]-e[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},n.prototype.process=function(){var t,e=this.$scrollElement.scrollTop()+this.options.offset,i=this.getScrollHeight(),o=this.options.offset+i-this.$scrollElement.height(),n=this.offsets,s=this.targets,a=this.activeTarget;if(this.scrollHeight!=i&&this.refresh(),o<=e)return a!=(t=s[s.length-1])&&this.activate(t);if(a&&e=n[t]&&(n[t+1]===undefined||e .active"),n=i&&r.support.transition&&(o.length&&o.hasClass("fade")||!!e.find("> .fade").length);function s(){o.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),t.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),n?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu").length&&t.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),i&&i()}o.length&&n?o.one("bsTransitionEnd",s).emulateTransitionEnd(a.TRANSITION_DURATION):s(),o.removeClass("in")};var t=r.fn.tab;r.fn.tab=e,r.fn.tab.Constructor=a,r.fn.tab.noConflict=function(){return r.fn.tab=t,this};var i=function(t){t.preventDefault(),e.call(r(this),"show")};r(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',i).on("click.bs.tab.data-api",'[data-toggle="pill"]',i)}(jQuery),function(l){"use strict";var h=function(t,e){this.options=l.extend({},h.DEFAULTS,e);var i=this.options.target===h.DEFAULTS.target?l(this.options.target):l(document).find(this.options.target);this.$target=i.on("scroll.bs.affix.data-api",l.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",l.proxy(this.checkPositionWithEventLoop,this)),this.$element=l(t),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};function i(o){return this.each(function(){var t=l(this),e=t.data("bs.affix"),i="object"==typeof o&&o;e||t.data("bs.affix",e=new h(this,i)),"string"==typeof o&&e[o]()})}h.VERSION="3.4.1",h.RESET="affix affix-top affix-bottom",h.DEFAULTS={offset:0,target:window},h.prototype.getState=function(t,e,i,o){var n=this.$target.scrollTop(),s=this.$element.offset(),a=this.$target.height();if(null!=i&&"top"==this.affixed)return n<'col-sm-6'f>r>" + "t" + "<'row'<'col-sm-6'i><'col-sm-6'p>>", + "oLanguage": { + "sLengthMenu": "_MENU_ records per page" + } +}); + + +/* Default class modification */ +$.extend($.fn.dataTableExt.oStdClasses, { + "sWrapper": "dataTables_wrapper form-inline", + "sFilterInput": "form-control input-sm", + "sLengthSelect": "form-control input-sm" +}); + +// In 1.10 we use the pagination renderers to draw the Bootstrap paging, +// rather than custom plug-in +if ($.fn.dataTable.Api) { + $.fn.dataTable.defaults.renderer = 'bootstrap'; + $.fn.dataTable.ext.renderer.pageButton.bootstrap = function(settings, host, idx, buttons, page, pages) { + var api = new $.fn.dataTable.Api(settings); + var classes = settings.oClasses; + var lang = settings.oLanguage.oPaginate; + var btnDisplay, btnClass; + + var attach = function(container, buttons) { + var i, ien, node, button; + var clickHandler = function(e) { + e.preventDefault(); + if (e.data.action !== 'ellipsis') { + api.page(e.data.action).draw(false); + } + }; + + for (i = 0, ien = buttons.length; i < ien; i++) { + button = buttons[i]; + + if ($.isArray(button)) { + attach(container, button); + } else { + btnDisplay = ''; + btnClass = ''; + + switch (button) { + case 'ellipsis': + btnDisplay = '…'; + btnClass = 'disabled'; + break; + + case 'first': + btnDisplay = lang.sFirst; + btnClass = button + (page > 0 ? + '' : ' disabled'); + break; + + case 'previous': + btnDisplay = lang.sPrevious; + btnClass = button + (page > 0 ? + '' : ' disabled'); + break; + + case 'next': + btnDisplay = lang.sNext; + btnClass = button + (page < pages - 1 ? + '' : ' disabled'); + break; + + case 'last': + btnDisplay = lang.sLast; + btnClass = button + (page < pages - 1 ? + '' : ' disabled'); + break; + + default: + btnDisplay = button + 1; + btnClass = page === button ? + 'active' : ''; + break; + } + + if (btnDisplay) { + node = $('
  • ', { + 'class': classes.sPageButton + ' ' + btnClass, + 'aria-controls': settings.sTableId, + 'tabindex': settings.iTabIndex, + 'id': idx === 0 && typeof button === 'string' ? settings.sTableId + '_' + button : null + }) + .append($('', { + 'href': '#' + }) + .html(btnDisplay) + ) + .appendTo(container); + + settings.oApi._fnBindAction( + node, { + action: button + }, clickHandler + ); + } + } + } + }; + + attach( + $(host).empty().html('
      ').children('ul'), + buttons + ); + } +} else { + // Integration for 1.9- + $.fn.dataTable.defaults.sPaginationType = 'bootstrap'; + + /* API method to get paging information */ + $.fn.dataTableExt.oApi.fnPagingInfo = function(oSettings) { + return { + "iStart": oSettings._iDisplayStart, + "iEnd": oSettings.fnDisplayEnd(), + "iLength": oSettings._iDisplayLength, + "iTotal": oSettings.fnRecordsTotal(), + "iFilteredTotal": oSettings.fnRecordsDisplay(), + "iPage": oSettings._iDisplayLength === -1 ? 0 : Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength), + "iTotalPages": oSettings._iDisplayLength === -1 ? 0 : Math.ceil(oSettings.fnRecordsDisplay() / oSettings._iDisplayLength) + }; + }; + + /* Bootstrap style pagination control */ + $.extend($.fn.dataTableExt.oPagination, { + "bootstrap": { + "fnInit": function(oSettings, nPaging, fnDraw) { + var oLang = oSettings.oLanguage.oPaginate; + var fnClickHandler = function(e) { + e.preventDefault(); + if (oSettings.oApi._fnPageChange(oSettings, e.data.action)) { + fnDraw(oSettings); + } + }; + + $(nPaging).append( + '' + ); + var els = $('a', nPaging); + $(els[0]).bind('click.DT', { + action: "previous" + }, fnClickHandler); + $(els[1]).bind('click.DT', { + action: "next" + }, fnClickHandler); + }, + + "fnUpdate": function(oSettings, fnDraw) { + var iListLength = 5; + var oPaging = oSettings.oInstance.fnPagingInfo(); + var an = oSettings.aanFeatures.p; + var i, ien, j, sClass, iStart, iEnd, iHalf = Math.floor(iListLength / 2); + + if (oPaging.iTotalPages < iListLength) { + iStart = 1; + iEnd = oPaging.iTotalPages; + } else if (oPaging.iPage <= iHalf) { + iStart = 1; + iEnd = iListLength; + } else if (oPaging.iPage >= (oPaging.iTotalPages - iHalf)) { + iStart = oPaging.iTotalPages - iListLength + 1; + iEnd = oPaging.iTotalPages; + } else { + iStart = oPaging.iPage - iHalf + 1; + iEnd = iStart + iListLength - 1; + } + + for (i = 0, ien = an.length; i < ien; i++) { + // Remove the middle elements + $('li:gt(0)', an[i]).filter(':not(:last)').remove(); + + // Add the new list items and their event handlers + for (j = iStart; j <= iEnd; j++) { + sClass = (j == oPaging.iPage + 1) ? 'class="active"' : ''; + $('
    • ' + j + '
    • ') + .insertBefore($('li:last', an[i])[0]) + .bind('click', function(e) { + e.preventDefault(); + oSettings._iDisplayStart = (parseInt($('a', this).text(), 10) - 1) * oPaging.iLength; + fnDraw(oSettings); + }); + } + + // Add / remove disabled classes from the static elements + if (oPaging.iPage === 0) { + $('li:first', an[i]).addClass('disabled'); + } else { + $('li:first', an[i]).removeClass('disabled'); + } + + if (oPaging.iPage === oPaging.iTotalPages - 1 || oPaging.iTotalPages === 0) { + $('li:last', an[i]).addClass('disabled'); + } else { + $('li:last', an[i]).removeClass('disabled'); + } + } + } + } + }); +} + + +/* + * TableTools Bootstrap compatibility + * Required TableTools 2.1+ + */ +if ($.fn.DataTable.TableTools) { + // Set the classes that TableTools uses to something suitable for Bootstrap + $.extend(true, $.fn.DataTable.TableTools.classes, { + "container": "DTTT btn-group", + "buttons": { + "normal": "btn btn-default", + "disabled": "disabled" + }, + "collection": { + "container": "DTTT_dropdown dropdown-menu", + "buttons": { + "normal": "", + "disabled": "disabled" + } + }, + "print": { + "info": "DTTT_print_info modal" + }, + "select": { + "row": "active" + } + }); + + // Have the collection use a bootstrap compatible dropdown + $.extend(true, $.fn.DataTable.TableTools.DEFAULTS.oTags, { + "collection": { + "container": "ul", + "button": "li", + "liner": "a" + } + }); +} diff --git a/intelmq/web/static/plugins/dataTables/jquery.dataTables.js b/intelmq/web/static/plugins/dataTables/jquery.dataTables.js new file mode 100644 index 000000000..854728d92 --- /dev/null +++ b/intelmq/web/static/plugins/dataTables/jquery.dataTables.js @@ -0,0 +1,15246 @@ +/*SPDX-FileCopyrightText: 2008-2017 SpryMedia Ltd.*/ +/*SPDX-License-Identifier: MIT*/ + +/*! DataTables 1.10.16 + * ©2008-2017 SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary DataTables + * @description Paginate, search and order HTML tables + * @version 1.10.16 + * @file jquery.dataTables.js + * @author SpryMedia Ltd + * @contact www.datatables.net + * @copyright Copyright 2008-2017 SpryMedia Ltd. + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ + +/*jslint evil: true, undef: true, browser: true */ +/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ + +(function( factory ) { + "use strict"; + + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window + require('jquery') : + require('jquery')( root ); + } + + return factory( $, root, root.document ); + }; + } + else { + // Browser + factory( jQuery, window, document ); + } +} +(function( $, window, document, undefined ) { + "use strict"; + + /** + * DataTables is a plug-in for the jQuery Javascript library. It is a highly + * flexible tool, based upon the foundations of progressive enhancement, + * which will add advanced interaction controls to any HTML table. For a + * full list of features please refer to + * [DataTables.net](href="http://datatables.net). + * + * Note that the `DataTable` object is not a global variable but is aliased + * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may + * be accessed. + * + * @class + * @param {object} [init={}] Configuration object for DataTables. Options + * are defined by {@link DataTable.defaults} + * @requires jQuery 1.7+ + * + * @example + * // Basic initialisation + * $(document).ready( function { + * $('#example').dataTable(); + * } ); + * + * @example + * // Initialisation with configuration options - in this case, disable + * // pagination and sorting. + * $(document).ready( function { + * $('#example').dataTable( { + * "paginate": false, + * "sort": false + * } ); + * } ); + */ + var DataTable = function ( options ) + { + /** + * Perform a jQuery selector action on the table's TR elements (from the tbody) and + * return the resulting jQuery object. + * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on + * @param {object} [oOpts] Optional parameters for modifying the rows to be included + * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter + * criterion ("applied") or all TR elements (i.e. no filter). + * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. + * Can be either 'current', whereby the current sorting of the table is used, or + * 'original' whereby the original order the data was read into the table is used. + * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page + * ("current") or not ("all"). If 'current' is given, then order is assumed to be + * 'current' and filter is 'applied', regardless of what they might be given as. + * @returns {object} jQuery object, filtered by the given selector. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Highlight every second row + * oTable.$('tr:odd').css('backgroundColor', 'blue'); + * } ); + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Filter to rows with 'Webkit' in them, add a background colour and then + * // remove the filter, thus highlighting the 'Webkit' rows only. + * oTable.fnFilter('Webkit'); + * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); + * oTable.fnFilter(''); + * } ); + */ + this.$ = function ( sSelector, oOpts ) + { + return this.api(true).$( sSelector, oOpts ); + }; + + + /** + * Almost identical to $ in operation, but in this case returns the data for the matched + * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes + * rather than any descendants, so the data can be obtained for the row/cell. If matching + * rows are found, the data returned is the original data array/object that was used to + * create the row (or a generated array if from a DOM source). + * + * This method is often useful in-combination with $ where both functions are given the + * same parameters and the array indexes will match identically. + * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on + * @param {object} [oOpts] Optional parameters for modifying the rows to be included + * @param {string} [oOpts.filter=none] Select elements that meet the current filter + * criterion ("applied") or all elements (i.e. no filter). + * @param {string} [oOpts.order=current] Order of the data in the processed array. + * Can be either 'current', whereby the current sorting of the table is used, or + * 'original' whereby the original order the data was read into the table is used. + * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page + * ("current") or not ("all"). If 'current' is given, then order is assumed to be + * 'current' and filter is 'applied', regardless of what they might be given as. + * @returns {array} Data for the matched elements. If any elements, as a result of the + * selector, were not TR, TD or TH elements in the DataTable, they will have a null + * entry in the array. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Get the data from the first row in the table + * var data = oTable._('tr:first'); + * + * // Do something useful with the data + * alert( "First cell is: "+data[0] ); + * } ); + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Filter to 'Webkit' and get all data for + * oTable.fnFilter('Webkit'); + * var data = oTable._('tr', {"search": "applied"}); + * + * // Do something with the data + * alert( data.length+" rows matched the search" ); + * } ); + */ + this._ = function ( sSelector, oOpts ) + { + return this.api(true).rows( sSelector, oOpts ).data(); + }; + + + /** + * Create a DataTables Api instance, with the currently selected tables for + * the Api's context. + * @param {boolean} [traditional=false] Set the API instance's context to be + * only the table referred to by the `DataTable.ext.iApiIndex` option, as was + * used in the API presented by DataTables 1.9- (i.e. the traditional mode), + * or if all tables captured in the jQuery object should be used. + * @return {DataTables.Api} + */ + this.api = function ( traditional ) + { + return traditional ? + new _Api( + _fnSettingsFromNode( this[ _ext.iApiIndex ] ) + ) : + new _Api( this ); + }; + + + /** + * Add a single new row or multiple rows of data to the table. Please note + * that this is suitable for client-side processing only - if you are using + * server-side processing (i.e. "bServerSide": true), then to add data, you + * must add it to the data source, i.e. the server-side, through an Ajax call. + * @param {array|object} data The data to be added to the table. This can be: + *
        + *
      • 1D array of data - add a single row with the data provided
      • + *
      • 2D array of arrays - add multiple rows in a single call
      • + *
      • object - data object when using mData
      • + *
      • array of objects - multiple data objects when using mData
      • + *
      + * @param {bool} [redraw=true] redraw the table or not + * @returns {array} An array of integers, representing the list of indexes in + * aoData ({@link DataTable.models.oSettings}) that have been added to + * the table. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * // Global var for counter + * var giCount = 2; + * + * $(document).ready(function() { + * $('#example').dataTable(); + * } ); + * + * function fnClickAddRow() { + * $('#example').dataTable().fnAddData( [ + * giCount+".1", + * giCount+".2", + * giCount+".3", + * giCount+".4" ] + * ); + * + * giCount++; + * } + */ + this.fnAddData = function( data, redraw ) + { + var api = this.api( true ); + + /* Check if we want to add multiple rows or not */ + var rows = $.isArray(data) && ( $.isArray(data[0]) || $.isPlainObject(data[0]) ) ? + api.rows.add( data ) : + api.row.add( data ); + + if ( redraw === undefined || redraw ) { + api.draw(); + } + + return rows.flatten().toArray(); + }; + + + /** + * This function will make DataTables recalculate the column sizes, based on the data + * contained in the table and the sizes applied to the columns (in the DOM, CSS or + * through the sWidth parameter). This can be useful when the width of the table's + * parent element changes (for example a window resize). + * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable( { + * "sScrollY": "200px", + * "bPaginate": false + * } ); + * + * $(window).on('resize', function () { + * oTable.fnAdjustColumnSizing(); + * } ); + * } ); + */ + this.fnAdjustColumnSizing = function ( bRedraw ) + { + var api = this.api( true ).columns.adjust(); + var settings = api.settings()[0]; + var scroll = settings.oScroll; + + if ( bRedraw === undefined || bRedraw ) { + api.draw( false ); + } + else if ( scroll.sX !== "" || scroll.sY !== "" ) { + /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ + _fnScrollDraw( settings ); + } + }; + + + /** + * Quickly and simply clear a table + * @param {bool} [bRedraw=true] redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) + * oTable.fnClearTable(); + * } ); + */ + this.fnClearTable = function( bRedraw ) + { + var api = this.api( true ).clear(); + + if ( bRedraw === undefined || bRedraw ) { + api.draw(); + } + }; + + + /** + * The exact opposite of 'opening' a row, this function will close any rows which + * are currently 'open'. + * @param {node} nTr the table row to 'close' + * @returns {int} 0 on success, or 1 if failed (can't find the row) + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnClose = function( nTr ) + { + this.api( true ).row( nTr ).child.hide(); + }; + + + /** + * Remove a row for the table + * @param {mixed} target The index of the row from aoData to be deleted, or + * the TR element you want to delete + * @param {function|null} [callBack] Callback function + * @param {bool} [redraw=true] Redraw the table or not + * @returns {array} The row that was deleted + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Immediately remove the first row + * oTable.fnDeleteRow( 0 ); + * } ); + */ + this.fnDeleteRow = function( target, callback, redraw ) + { + var api = this.api( true ); + var rows = api.rows( target ); + var settings = rows.settings()[0]; + var data = settings.aoData[ rows[0][0] ]; + + rows.remove(); + + if ( callback ) { + callback.call( this, settings, data ); + } + + if ( redraw === undefined || redraw ) { + api.draw(); + } + + return data; + }; + + + /** + * Restore the table to it's original state in the DOM by removing all of DataTables + * enhancements, alterations to the DOM structure of the table and event listeners. + * @param {boolean} [remove=false] Completely remove the table from the DOM + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * // This example is fairly pointless in reality, but shows how fnDestroy can be used + * var oTable = $('#example').dataTable(); + * oTable.fnDestroy(); + * } ); + */ + this.fnDestroy = function ( remove ) + { + this.api( true ).destroy( remove ); + }; + + + /** + * Redraw the table + * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) + * oTable.fnDraw(); + * } ); + */ + this.fnDraw = function( complete ) + { + // Note that this isn't an exact match to the old call to _fnDraw - it takes + // into account the new data, but can hold position. + this.api( true ).draw( complete ); + }; + + + /** + * Filter the input based on data + * @param {string} sInput String to filter the table on + * @param {int|null} [iColumn] Column to limit filtering to + * @param {bool} [bRegex=false] Treat as regular expression or not + * @param {bool} [bSmart=true] Perform smart filtering or not + * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) + * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sometime later - filter... + * oTable.fnFilter( 'test string' ); + * } ); + */ + this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive ) + { + var api = this.api( true ); + + if ( iColumn === null || iColumn === undefined ) { + api.search( sInput, bRegex, bSmart, bCaseInsensitive ); + } + else { + api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive ); + } + + api.draw(); + }; + + + /** + * Get the data for the whole table, an individual row or an individual cell based on the + * provided parameters. + * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as + * a TR node then the data source for the whole row will be returned. If given as a + * TD/TH cell node then iCol will be automatically calculated and the data for the + * cell returned. If given as an integer, then this is treated as the aoData internal + * data index for the row (see fnGetPosition) and the data for that row used. + * @param {int} [col] Optional column index that you want the data of. + * @returns {array|object|string} If mRow is undefined, then the data for all rows is + * returned. If mRow is defined, just data for that row, and is iCol is + * defined, only data for the designated cell is returned. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * // Row data + * $(document).ready(function() { + * oTable = $('#example').dataTable(); + * + * oTable.$('tr').click( function () { + * var data = oTable.fnGetData( this ); + * // ... do something with the array / object of data for the row + * } ); + * } ); + * + * @example + * // Individual cell data + * $(document).ready(function() { + * oTable = $('#example').dataTable(); + * + * oTable.$('td').click( function () { + * var sData = oTable.fnGetData( this ); + * alert( 'The cell clicked on had the value of '+sData ); + * } ); + * } ); + */ + this.fnGetData = function( src, col ) + { + var api = this.api( true ); + + if ( src !== undefined ) { + var type = src.nodeName ? src.nodeName.toLowerCase() : ''; + + return col !== undefined || type == 'td' || type == 'th' ? + api.cell( src, col ).data() : + api.row( src ).data() || null; + } + + return api.data().toArray(); + }; + + + /** + * Get an array of the TR nodes that are used in the table's body. Note that you will + * typically want to use the '$' API method in preference to this as it is more + * flexible. + * @param {int} [iRow] Optional row index for the TR element you want + * @returns {array|node} If iRow is undefined, returns an array of all TR elements + * in the table's body, or iRow is defined, just the TR element requested. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Get the nodes from the table + * var nNodes = oTable.fnGetNodes( ); + * } ); + */ + this.fnGetNodes = function( iRow ) + { + var api = this.api( true ); + + return iRow !== undefined ? + api.row( iRow ).node() : + api.rows().nodes().flatten().toArray(); + }; + + + /** + * Get the array indexes of a particular cell from it's DOM element + * and column index including hidden columns + * @param {node} node this can either be a TR, TD or TH in the table's body + * @returns {int} If nNode is given as a TR, then a single index is returned, or + * if given as a cell, an array of [row index, column index (visible), + * column index (all)] is given. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * $('#example tbody td').click( function () { + * // Get the position of the current data from the node + * var aPos = oTable.fnGetPosition( this ); + * + * // Get the data array for this row + * var aData = oTable.fnGetData( aPos[0] ); + * + * // Update the data array and return the value + * aData[ aPos[1] ] = 'clicked'; + * this.innerHTML = 'clicked'; + * } ); + * + * // Init DataTables + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnGetPosition = function( node ) + { + var api = this.api( true ); + var nodeName = node.nodeName.toUpperCase(); + + if ( nodeName == 'TR' ) { + return api.row( node ).index(); + } + else if ( nodeName == 'TD' || nodeName == 'TH' ) { + var cell = api.cell( node ).index(); + + return [ + cell.row, + cell.columnVisible, + cell.column + ]; + } + return null; + }; + + + /** + * Check to see if a row is 'open' or not. + * @param {node} nTr the table row to check + * @returns {boolean} true if the row is currently open, false otherwise + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnIsOpen = function( nTr ) + { + return this.api( true ).row( nTr ).child.isShown(); + }; + + + /** + * This function will place a new row directly after a row which is currently + * on display on the page, with the HTML contents that is passed into the + * function. This can be used, for example, to ask for confirmation that a + * particular record should be deleted. + * @param {node} nTr The table row to 'open' + * @param {string|node|jQuery} mHtml The HTML to put into the row + * @param {string} sClass Class to give the new TD cell + * @returns {node} The row opened. Note that if the table row passed in as the + * first parameter, is not found in the table, this method will silently + * return. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnOpen = function( nTr, mHtml, sClass ) + { + return this.api( true ) + .row( nTr ) + .child( mHtml, sClass ) + .show() + .child()[0]; + }; + + + /** + * Change the pagination - provides the internal logic for pagination in a simple API + * function. With this function you can have a DataTables table go to the next, + * previous, first or last pages. + * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" + * or page number to jump to (integer), note that page 0 is the first page. + * @param {bool} [bRedraw=true] Redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * oTable.fnPageChange( 'next' ); + * } ); + */ + this.fnPageChange = function ( mAction, bRedraw ) + { + var api = this.api( true ).page( mAction ); + + if ( bRedraw === undefined || bRedraw ) { + api.draw(false); + } + }; + + + /** + * Show a particular column + * @param {int} iCol The column whose display should be changed + * @param {bool} bShow Show (true) or hide (false) the column + * @param {bool} [bRedraw=true] Redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Hide the second column after initialisation + * oTable.fnSetColumnVis( 1, false ); + * } ); + */ + this.fnSetColumnVis = function ( iCol, bShow, bRedraw ) + { + var api = this.api( true ).column( iCol ).visible( bShow ); + + if ( bRedraw === undefined || bRedraw ) { + api.columns.adjust().draw(); + } + }; + + + /** + * Get the settings for a particular table for external manipulation + * @returns {object} DataTables settings object. See + * {@link DataTable.models.oSettings} + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * var oSettings = oTable.fnSettings(); + * + * // Show an example parameter from the settings + * alert( oSettings._iDisplayStart ); + * } ); + */ + this.fnSettings = function() + { + return _fnSettingsFromNode( this[_ext.iApiIndex] ); + }; + + + /** + * Sort the table by a particular column + * @param {int} iCol the data index to sort on. Note that this will not match the + * 'display index' if you have hidden data entries + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sort immediately with columns 0 and 1 + * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); + * } ); + */ + this.fnSort = function( aaSort ) + { + this.api( true ).order( aaSort ).draw(); + }; + + + /** + * Attach a sort listener to an element for a given column + * @param {node} nNode the element to attach the sort listener to + * @param {int} iColumn the column that a click on this node will sort on + * @param {function} [fnCallback] callback function when sort is run + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sort on column 1, when 'sorter' is clicked on + * oTable.fnSortListener( document.getElementById('sorter'), 1 ); + * } ); + */ + this.fnSortListener = function( nNode, iColumn, fnCallback ) + { + this.api( true ).order.listener( nNode, iColumn, fnCallback ); + }; + + + /** + * Update a table cell or row - this method will accept either a single value to + * update the cell with, an array of values with one element for each column or + * an object in the same format as the original data source. The function is + * self-referencing in order to make the multi column updates easier. + * @param {object|array|string} mData Data to update the cell/row with + * @param {node|int} mRow TR element you want to update or the aoData index + * @param {int} [iColumn] The column to update, give as null or undefined to + * update a whole row. + * @param {bool} [bRedraw=true] Redraw the table or not + * @param {bool} [bAction=true] Perform pre-draw actions or not + * @returns {int} 0 on success, 1 on error + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell + * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row + * } ); + */ + this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction ) + { + var api = this.api( true ); + + if ( iColumn === undefined || iColumn === null ) { + api.row( mRow ).data( mData ); + } + else { + api.cell( mRow, iColumn ).data( mData ); + } + + if ( bAction === undefined || bAction ) { + api.columns.adjust(); + } + + if ( bRedraw === undefined || bRedraw ) { + api.draw(); + } + return 0; + }; + + + /** + * Provide a common method for plug-ins to check the version of DataTables being used, in order + * to ensure compatibility. + * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the + * formats "X" and "X.Y" are also acceptable. + * @returns {boolean} true if this version of DataTables is greater or equal to the required + * version, or false if this version of DataTales is not suitable + * @method + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * alert( oTable.fnVersionCheck( '1.9.0' ) ); + * } ); + */ + this.fnVersionCheck = _ext.fnVersionCheck; + + + var _that = this; + var emptyInit = options === undefined; + var len = this.length; + + if ( emptyInit ) { + options = {}; + } + + this.oApi = this.internal = _ext.internal; + + // Extend with old style plug-in API methods + for ( var fn in DataTable.ext.internal ) { + if ( fn ) { + this[fn] = _fnExternApiFunc(fn); + } + } + + this.each(function() { + // For each initialisation we want to give it a clean initialisation + // object that can be bashed around + var o = {}; + var oInit = len > 1 ? // optimisation for single table case + _fnExtend( o, options, true ) : + options; + + /*global oInit,_that,emptyInit*/ + var i=0, iLen, j, jLen, k, kLen; + var sId = this.getAttribute( 'id' ); + var bInitHandedOff = false; + var defaults = DataTable.defaults; + var $this = $(this); + + + /* Sanity check */ + if ( this.nodeName.toLowerCase() != 'table' ) + { + _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); + return; + } + + /* Backwards compatibility for the defaults */ + _fnCompatOpts( defaults ); + _fnCompatCols( defaults.column ); + + /* Convert the camel-case defaults to Hungarian */ + _fnCamelToHungarian( defaults, defaults, true ); + _fnCamelToHungarian( defaults.column, defaults.column, true ); + + /* Setting up the initialisation object */ + _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ) ); + + + + /* Check to see if we are re-initialising a table */ + var allSettings = DataTable.settings; + for ( i=0, iLen=allSettings.length ; i').appendTo($this); + } + oSettings.nTHead = thead[0]; + + var tbody = $this.children('tbody'); + if ( tbody.length === 0 ) { + tbody = $('').appendTo($this); + } + oSettings.nTBody = tbody[0]; + + var tfoot = $this.children('tfoot'); + if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) { + // If we are a scrolling table, and no footer has been given, then we need to create + // a tfoot element for the caption element to be appended to + tfoot = $('').appendTo($this); + } + + if ( tfoot.length === 0 || tfoot.children().length === 0 ) { + $this.addClass( oClasses.sNoFooter ); + } + else if ( tfoot.length > 0 ) { + oSettings.nTFoot = tfoot[0]; + _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot ); + } + + /* Check if there is data passing into the constructor */ + if ( oInit.aaData ) { + for ( i=0 ; i/g; + + // This is not strict ISO8601 - Date.parse() is quite lax, although + // implementations differ between browsers. + var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/; + + // Escape regular expression special characters + var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); + + // http://en.wikipedia.org/wiki/Foreign_exchange_market + // - \u20BD - Russian ruble. + // - \u20a9 - South Korean Won + // - \u20BA - Turkish Lira + // - \u20B9 - Indian Rupee + // - R - Brazil (R$) and South Africa + // - fr - Swiss Franc + // - kr - Swedish krona, Norwegian krone and Danish krone + // - \u2009 is thin space and \u202F is narrow no-break space, both used in many + // standards as thousands separators. + var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi; + + + var _empty = function ( d ) { + return !d || d === true || d === '-' ? true : false; + }; + + + var _intVal = function ( s ) { + var integer = parseInt( s, 10 ); + return !isNaN(integer) && isFinite(s) ? integer : null; + }; + + // Convert from a formatted number with characters other than `.` as the + // decimal place, to a Javascript number + var _numToDecimal = function ( num, decimalPoint ) { + // Cache created regular expressions for speed as this function is called often + if ( ! _re_dic[ decimalPoint ] ) { + _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); + } + return typeof num === 'string' && decimalPoint !== '.' ? + num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : + num; + }; + + + var _isNumber = function ( d, decimalPoint, formatted ) { + var strType = typeof d === 'string'; + + // If empty return immediately so there must be a number if it is a + // formatted string (this stops the string "k", or "kr", etc being detected + // as a formatted number for currency + if ( _empty( d ) ) { + return true; + } + + if ( decimalPoint && strType ) { + d = _numToDecimal( d, decimalPoint ); + } + + if ( formatted && strType ) { + d = d.replace( _re_formatted_numeric, '' ); + } + + return !isNaN( parseFloat(d) ) && isFinite( d ); + }; + + + // A string without HTML in it can be considered to be HTML still + var _isHtml = function ( d ) { + return _empty( d ) || typeof d === 'string'; + }; + + + var _htmlNumeric = function ( d, decimalPoint, formatted ) { + if ( _empty( d ) ) { + return true; + } + + var html = _isHtml( d ); + return ! html ? + null : + _isNumber( _stripHtml( d ), decimalPoint, formatted ) ? + true : + null; + }; + + + var _pluck = function ( a, prop, prop2 ) { + var out = []; + var i=0, ien=a.length; + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if ( prop2 !== undefined ) { + for ( ; i') + .css( { + position: 'fixed', + top: 0, + left: $(window).scrollLeft()*-1, // allow for scrolling + height: 1, + width: 1, + overflow: 'hidden' + } ) + .append( + $('
      ') + .css( { + position: 'absolute', + top: 1, + left: 1, + width: 100, + overflow: 'scroll' + } ) + .append( + $('
      ') + .css( { + width: '100%', + height: 10 + } ) + ) + ) + .appendTo( 'body' ); + + var outer = n.children(); + var inner = outer.children(); + + // Numbers below, in order, are: + // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth + // + // IE6 XP: 100 100 100 83 + // IE7 Vista: 100 100 100 83 + // IE 8+ Windows: 83 83 100 83 + // Evergreen Windows: 83 83 100 83 + // Evergreen Mac with scrollbars: 85 85 100 85 + // Evergreen Mac without scrollbars: 100 100 100 100 + + // Get scrollbar width + browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; + + // IE6/7 will oversize a width 100% element inside a scrolling element, to + // include the width of the scrollbar, while other browsers ensure the inner + // element is contained without forcing scrolling + browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; + + // In rtl text layout, some browsers (most, but not all) will place the + // scrollbar on the left, rather than the right. + browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1; + + // IE8- don't provide height and width for getBoundingClientRect + browser.bBounding = n[0].getBoundingClientRect().width ? true : false; + + n.remove(); + } + + $.extend( settings.oBrowser, DataTable.__browser ); + settings.oScroll.iBarWidth = DataTable.__browser.barWidth; + } + + + /** + * Array.prototype reduce[Right] method, used for browsers which don't support + * JS 1.6. Done this way to reduce code size, since we iterate either way + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnReduce ( that, fn, init, start, end, inc ) + { + var + i = start, + value, + isSet = false; + + if ( init !== undefined ) { + value = init; + isSet = true; + } + + while ( i !== end ) { + if ( ! that.hasOwnProperty(i) ) { + continue; + } + + value = isSet ? + fn( value, that[i], i, that ) : + that[i]; + + isSet = true; + i += inc; + } + + return value; + } + + /** + * Add a column to the list used for the table with default values + * @param {object} oSettings dataTables settings object + * @param {node} nTh The th element for this column + * @memberof DataTable#oApi + */ + function _fnAddColumn( oSettings, nTh ) + { + // Add column to aoColumns array + var oDefaults = DataTable.defaults.column; + var iCol = oSettings.aoColumns.length; + var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { + "nTh": nTh ? nTh : document.createElement('th'), + "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', + "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], + "mData": oDefaults.mData ? oDefaults.mData : iCol, + idx: iCol + } ); + oSettings.aoColumns.push( oCol ); + + // Add search object for column specific search. Note that the `searchCols[ iCol ]` + // passed into extend can be undefined. This allows the user to give a default + // with only some of the parameters defined, and also not give a default + var searchCols = oSettings.aoPreSearchCols; + searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] ); + + // Use the default column options function to initialise classes etc + _fnColumnOptions( oSettings, iCol, $(nTh).data() ); + } + + + /** + * Apply options for a column + * @param {object} oSettings dataTables settings object + * @param {int} iCol column index to consider + * @param {object} oOptions object with sType, bVisible and bSearchable etc + * @memberof DataTable#oApi + */ + function _fnColumnOptions( oSettings, iCol, oOptions ) + { + var oCol = oSettings.aoColumns[ iCol ]; + var oClasses = oSettings.oClasses; + var th = $(oCol.nTh); + + // Try to get width information from the DOM. We can't get it from CSS + // as we'd need to parse the CSS stylesheet. `width` option can override + if ( ! oCol.sWidthOrig ) { + // Width attribute + oCol.sWidthOrig = th.attr('width') || null; + + // Style attribute + var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); + if ( t ) { + oCol.sWidthOrig = t[1]; + } + } + + /* User specified column options */ + if ( oOptions !== undefined && oOptions !== null ) + { + // Backwards compatibility + _fnCompatCols( oOptions ); + + // Map camel case parameters to their Hungarian counterparts + _fnCamelToHungarian( DataTable.defaults.column, oOptions ); + + /* Backwards compatibility for mDataProp */ + if ( oOptions.mDataProp !== undefined && !oOptions.mData ) + { + oOptions.mData = oOptions.mDataProp; + } + + if ( oOptions.sType ) + { + oCol._sManualType = oOptions.sType; + } + + // `class` is a reserved word in Javascript, so we need to provide + // the ability to use a valid name for the camel case input + if ( oOptions.className && ! oOptions.sClass ) + { + oOptions.sClass = oOptions.className; + } + if ( oOptions.sClass ) { + th.addClass( oOptions.sClass ); + } + + $.extend( oCol, oOptions ); + _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); + + /* iDataSort to be applied (backwards compatibility), but aDataSort will take + * priority if defined + */ + if ( oOptions.iDataSort !== undefined ) + { + oCol.aDataSort = [ oOptions.iDataSort ]; + } + _fnMap( oCol, oOptions, "aDataSort" ); + } + + /* Cache the data get and set functions for speed */ + var mDataSrc = oCol.mData; + var mData = _fnGetObjectDataFn( mDataSrc ); + var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; + + var attrTest = function( src ) { + return typeof src === 'string' && src.indexOf('@') !== -1; + }; + oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && ( + attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) + ); + oCol._setter = null; + + oCol.fnGetData = function (rowData, type, meta) { + var innerData = mData( rowData, type, undefined, meta ); + + return mRender && type ? + mRender( innerData, type, rowData, meta ) : + innerData; + }; + oCol.fnSetData = function ( rowData, val, meta ) { + return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta ); + }; + + // Indicate if DataTables should read DOM data as an object or array + // Used in _fnGetRowElements + if ( typeof mDataSrc !== 'number' ) { + oSettings._rowReadObject = true; + } + + /* Feature sorting overrides column specific when off */ + if ( !oSettings.oFeatures.bSort ) + { + oCol.bSortable = false; + th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called + } + + /* Check that the class assignment is correct for sorting */ + var bAsc = $.inArray('asc', oCol.asSorting) !== -1; + var bDesc = $.inArray('desc', oCol.asSorting) !== -1; + if ( !oCol.bSortable || (!bAsc && !bDesc) ) + { + oCol.sSortingClass = oClasses.sSortableNone; + oCol.sSortingClassJUI = ""; + } + else if ( bAsc && !bDesc ) + { + oCol.sSortingClass = oClasses.sSortableAsc; + oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; + } + else if ( !bAsc && bDesc ) + { + oCol.sSortingClass = oClasses.sSortableDesc; + oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; + } + else + { + oCol.sSortingClass = oClasses.sSortable; + oCol.sSortingClassJUI = oClasses.sSortJUI; + } + } + + + /** + * Adjust the table column widths for new data. Note: you would probably want to + * do a redraw after calling this function! + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnAdjustColumnSizing ( settings ) + { + /* Not interested in doing column width calculation if auto-width is disabled */ + if ( settings.oFeatures.bAutoWidth !== false ) + { + var columns = settings.aoColumns; + + _fnCalculateColumnWidths( settings ); + for ( var i=0 , iLen=columns.length ; i