pax_global_header00006660000000000000000000000064146450116240014515gustar00rootroot0000000000000052 comment=b96c9cf6ae9abe16c660b8a611d7b363d97f50e6 simplemonitor-1.13.0/000077500000000000000000000000001464501162400145005ustar00rootroot00000000000000simplemonitor-1.13.0/.codacy.yml000066400000000000000000000002741464501162400165460ustar00rootroot00000000000000--- engines: pylint: enabled: true python_verison: 3 remark-stringify: listItemIndent: space paddedTable: false tablePipeAlign: false exclude_paths: - "tests/**" simplemonitor-1.13.0/.codecov.yml000066400000000000000000000000361464501162400167220ustar00rootroot00000000000000ignore: - "tests/test_*.py" simplemonitor-1.13.0/.coveragerc000066400000000000000000000001101464501162400166110ustar00rootroot00000000000000[report] exclude_lines = raise RuntimeError raise NotImplementedError simplemonitor-1.13.0/.dockerignore000066400000000000000000000005011464501162400171500ustar00rootroot00000000000000# environment specific # + + + + + + + + + + + + + + + + + + + + .idea/ venv/ .vscode .git # python specific # + + + + + + + + + + + + + + + + + + + + *.pyc .python-version # volumes # + + + + + + + + + + + + + + + + + + + + _monitor-export # other bits of the project not required .github/ build dist docs tests tags simplemonitor-1.13.0/.github/000077500000000000000000000000001464501162400160405ustar00rootroot00000000000000simplemonitor-1.13.0/.github/FUNDING.yml000066400000000000000000000001021464501162400176460ustar00rootroot00000000000000# These are supported funding model platforms github: [jamesoff] simplemonitor-1.13.0/.github/dependabot.yml000066400000000000000000000012351464501162400206710ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: daily open-pull-requests-limit: 10 target-branch: develop ignore: - dependency-name: boto3 - dependency-name: black versions: - 21.4b0 - dependency-name: pylint versions: - 2.7.0 - 2.7.1 - 2.7.3 - 2.7.4 - 2.8.1 - dependency-name: urllib3 versions: - 1.26.4 - dependency-name: pytest versions: - 6.2.3 - dependency-name: colorlog versions: - 4.8.0 - dependency-name: flake8 versions: - 3.9.0 - dependency-name: ping3 versions: - 2.7.0 - dependency-name: arrow versions: - 1.0.3 simplemonitor-1.13.0/.github/workflows/000077500000000000000000000000001464501162400200755ustar00rootroot00000000000000simplemonitor-1.13.0/.github/workflows/codeql.yml000066400000000000000000000016561464501162400220770ustar00rootroot00000000000000name: "CodeQL" on: push: branches: [ "develop", "main" ] pull_request: branches: [ "develop" ] schedule: - cron: "47 18 * * 6" jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ javascript, python ] steps: - name: Checkout uses: actions/checkout@v3 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild uses: github/codeql-action/autobuild@v2 if: ${{ matrix.language == 'javascript' || matrix.language == 'python' }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: category: "/language:${{ matrix.language }}" simplemonitor-1.13.0/.github/workflows/docker.yml000066400000000000000000000014131464501162400220660ustar00rootroot00000000000000name: Docker on: [push, pull_request] jobs: docker: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Prepare ini files run: cp tests/monitor-docker.ini monitor.ini; cp tests/monitors-docker.ini monitors.ini - name: Build standalone container run: docker build -f docker/monitor.Dockerfile -t simplemonitor:latest . - name: Test Docker container run: docker run simplemonitor simplemonitor --one-shot -v docker-compose: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Prepare ini files run: cp tests/monitor-docker.ini monitor.ini; cp tests/monitors-docker.ini monitors.ini - name: Build docker-compose environment run: docker-compose build simplemonitor-1.13.0/.github/workflows/linting.yml000066400000000000000000000023161464501162400222660ustar00rootroot00000000000000name: Linting on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] name: Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v3 - name: Setup python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Check pip run: pip --version - name: Update pip run: pip install --upgrade pip - name: Install poetry run: pip install poetry - name: poetry install run: poetry install - name: check code style with black run: make black if: ${{ matrix.python-version == '3.8' || matrix.python-version == '3.9' }} # broken in 3.6 currently - name: flake8 tests run: make flake8 if: ${{ matrix.python-version == '3.8' || matrix.python-version == '3.9' }} - name: bandit run: make bandit if: ${{ matrix.python-version == '3.8' || matrix.python-version == '3.9' }} - name: mypy run: make mypy if: ${{ matrix.python-version == '3.8' || matrix.python-version == '3.9' }} simplemonitor-1.13.0/.github/workflows/main.yml000066400000000000000000000022501464501162400215430ustar00rootroot00000000000000name: Tests (Linux) on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] name: Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v3 - name: Setup python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Create html dir run: mkdir -p html - name: Check pip run: pip --version - name: Update pip run: pip install --upgrade pip - name: Install poetry run: pip install poetry - name: poetry install run: poetry install - name: Integration tests run: make integration-tests - name: Integration tests (threaded) run: make integration-tests-threaded - name: Config environment variables tests run: make env-test - name: Unit tests run: make unit-test - name: Network logger tests run: make network-test - name: Output coverage report run: poetry run coverage xml -i - uses: codecov/codecov-action@v3 simplemonitor-1.13.0/.github/workflows/publish.yml000066400000000000000000000052721464501162400222740ustar00rootroot00000000000000name: Publish to TestPyPI and PyPI on: push: branches: - main tags: - v* jobs: build: name: Build distribution runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install poetry run: >- pip install poetry --user - name: Build run: poetry build - name: Store distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ publish-to-pypi: name: Publish Python distribution to PyPI if: startsWith(github.ref, 'refs/tags/') needs: - build runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/simplemonitor permissions: id-token: write steps: - name: Download dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 github-release: name: Sign distribution and upload to GitHub Release needs: - publish-to-pypi runs-on: ubuntu-latest permissions: contents: write id-token: write steps: - name: Download dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Sign the dists with Sigstore uses: sigstore/gh-action-sigstore-python@v2.1.1 with: inputs: >- ./dist/*.tar.gz ./dist/*.whl - name: Create GitHub release env: GITHUB_TOKEN: ${{ github.token }} run: >- gh release create '${{ github.ref_name }}' --repo '${{ github.repository }}' --notes "" - name: Upload artifact signatures to GitHub Release env: GITHUB_TOKEN: ${{ github.token }} run: >- gh release upload '${{ github.ref_name }}' dist/** --repo '${{ github.repository }}' publish-to-testpypi: name: Publish Python distribution to TestPyPI needs: - build runs-on: ubuntu-latest environment: name: pypi url: https://test.pypi.org/p/simplemonitor permissions: id-token: write steps: - name: Download dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish to TestPyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ simplemonitor-1.13.0/.github/workflows/windows.yml000066400000000000000000000017741464501162400223230ustar00rootroot00000000000000name: Tests (Windows) on: [push, pull_request] jobs: build: runs-on: windows-latest strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] name: Python ${{ matrix.python-version }} (Windows) steps: - uses: actions/checkout@v3 - name: Setup python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Check pip run: python -m pip --version - name: Update pip run: python -m pip install --user --upgrade pip - name: Install poetry run: pip install poetry - name: poetry install run: poetry install - name: create HTML output folder run: mkdir html - name: Integration tests run: make integration-tests - name: Integration tests (threaded) run: make integration-tests-threaded - name: Unit tests run: make unit-test - uses: codecov/codecov-action@v3 simplemonitor-1.13.0/.gitignore000066400000000000000000000010441464501162400164670ustar00rootroot00000000000000*.pyc _site build/* tags .ropeproject .bundle .idea/ monitor.ini config.ini monitors.ini monitor.json htmlcov/ .coverage .idea/ html/status.html html/index.html monitor.db monitor?.log monitor2.db _monitor-export/ venv/ env/ *.vscode *.swp docs/.sass-cache docs/vendor monitor.log monitor.pid output.json did_recover* simplemonitor.egg-info dist /html network.log .coverage.* .ring_token.cache simplemonitor.code-workspace client.log master.log coverage.xml _build old_docs docs/_build *.log .tox pyrightconfig.json simplemonitor/html/node_modules simplemonitor-1.13.0/.pre-commit-config.yaml000066400000000000000000000020351464501162400207610ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 hooks: - id: check-yaml - id: end-of-file-fixer exclude: | (?x)^( simplemonitor/html/dist/.*bundle.js )$ - id: trailing-whitespace exclude: | (?x)^( tests/test_alerter.py| tests/html/.* )$ - id: flake8 - repo: https://github.com/psf/black rev: 24.4.2 hooks: - id: black - repo: https://github.com/asottile/seed-isort-config rev: v2.2.0 hooks: - id: seed-isort-config - repo: https://github.com/pre-commit/mirrors-isort rev: v5.9.2 # pick the isort version you'd like to use from https://github.com/pre-commit/mirrors-isort/releases hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.910 hooks: - id: mypy args: ["--install-types", "--non-interactive"] simplemonitor-1.13.0/.python-version000066400000000000000000000000071464501162400175020ustar00rootroot000000000000003.10.1 simplemonitor-1.13.0/.readthedocs.yaml000066400000000000000000000002471464501162400177320ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.12" sphinx: configuration: docs/conf.py python: install: - requirements: docs/requirements.txt simplemonitor-1.13.0/CHANGELOG000066400000000000000000000166531464501162400157250ustar00rootroot000000000000001.13.0: * Add ntfy alerter 1.12.1: * Adjust no-alerters message to warn not critical * Include output on "command" monitor failure * Switch map output to just OSM * Make make_start option required if map is enabled * Improve help output and add basic man page * Add hostname to notifications * Support loading config files from a directory * Add timeout for receive in network listener * Add option to capture "command" monitor output * Switch to icmplib for "ping" monitor 1.12: * Require Python 3.7+ * Add allow_redirects option for HTTP monitor * Add port option for DNS monitor * Improve reliability of network logging thread * Fix display of 60 seconds up/downtime as "1:00" instead of "0:60" * Add heartbeat option to Loggers * Add downtime to monitor recovery message * Add nextcloud alerter * Add support for Ring camera * Improve logic for OOH alerting * Improve compound monitor alert/failure calculation * Remove PyOpenSSL and use native SSL support * Improve efficiency for copying HTML files around * Fix double-call to record_fail in unix service monitor 1.11: * Run monitors multithreaded * Add TLS certificate expiry monitor * Add unifi failover/watchdog monitors * Add sms77 alerter * Add twilio SMS alerter * Add fileloggerng logger with rotation support * Add seq logger * Improve timezone support for alerters * Add option to only listen on IPv4 for remote instances * Make group filtering work on remote monitors * Add "_all" group * Add enabled option for monitors * Improve HASS logger * Add new remote logging protocol * Remove pickle support for remote instances * Add client_name option for remote logging * Add descriptions to alerters and loggers * Add support for cc field in SMTP logger * Use jinja2 template for HTML logger rendering * Add map output option for HTML logger * Add gps property to monitors, currently only used for map output * Rewrite documentation, now built with Sphinx and hosted at RTD * Use poetry for project/dependency management * Assorted code refactoring and bugfixes 1.10: * REQUIRE PYTHON >= 3.6.2 * New style HTML page for HTML logger * Track availability percentage, and include in HTML * Add Arlo battery monitor * Add Amazon SNS alerter * Add only_failures option to alerters * Add failure_doc property to monitors, to allow linking to e.g. runbooks * Use psutil library to monitor Windows Services; fixes issue where non-English localisations broke * Add memory and swap monitors * Add group support to loggers * Add unix_service monitor, for generic cross-flavour service checking (if "service X status" works, you can use this) * Add process monitor, to check a process(es) are running * Add option to bind network listener to a specific IP * Add new ping monitor, which uses a Python library to ping hosts. Fixes issues from trying to parse ping(8) output (e.g. localisations), but requires root to work * Add username/password options for MQTT logger * Improve timezone handling; everything is done in UTC internally and you can specify the timezone to use for alerters and loggers * Improve handling of exceptions thrown but not handled by monitors * Improve handling of monitors which went away (or were renamed) on remote hosts * Improve reporting of SSL errors in HTTP monitor * Unified alert message generator across all Alerters * Many code improvements and refactoring to support new features in the future 1.9: * REQUIRE PYTHON >= 3.5 * Switch to distribution as a pip package! You can now "pip install simplemonitor" * Add memory monitor * Add Ring doorbell monitor (checks battery level) * Add support for checking file is not too large * Add support for running a command when a monitor recovers * Add support for reloading config on the fly * Fixes to compound monitor logic * HTTP monitor can check both return code and regexp now * Update pkgaudit monitor to chase new output format * DNS monitor can check response is NXDOMAIN * Many code quality improvements (I hope) 1.8: * REMOVED SUPPORT FOR Python 2.6 * LAST RELEASE TO SUPPORT 2.7 * CHANGED REMOTE MONITOR PROTOCOL (security fix) * Support Python 3 * Add JSON logger * Add 46elks SMS alerter * Add PushBullet alerter * Add Telegram alerter * Add Notification Center alerter (for macOS) * Add systemd unit monitor * Add Home Automation monitor * Add MQTT logger * Improve Slack alerter's configurability * Add basic HTTP Auth and timeouts to HTTP monitor * Verify SSL certificates by defaults * Add notification groups * Add support for environment variables in config values and section names * Add tests * Add sample docker configurations * Use Pipenv for requirements management * Added example startup scripts including a Windows Service * Improved logging output (to stdout, not the Logger class) * IPv6 support for network Logger * DB Loggers now auto-create the database/table as needed, and can update schema version * Email Logger now supports multiple addresses * DNS Monitor now supports multivalue responses * Use JSON format for remote monitor protocol; more secure than pickle 1.7: + Add Slack alerter + Add Command monitor + Add pkg audit monitor + Add SSL client auth + Add Amazon SES alerter + Add a sample upstart script + Add support for Pushover + Add support for alerters to repeat + Support username/password and SSL support for SMTP * Use subprocess everywhere * Honour SMTP port in configuration * Honour gap configuration option * Use HTTPS for BulkSMS API * Date format for logfile is configurable * Use UTC everywhere for times; should fix monitors which have been down -1 hours * Teach the host (ping) monitor to use command line options better on different operating systems * Verify dependencies exist * Stop alerters from firing success notifications out-of-hours 1.6: * Added DNS monitor * Added Execute logger 1.4: * Fixed a problem where success alerts were always sent * Many other small fixes + Added remote monitor/central reporting support + Added support for monitor defaults + Added HTML logger type to generate a status webpage + Added syslog alerter 1.3: + Added -p option to write a pidfile + Added support for a HUP signal to close/reopen logfiles 1.2: * Refactored monitor/logger/alerter registration code These objects are now more self-loading and just need to be passed a dict of their config options. * Changed to using packages for monitors, loggers and alerters. * Fixed bug where initial failure time was always the most recent failure time. * Fixed a bug with the DiskSpace monitor which meant the free space on non-Windows platforms was incorrectly calculated. * Changed the intention of the DiskSpace monitior on non-Windows platforms to measure the non-superuser free space. * Fixed a bug which could mean the main loop ignored the interval if an error occurred during tests. * Better support for the dry_run setting on alerters * BulkSMS sender now limits messages to 160 chars (otherwise BulkSMS rejects them). * Formatted times in alerts to not include microseconds + Monitors now know their own name + Alerters now have configurable time ranges for operation. + Alerters now have days of operation. + Alerters can hold alerts that occur out-of-hours and update you if the monitor is still failed when they become in-hours. + Email alerts for failure now include downtime (which will be 0 for an alert that fires immediately, but may be useful for alerters with a limit). <= 1.1: * Changes not tracked. simplemonitor-1.13.0/CODE_OF_CONDUCT.md000066400000000000000000000062171464501162400173050ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at james@jamesoff.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ simplemonitor-1.13.0/CONTRIBUTORS000066400000000000000000000010661464501162400163630ustar00rootroot00000000000000BrainDoctor Bruno Volpato Carles Pina i Estany Charlie & Christian Groschupp Daniele Teti Ed Levin Erwin Goslawski Fabian Fred Clift FvDxxx G Fisher Herman Slatman James Seward Jamie McClelland Joe Shamah John Impallomeni Kirk Winkelman LGTM Migrator MattK Michał Słomkowski Paolo Cretaro Patrick Valsecchi Ryan Jarvis Sara Murray Scott Horsley Sebastian Klapp Simeon Felis Tim User & Valentin Lorentz Wayward One WoJ andrew andronkyr dan danieldh206 error454 fabian graham kz159 nvnwater pheuzoune r4r3 rarosalion shakreiner snyk-bot tlegras wojtek wsw70 wxcafé simplemonitor-1.13.0/LICENCE000066400000000000000000000027141464501162400154710ustar00rootroot00000000000000Copyright (c) 2007, James M Seward All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of jamesoff.net nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. simplemonitor-1.13.0/Makefile000066400000000000000000000034361464501162400161460ustar00rootroot00000000000000.PHONY: flake8 dist twine twine-test integration-tests env-test network-test black mypy linting mypy-strict bandit bandit-strict ifeq ($(OS),Windows_NT) MOCKSPATH := tests\mocks; INTEGRATION_CONFIG := tests/monitor-windows.ini else MOCKSPATH := $(PWD)/tests/mocks: INTEGRATION_CONFIG := tests/monitor.ini endif PIPENV := $(shell which poetry) flake8: poetry run flake8 *.py simplemonitor/ integration-tests: PATH="$(MOCKSPATH)$(PATH)" $(PIPENV) run coverage run monitor.py -1 -v -d -f $(INTEGRATION_CONFIG) -j 1 integration-tests-threaded: PATH="$(MOCKSPATH)$(PATH)" $(PIPENV) run coverage run monitor.py -1 -v -d -f $(INTEGRATION_CONFIG) env-test: env TEST_VALUE=myenv poetry run coverage run --append monitor.py -t -f tests/monitor-env.ini unit-test: poetry run pytest --cov-append --cov=simplemonitor --cov-report= tests network-test: rm -f master.log rm -f client.log poetry run tests/test-network.sh dist: rm -f dist/simplemonitor-* poetry run python setup.py sdist bdist_wheel twine-test: poetry run python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* twine: poetry run python -m twine upload dist/* black: poetry run black --check --diff *.py simplemonitor/ mypy: poetry run mypy *.py simplemonitor/ mypy-strict: poetry run mypy --disallow-untyped-calls --disallow-untyped-defs --disallow-incomplete-defs --disallow-untyped-decorators *.py simplemonitor/ bandit: poetry run bandit -r -ll *.py simplemonitor/ bandit-strict: poetry run bandit -r -l *.py simplemonitor/ linting: black flake8 mypy bandit docker-build: docker build -f docker/monitor.Dockerfile . docker-compose-build: docker-compose build html-bundle: cd simplemonitor/html && npx webpack fix-html-tests: for i in tests/html/*.html; do sed -i -E -e 's/1.12.0/__VERSION__/g' "$$i"; done simplemonitor-1.13.0/README.md000066400000000000000000000022171464501162400157610ustar00rootroot00000000000000[![PyPI version fury.io](https://badge.fury.io/py/simplemonitor.svg)](https://pypi.python.org/pypi/simplemonitor/) [![Downloads](https://pepy.tech/badge/simplemonitor)](https://pepy.tech/project/simplemonitor) ![Tests (Windows)](https://github.com/jamesoff/simplemonitor/workflows/Tests%20(Windows)/badge.svg) ![Tests (Linux)](https://github.com/jamesoff/simplemonitor/workflows/Tests%20(Linux)/badge.svg) ![Linting](https://github.com/jamesoff/simplemonitor/workflows/Linting/badge.svg) [![codecov](https://codecov.io/gh/jamesoff/simplemonitor/branch/master/graph/badge.svg)](https://codecov.io/gh/jamesoff/simplemonitor) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) SimpleMonitor is a Python script which monitors hosts and network connectivity. It is designed to be quick and easy to set up and lacks complex features that can make things like Nagios, OpenNMS and Zenoss overkill for a small business or home network. Remote monitor instances can send their results back to a central location. Requires Python >= 3.6.2. Documentation is here: https://simplemonitor.readthedocs.io/en/latest/ simplemonitor-1.13.0/build/000077500000000000000000000000001464501162400155775ustar00rootroot00000000000000simplemonitor-1.13.0/build/update-contributors.sh000077500000000000000000000004351464501162400221550ustar00rootroot00000000000000#!/usr/bin/env bash contributors=CONTRIBUTORS [[ -f "$contributors".tmp ]] && rm -f "$contributors".tmp git shortlog -s | grep -vE "dependabot|synk-bot" | cut -f2- > "$contributors".tmp sort -u CONTRIBUTORS.tmp > CONTRIBUTORS [[ -f "$contributors".tmp ]] && rm -f "$contributors".tmp simplemonitor-1.13.0/docker-compose.yml000066400000000000000000000007361464501162400201430ustar00rootroot00000000000000version: '2' services: monitor: build: context: ./ dockerfile: ./docker/monitor.Dockerfile volumes: - http-content:/code/html - ./_monitor-export:/code/monitor-export restart: always depends_on: - webserver webserver: build: context: ./ dockerfile: ./docker/webserver.Dockerfile ports: - "8000:80" volumes: - http-content:/usr/share/nginx/html restart: always volumes: http-content: simplemonitor-1.13.0/docker/000077500000000000000000000000001464501162400157475ustar00rootroot00000000000000simplemonitor-1.13.0/docker/README.md000066400000000000000000000024501464501162400172270ustar00rootroot00000000000000Docker environment for SimpleMonitor ==================================== Caveat: I'm not a Docker expert :) Just Docker ----------- To build a Docker container, place your `monitor.ini` and `monitors.ini` files in the top level of the repo (i.e. not in the `docker` directory) and build a container: ```bash docker build -f docker/monitor.Dockerfile . ``` You should now be able to run this container. Note that if you update your configuration, you will need to rebuild the container. You should be able to use Alerters OK from this set up, but using loggers may require some work (to get the file(s) out of the container); something like a volume mount I expect. (See note at top of file.) docker-compose -------------- The docker-compose stack runs a SimpleMonitor container and a webserver container, so you can use the HTML Logger. It also pre-configures a `_monitor-export` directory between the container and the host you can use to share files. To use it, place your configuration files in the top level of the repo and build the containers: ```bash docker-compose build ``` and then you can run it all: ```bash docker-compose run ``` You can access the webserver on `localhost:8000`. If you update your configuration you will need to rebuild the monitor container: `docker-compose build monitor`. simplemonitor-1.13.0/docker/monitor.Dockerfile000066400000000000000000000037761464501162400214440ustar00rootroot00000000000000# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- # >> python @ alpine # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- FROM python:3.12-alpine # >> meta :: labels LABEL version_dockerfile="21-01-2024:prod" \ version_image="python:3.12-alpine" # >> package :: install RUN apk --no-cache add --update \ # __ install :: basics build-base \ openssl \ # __ install :: tools bash \ sudo \ openrc \ su-exec \ bind-tools \ openssl-dev \ libffi-dev \ rust \ cargo # >> env :: web/docker paths ENV DOCKER_ROOT=/code \ DOCKER_HTML_ROOT=/code/html \ DOCKER_HTML_BACKUP=/code/html-backup \ DOCKER_ENTRYPOINT_BINARY=/bin/monitor.entrypoint.sh \ DOCKER_ENTRYPOINT_ORIGIN=/code/docker/monitor.entrypoint.sh # >> env :: source/host paths ENV SOURCE_ROOT=./ # >> env :: volumes ENV VOLUME_UNIVERSAL_HTML=$DOCKER_HTML_ROOT \ VOLUME_MONITOR_EXPORT=/code/monitor-export # >> env :: user/groups ENV MAIN_USER=simplemonitor \ MAIN_USER_ID=1500 \ MAIN_GROUP=simplemonitor \ MAIN_GROUP_ID=1500 # >> setup :: root-directory RUN mkdir -p $DOCKER_ROOT COPY $SOURCE_ROOT $DOCKER_ROOT WORKDIR $DOCKER_ROOT RUN pip install --upgrade pip # >> install :: py-requirements RUN pip install --no-cache-dir "$DOCKER_ROOT" # >> prepare :: volumes RUN mkdir -p $VOLUME_MONITOR_EXPORT $DOCKER_HTML_ROOT # >> setup :: volumes VOLUME $VOLUME_UNIVERSAL_HTML \ $VOLUME_MONITOR_EXPORT # >> add :: user, group, project-directory-rights RUN addgroup -g $MAIN_GROUP_ID $MAIN_GROUP \ && adduser -D -G $MAIN_GROUP -u $MAIN_USER_ID $MAIN_USER \ && chown -R $MAIN_USER:$MAIN_GROUP $DOCKER_ROOT # >> entrypoint :: prepare RUN cp $DOCKER_ENTRYPOINT_ORIGIN $DOCKER_ENTRYPOINT_BINARY \ && chmod +x $DOCKER_ENTRYPOINT_BINARY # Start the monitor CMD ["/bin/monitor.entrypoint.sh"] simplemonitor-1.13.0/docker/monitor.entrypoint.sh000066400000000000000000000037021464501162400222060ustar00rootroot00000000000000#!/bin/bash # ENVs # == == == == == == == == == == == == == == == # >> env :: web/docker paths # ENV DOCKER_ROOT=/code \ # DOCKER_HTML_BACKUP=/code/html-backup \ # DOCKER_ENTRYPOINT_BINARY=/bin/docker.entrypoint.sh \ # DOCKER_ENTRYPOINT_ORIGIN=/code/docker/docker.entrypoint.sh # >> env :: source/host paths # ENV SOURCE_ROOT=./ \ # SOURCE_HTML_ROOT=./html/ # >> env :: user/groups # ENV MAIN_USER=simplemonitor \ # MAIN_USER_ID=1500 \ # MAIN_GROUP=simplemonitor \ # MAIN_GROUP_ID=1500 # >> env :: volumes # ENV VOLUME_UNIVERSAL_HTML=/code/html if [ ! -f /code/init.flag ]; then # fix docker issue with right-levels on volumes # == == == == == == == == == == == == == == == chown -R $MAIN_USER:$MAIN_GROUP $VOLUME_UNIVERSAL_HTML chmod -R 777 $VOLUME_UNIVERSAL_HTML chown -R $MAIN_USER:$MAIN_GROUP $VOLUME_MONITOR_EXPORT chmod -R 777 $VOLUME_MONITOR_EXPORT # set file-flag # == == == == == == == == == == == == == == == su-exec $DOCKER_USER:$DOCKER_GROUP touch /code/init.flag fi echo "environment vars == == == == == == == == ==" echo "== == == == == == == == == == == == == == ==" echo "DOCKER_ROOT "$DOCKER_ROOT echo "DOCKER_HTML_ROOT "$DOCKER_HTML_ROOT echo "DOCKER_HTML_BACKUP "$DOCKER_HTML_BACKUP echo "SOURCE_ROOT "$SOURCE_ROOT echo "DOCKER_ENTRYPOINT_BINARY "$DOCKER_ENTRYPOINT_BINARY echo "DOCKER_ENTRYPOINT_ORIGIN "$DOCKER_ENTRYPOINT_ORIGIN echo "MAIN_USER "$MAIN_USER echo "MAIN_USER_ID "$MAIN_USER_ID echo "MAIN_GROUP "$MAIN_GROUP echo "MAIN_GROUP_ID "$MAIN_GROUP_ID echo "VOLUME_UNIVERSAL_HTML "$VOLUME_UNIVERSAL_HTML echo "VOLUME_MONITOR_EXPORT "$VOLUME_MONITOR_EXPORT # exec entrypoint.py # == == == == == == == == == == == == == == == cd /code simplemonitor # exec some other commands # == == == == == == == == == == == == == == == exec "$@" simplemonitor-1.13.0/docker/webserver.Dockerfile000066400000000000000000000006771464501162400217560ustar00rootroot00000000000000# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- # >> nginx @ alpine # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- FROM nginx:1.15.1-alpine # >> meta :: labels LABEL version_dockerfile="10-07-2018:prod" \ version_image="nginx:1.15.1-alpine" # >> package :: install RUN apk --no-cache add --update \ # __ install :: basics build-base \ openssl \ # __ install :: tools bash simplemonitor-1.13.0/docs/000077500000000000000000000000001464501162400154305ustar00rootroot00000000000000simplemonitor-1.13.0/docs/Makefile000066400000000000000000000011721464501162400170710ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) simplemonitor-1.13.0/docs/alerters.rst000066400000000000000000000133061464501162400200060ustar00rootroot00000000000000Alerter Configuration ===================== Alerters send one-off alerts when a monitor fails. They can also send an alert when it succeeds again. An alerter knows if it is urgent or not; if a monitor defined as non-urgent fails, an urgent alerter will not trigger for it. This means you can avoid receiving SMS alerts for things which don’t require your immediate attention. Alerters can also have a time configuration for hours when they are or are not allowed to alert. They can also send an alert at the end of the silence period for any monitors which are currently failed. Alerters are defined in the main configuration file, which by default is :file:`monitor.ini`. The section name is the name of your alerter, which you should then add to the ``alerters`` configuration value. .. contents:: Common options -------------- These options are common to all alerter types. .. confval:: type :type: string :required: true the type of the alerter; one of those in the list below. .. confval:: depend :type: comma-separated list of string :required: false :default: none a list of monitors this alerter depends on. If any of them fail, no attempt will be made to send the alert. .. _alerter-limit: .. confval:: limit :type: integer :required: false :default: ``1`` the (virtual) number of times a monitor must have failed before this alerter fires for it. You can use this to escalate an alert to another email address or text messaging, for example. See the :ref:`tolerance` Monitor configuration option. .. confval:: dry_run :type: boolean :required: false :default: ``false`` makes an alerter do everything except actually send the message, and instead will print some information about what it would do. .. confval:: ooh_success :type: boolean :required: false :default: ``false`` makes an alerter trigger its success action even if out of hours .. confval:: groups :type: comma-separated list of string :required: false :default: ``default`` list of monitor groups this alerter should fire for. See the :ref:`group` setting for monitors. .. confval:: only_failures :type: boolean :required: false :default: ``false`` if true, only send alerts for failures (or catchups) .. _alerter-tz: .. confval:: tz :type: string :required: false :default: ``UTC`` the timezone to use in alert messages. See also :confval:`times_tz`. .. confval:: repeat :type: boolean :required: false :default: ``false`` fire this alerter (for a failed monitor) every iteration .. confval:: urgent :type: boolean :required: false if the alerter should be urgent or not. The default varies from alerter to alerter. Typically, those which send "page" style alerts such as SMS default to urgent. You can use this option to override that in e.g. the case of the SNS alerter, which could be urgent if sending SMSes, but non-urgent if sending emails. Time restrictions ----------------- All alerters accept time period configuration. By default, an alerter is active at all times, so you will always immediately receive an alert at the point where a monitor has failed enough (more times than the limit). To set limits on when an alerter can send, use the configuration values below. Note that the :confval:`times_type` option sets the timezone all the values are interpreted as. The default is the local timezone of the host evaluating the logic. .. confval:: day :type: comma-separated list of integer :required: false :default: all days which days an alerter can operate on. ``0`` is Monday, ``6`` is Sunday. .. confval:: times_type :type: string :required: false :default: ``always`` one of ``always``, ``only``, or ``not``. ``only`` means that the limits specify the period the alerter is allowed to operate in. ``not`` means the specify the period it isn't, and outside of that time it is allowed. .. confval:: time_lower :type: string :required: when :confval:`times_type` is not ``always`` the lower end of the time range. Must be lower than :confval:`time_upper`. The format is ``HH:mm`` in 24-hour clock. .. confval:: time_upper :type: string :required: when :confval:`times_type` is not ``always`` the upper end of the time range. Must be lower than :confval:`time_lower`. The format is ``HH:mm`` in 24-hour clock. .. confval:: times_tz :type: string :required: false :default: the host's local time the timezone for :confval:`day`, :confval:`time_lower` and :confval:`time_upper` to be interpreted in. .. confval:: delay :type: boolean :required: false :default: ``false`` set to true to have the alerter send a "catch-up" alert about a failed monitor if it failed during a time the alerter was not allowed to send, and is still failed as the alerter enters the time it is allowed to send. If the monitor fails and recovers during the not-allowed time, no alert is sent either way. Time examples ^^^^^^^^^^^^^ These snippets omit the alerter-specific configuration values. Don't trigger during the hours I'm in the office (8:30am to 5:30pm, Monday to Friday): .. code-block:: ini [out_of_hours] type=some-alerter-type times_type=not time_lower=08:30 time_upper_17:30 days=0,1,2,3,4 Don't send at antisocial times, but let me know later if something broke and hasn't recovered yet: .. code-block:: ini [polite_alerter] type=some-alerter-type times_type=only time_lower=07:30 time_upper=22:00 delay=1 .. _alerters-list: Alerters -------- .. note:: The ``type`` of the alerter is the first word in its heading. .. toctree:: :glob: alerters/* simplemonitor-1.13.0/docs/alerters/000077500000000000000000000000001464501162400172515ustar00rootroot00000000000000simplemonitor-1.13.0/docs/alerters/46elks.rst000066400000000000000000000015011464501162400211100ustar00rootroot0000000000000046elks - 46elks notifications ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst You will need to register for an account at 46elks_. .. _46elks: https://46elks.com/ .. confval:: username :type: string :required: true your 46wlks username .. confval:: password :type: string :required: true your 46wlks password .. confval:: target :type: string :required: true 46elks target value .. confval:: sender :type: string :required: false :default: ``SmplMntr`` your SMS sender field. Start with a ``+`` if using a phone number. .. confval:: api_host :type: string :required: false :default: ``api.46elks.com`` API endpoint to use .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request simplemonitor-1.13.0/docs/alerters/bulksms.rst000066400000000000000000000014441464501162400214660ustar00rootroot00000000000000bulksms - SMS via BulkSMS ^^^^^^^^^^^^^^^^^^^^^^^^^ .. warning:: Do not commit your credentials to a public repo! .. confval:: sender :type: string :required: false :default: ``SmplMntr`` who the SMS should appear to be from. Max 11 chars, and best to stick to alphanumerics. .. confval:: username :type: string :required: true your BulkSMS username .. confval:: password :type: string :required: true your BulkSMS password .. confval:: target :type: string :required: true the number to send the SMS to. Specify using country code and number, with no ``+`` or international prefix. For example, ``447777123456`` for a UK mobile. .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request simplemonitor-1.13.0/docs/alerters/email.rst000066400000000000000000000022501464501162400210710ustar00rootroot00000000000000email - send via SMTP ^^^^^^^^^^^^^^^^^^^^^ .. warning:: Do not commit your credentials to a public repo! .. confval:: host :type: string :required: true the email server to connect to .. confval:: port :type: integer :required: false :default: ``25`` the port to connect on .. confval:: from :type: string :required: true the email address to give as the sender. You can use ``user@example.com`` or ``Some name `` formats. .. confval:: to :type: string :required: true the email address to send to. You can specify multiple addresses by separating with ``;``. .. confval:: cc :type: string :required: false the email address to cc to. You can specify multiple addresses by separating with ``;``. .. confval:: username :type: string :required: false the username to log in to the SMTP server with .. confval:: password :type: string :required: false the password to log in to the SMTP server with .. confval:: ssl :type: string :required: false specify ``starttls` to use StartTLS. Specify ``yes`` to use SMTP SSL. Otherwise, no SSL is used at all. simplemonitor-1.13.0/docs/alerters/execute.rst000066400000000000000000000034411464501162400214470ustar00rootroot00000000000000execute - run external command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. confval:: fail_command :type: string :required: false command to execute when a monitor fails .. confval:: success_command :type: string :required: false command to execute when a montior recovers .. confval:: catchup_command :type: string :required: false command to execute when exiting a time period when the alerter couldn't fire, a monitor failed during that time, and hasn't recovered yet. (See the :confval:`delay` configuration option.) If you specify the literal string ``fail_command``, this will share the :confval:`fail_command` configuration value. You can specify the following variable inside ``{curly brackets}`` to have them substituted when the command is executed: * ``hostname``: the host the monitor is running on * ``name``: the monitor's name * ``days``, ``hours``, ``minutes``, and ``seconds``: the monitor's downtime * ``failed_at``: the date and time the monitor failed * ``vitual_fail_count``: the monitor's virtual failure count (number of failed checks - :confval:`tolerance`) * ``info``: the additional information the monitor recorded about its status * ``description``: description of what the monitor is checking You will probably need to quote parameters to the command. For example:: fail_command=say "Oh no, monitor {name} has failed at {failed_at}" The commands are executed directly by Python. If you require shell features, such as piping and redirection, you should use something like ``bash -c "..."``. For example:: fail_command=/bin/bash -c "/usr/bin/printf \"The simplemonitor for {name} has failed on {hostname}.\n\nTime: {failed_at}\nInfo: {info}\n\" | /usr/bin/mailx -A gmail -s \"PROBLEM: simplemonitor {name} has failed on {hostname}.\" email@address" simplemonitor-1.13.0/docs/alerters/nc.rst000066400000000000000000000002211464501162400203760ustar00rootroot00000000000000nc - macOS notifications ^^^^^^^^^^^^^^^^^^^^^^^^ Publishes alerts to the macOS Notification Center. Only for macOS. No configuration options. simplemonitor-1.13.0/docs/alerters/nextcloud.rst000066400000000000000000000012411464501162400220060ustar00rootroot00000000000000nextcloud_notification - notifications ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst Send a notification to a Nextcloud_ server. .. _nextcloud: https://nextcloud.com/ .. confval:: token :type: string :required: true your nextcloud token .. confval:: user :type: string :required: true the admin user name .. confval:: server :type: string :required: true the nextcloud server .. confval:: server :type: string :required: true the user id who should receive the notification .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request simplemonitor-1.13.0/docs/alerters/ntfy.rst000066400000000000000000000030761464501162400207710ustar00rootroot00000000000000ntfy - ntfy notifications ^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst Send alerts using the ntfy_ service. .. _ntfy: https://ntfy.sh .. confval:: topic :type: string :required: true the ntfy topic to send to .. confval:: priority :type: str or int :required: false :default: ``default`` the priority to use for notifications. an int 1 to 5 (1 is lowest, 5 is highest), or one of ``max``, ``high``, ``default``, ``low``, ``min`` .. confval:: tags :type: string :required: false the tags to add to the notification. A comma-separated list of strings .. confval:: token :type: string :required: false your token for ntfy, if required .. confval:: server :type: string :required: false :default: ``https://ntfy.sh`` the server to send to .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request .. confval:: icon_prefix :type: bool :required: false :default: ``false`` Prefix the subject line with an icon dependent on the result (failed/succeeded) .. confval:: icon_failed :type: str :required: false :default: ``274C`` Unicode code for the "failed" icon. The code is often provided as "U+" (e.g. ``U+274C``). The default icon for the failed status is ❌. .. confval:: icon_succeeded :type: str :required: false :default: ``2705`` Unicode code for the "succeeded" icon. The code is often provided as "U+" (e.g. ``U+2705``). The default icon for the failed status is ✅. simplemonitor-1.13.0/docs/alerters/pushbullet.rst000066400000000000000000000006031464501162400221710ustar00rootroot00000000000000pushbullet - push notifications ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst You will need to be registered at pushbullet_. .. _pushbullet: https://www.pushbullet.com/ .. confval:: token :type: string :required: true your pushbullet token .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request simplemonitor-1.13.0/docs/alerters/pushover.rst000066400000000000000000000007031464501162400216560ustar00rootroot00000000000000pushover - notifications ^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst You will need to be registered at pushover_. .. _pushover: https://pushover.net/ .. confval:: user :type: string :required: true your pushover user key .. confval:: token :type: string :required: true your pushover app token .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request simplemonitor-1.13.0/docs/alerters/ses.rst000066400000000000000000000010321464501162400205710ustar00rootroot00000000000000ses - email via Amazon Simple Email Service ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst .. include:: ../aws-boilerplate.rst You will need to `verify an address or domain`_. .. _verify an address or domain: https://docs.aws.amazon.com/ses/latest/dg/verify-addresses-and-domains.html .. confval:: from :type: string :required: true the email address to send from .. confval:: to :type: string :required: true the email address to send to .. include:: ../aws-confvals.rst simplemonitor-1.13.0/docs/alerters/slack.rst000066400000000000000000000013551464501162400211040ustar00rootroot00000000000000slack - Slack webhook ^^^^^^^^^^^^^^^^^^^^^ .. warning:: Do not commit your credentials to a public repo! First, set up a webhook for this to use. * Go to https://slack.com/apps/manage * Add a new webhook * Configure it to taste (channel, name, icon) * Copy the webhook URL for your configuration below .. confval:: url :type: string :required: true the Slack webhook URL .. confval:: channel :type: string :required: false :default: the channel configured on the webhook the channel to send to .. confval:: username :type: string :required: false :default: a username to send to .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request to Slack simplemonitor-1.13.0/docs/alerters/sms77.rst000066400000000000000000000010211464501162400207550ustar00rootroot00000000000000sms77 - SMS via sms77 ^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst Send SMSes via the SMS77 service. .. confval:: api_key :type: string :required: true your API key for SMS77 .. confval:: target :type: string :required: true the target number to send to .. confval:: sender :type: string :required: false :default: ``SmplMntr`` the sender to use for the SMS .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request simplemonitor-1.13.0/docs/alerters/sns.rst000066400000000000000000000013261464501162400206100ustar00rootroot00000000000000sns - Amazon Simple Notification Service ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst .. include:: ../aws-boilerplate.rst Note that not all regions with SNS also support sending SMS. .. confval:: topic :type: string :required: yes, if ``number`` is not given the ARN of the SNS topic to publish to. Specify this, or ``number``, but not both. .. confval:: number :type: string :required: yes, if ``topic`` is not given the phone number to SMS. Give the number as country code then number, without a ``+`` or other international access code. For example, ``447777123456`` for a UK mobile. Specify this, or ``topic``, but not both. .. include:: ../aws-confvals.rst simplemonitor-1.13.0/docs/alerters/syslog.rst000066400000000000000000000001431464501162400213210ustar00rootroot00000000000000syslog - send to syslog ^^^^^^^^^^^^^^^^^^^^^^^ Syslog alerters have no additional configuration. simplemonitor-1.13.0/docs/alerters/telegram.rst000066400000000000000000000006061464501162400216050ustar00rootroot00000000000000telegram - send to a chat ^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst .. confval:: token :type: string :required: true the token to access Telegram .. confval:: chat_id :type: string :required: true the chat id to send to .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request to Telegram simplemonitor-1.13.0/docs/alerters/twilio_sms.rst000066400000000000000000000014471464501162400222020ustar00rootroot00000000000000twilio_sms - SMS via Twilio ^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst Send SMSes via the Twilio service. .. confval:: account_sid :type: string :required: true your account SID for Twilio .. confval:: auth_token :type: string :required: true your auth token for Twilio .. confval:: target :type: string :required: true the target number to send to. Format should be ``+`` followed by a country code and then the phone number .. confval:: sender :type: string :required: false :default: ``SmplMntr`` the sender to use for the SMS. Should be a number in the same format as the target parameter, or you may be able to use an `alphanumberic ID `_. simplemonitor-1.13.0/docs/aws-boilerplate.rst000066400000000000000000000004511464501162400212540ustar00rootroot00000000000000If you have AWS credentials configured elsewhere (e.g. in :file:`~/.aws/credentials`), or in the environment, this will use those and you do not need to specify credentials in your configuration file. As a best practice, use an IAM User/Role which is only allowed to access the resources in use. simplemonitor-1.13.0/docs/aws-confvals.rst000066400000000000000000000005061464501162400205660ustar00rootroot00000000000000.. confval:: aws_region :type: string :required: false the AWS region to use (e.g. ``eu-west-1``) .. confval:: aws_access_key :type: string :required: false the AWS access key to use .. confval:: aws_secret_access_key :type: string :required: false the AWS secret access key to use simplemonitor-1.13.0/docs/conf.py000066400000000000000000000050531464501162400167320ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) import sphinx_rtd_theme # noqa: F401 from sphinx.application import Sphinx from sphinx.util.docfields import Field # -- Project information ----------------------------------------------------- project = "SimpleMonitor" copyright = "2024, James Seward" author = "James Seward" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ["sphinx_rtd_theme", "sphinx.ext.intersphinx"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} # Borrowed from mypy's conf.py def setup(app: Sphinx) -> None: app.add_object_type( "confval", "confval", objname="configuration value", indextemplate="pair: %s; configuration value", doc_field_types=[ Field("type", label="Type", has_arg=False, names=("type",)), Field("default", label="Default", has_arg=False, names=("default",)), Field("required", label="Required", has_arg=True, names=("required",)), ], ) simplemonitor-1.13.0/docs/configuration.rst000066400000000000000000000151621464501162400210360ustar00rootroot00000000000000Configuration ============= The main configuration lives in :file:`monitor.ini`. By default, SimpleMonitor will look for it in the working directory when launched. To specify a different file, use the ``-f`` option. The format is fairly standard "INI"; section names are lowercase in ``[square brackets]``, and values inside the sections are defined as ``key=value``. You can use blank lines to space things out, and comments start with ``#``. Section names and option values, but not option names, support environment variable injection. To include the value of an environment variable, use ``%env:VARIABLE%``, which will inject the value of ``$VARAIBLE`` from the environment. You can use this to share a common configuration file across multiple hosts, for example. This main configuration file contains the global settings for SimpleMonitor, plus the logging and alerting configuration. A separate file, by default :file:`monitors.ini`, contains the monitor configuration. You can specify a different monitors configuration file using a directive in the main configuration. .. warning:: I know the configuration file names are dumb, sorry. .. _config-bytes: Configuration value types ------------------------- Values which take **bool** accept ``1``, ``yes``, and ``true`` as truthy, and everything else as falsey. Values which take **bytes** accept suffixes of ``K``, ``M``, or ``G`` for kibibytes, mibibytes or gibibytes, otherwise are just a number of bytes. ``monitor.ini`` --------------- This file must contain a ``[monitor]`` section, which must contain at least the ``interval`` setting. ``[monitor]`` section ^^^^^^^^^^^^^^^^^^^^^ .. confval:: interval :type: integer :required: true defines how many seconds to wait between running all the monitors. Note that the time taken to run the monitors is not subtracted from the interval, so the next iteration will run at interval + time_to_run_monitors seconds. .. confval:: monitors :type: string :required: false :default: ``monitors.ini`` the filename to load the monitors themselves from. Relative to the cwd, not the path of this configuration file. If you want to use only ``monitors_dir``, set this to nothing (``monitors=``). .. confval:: monitors_dir :type: string :required: false a directory to scan for ``*.ini`` files which are merged with the main monitors config file. Files are loaded in lexicographic order, and if a monitor name is reused, the last definition wins. Relative to the cwd, not the path of this configuration file. The main ``monitors`` file is always loaded first. .. confval:: pidfile :type: string :required: false :default: none the path to write a pidfile to. .. _config-remote: .. confval:: remote :type: bool :required: false :default: false enables the listener for receiving data from remote instances. Can be overridden to disabled with ``-N`` command line option. .. confval:: remote_port :type: integer :required: if ``remote`` is enabled the TCP port to listen on for remote data .. confval:: key :type: string :required: if ``remote`` is enabled shared secret for validating data from remote instances. .. confval:: bind_host :type: string :required: false :default: ``0.0.0.0`` (all interfaces) the local IP address to listen on, if ``remote`` is enabled. .. confval:: hup_file :type: string :required: false :default: none a file to watch the modification time on. If the modification time increases, SimpleMonitor :ref:`reloads its configuration`. .. tip:: SimpleMonitor will reload if it receives SIGHUP; this option is useful for platforms which don't have that. .. confval:: bind_host :type: string :required: false :default: all interfaces the local address to bind to for remote data ``[reporting]`` section ^^^^^^^^^^^^^^^^^^^^^^^ .. confval:: loggers :type: comma-separated list of string :required: false :default: none the names of the loggers you want to use. Each one must be a ``[section]`` in this configuration file. See Loggers for the common options and list of Alerters with their configurations. .. confval:: alerters :type: comma-separated list of string :required: false :default: none the names of the alerters you want to use. Each one must be a ``[section]`` in this configuration file. See Alerters for the common options and list of Alerters with their configurations. ``monitors.ini`` ---------------- This file only contains monitors. Each monitor is a ``[section]`` in the file, with the section name giving the monitor its name. The name ``defaults`` is reserved, and can be used to specify default values for options. Each monitor's individual configuration overrides the defaults. See Monitors for the common options and list of Monitors with their configurations. Example configuration --------------------- This is an example pair of configuration files to show what goes where. For more examples, see :ref:`Config examples`. :file:`monitor.ini`: .. code-block:: ini [monitor] interval=60 [reporting] loggers=logfile alerters=email,sms # write a log file with the state of each monitor, each time [logfile] type=logfile filename=monitor.log # email me when monitors fail or succeed [email] type=email host=mailserver.example.com from=monitor@example.com to=admin@example.com # send me an SMS after a monitor has failed 10 times in a row [sms] type=bulksms username=some-username password=some-password target=+447777123456 limit=10 :file:`monitors.ini`: .. code-block:: ini # check the webserver pings [www-ping] type=ping host=www.example.com # check the webserver answers https; don't bother checking if it's not pinging [www-http] type=http url=https://www.example.com depend=www-ping # check the root partition has at least 1GB of free space [root-diskspace] type=diskspace partition=/ limit=1G .. _Reloading: Reloading --------- You can send SimpleMonitor a SIGHUP to make it reload its configuration. On platforms which don't have that (e.g. Windows), you can specify a file to watch. If the modification time of the file changes, SimpleMonitor will reload its configuration. Reloading will pick up a change to ``interval`` but no other configuration in the ``[monitor]`` section. Monitors, Alerters and Loggers are reloaded. You can add and remove them, and change their configurations, but not change their types. (To change a type, first remove it from the configuration and reload, then add it back in.) simplemonitor-1.13.0/docs/creating-alerters.rst000066400000000000000000000063271464501162400216050ustar00rootroot00000000000000Creating Alerters ================= To create your own Alerter, you need to: 1. Create a Python file in :file:`simplemonitor/Alerters` (or pick a suitable existing one to add it to) 2. If you're creating a new file, you'll need a couple of imports: .. code-block:: python from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register 3. Define your alerter class, which should subclass ``Alerter`` and be decorated by ``@register``. Set a class attribute for the "type" which will be used in the alerter configuration to use it. .. code-block:: python @register class MyAlerter(Alerter): alerter_type = "my_alerter" 4. Define your initialiser. It should call the superclass's initialiser, and then read its configuration values from the supplied dict. You can also do any other initialisation here. This code should be safe to re-run, as if SimpleMonitor reloads its configuration, it will call ``__init__()`` with the new configuration dict. Use the :py:func:`get_config_option` helper to read config values. .. code-block:: python @register class MyAlerter(Alerter): alerter_type = "my_alerter" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.my_setting = self.get_config_option("setting", required=True) 5. Add a ``send_alerter`` function. This receives the information for a single monitor. You should first call ``self.should_alert(monitor)``, which will return the type of alert to be sent (e.g. failure). You should return if it is ``AlertType.NONE``. You should then prepare your message. Call ``self.build_message()`` to generate the message content. Check the value of ``self._dry_run`` and if it is True, you should log (using ``self.alerter_logger.info(...)``) what you would do, else you should do it. .. py:function:: Alerter.build_message(length: AlertLength, alert_type: AlertType, monitor: Monitor) -> str Generate a suitable length alert message for the given type of alert, for the given Monitor. :param AlertLength: one of the AlertLength enum values: ``NOTIFICATION`` (shortest), ``SMS`` (will be <= 140 chars), ``ONELINE``, ``TERSE`` (not currently supported), ``FULL``, or ``ESSAY`` :param AlertType: one of the AlertType enum values: ``NONE``, ``FAILURE``, ``CATCHUP``, or ``SUCCESS`` :param monitor: the Monitor to generate the message for :return: the built message :rtype: str :raises ValueError: if the AlertType is unknown :raises NotImplementedError: if the AlertLength is unknown or unsupported 7. You should also give a ``_describe_action`` function, which explains what this alerter does. Note that the time configuration for the alerter will be automatically added: .. code-block:: python @register class MyAlerter(Alerter): # ... def _describe_action(self) -> str: return f"sending FooAlerters to {self.recipient}" 7. In :file:`simplemonitor/Alerters/__init__.py`, add your Alerter to the list of imports. That's it! You should now be able to use ``type=my_alerter`` in your Alerters configuration to use your alerter. simplemonitor-1.13.0/docs/creating-loggers.rst000066400000000000000000000077361464501162400214330ustar00rootroot00000000000000Creating Loggers ================ Before writing your logger, you need to consider if you should support **batching** or not. If a logger supports batching, then it collects all the monitor results and then performs its logging action. For example, the HTML logger uses batching so that when it generates the HTML output, it knows all the monitors to include (and can sort them etc). Non-batching loggers will simply perform their logging action multiple times, once per monitor. To create your own Logger, you need to: 1. Create a Python file in :file:`simplemonitor/Loggers` (or pick a suitable existing one to add it to) 2. If you're creating a new file, you'll need a couple of imports: .. code-block:: python from ..Monitors.monitor import Monitor from .logger import Logger, register 3. Define your logger class, which should subclass ``Logger`` and be decorated by ``@register``. Set a class attribute for the "type" which will be used in the logger configuration to use it. Additionally, set the ``supports_batch`` value to indicate if your logger should be used in batching mode. .. code-block:: python @register class MyLogger(Logger): logger_type = "my_logger" supports_batch = True # or False 4. Define your initialiser. It should call the superclass's initialiser, and then read its configuration values from the supplied dict. You can also do any other initialisation here. This code should be safe to re-run, as if SimpleMonitor reloads its configuration, it will call ``__init__()`` with the new configuration dict. Use the :py:func:`get_config_option` helper to read config values. .. code-block:: python @register class MyLogger(Logger): logger_type = "my_logger" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.my_setting = self.get_config_option("setting", required=True) 5. Add a ``save_result2`` function (yes, I know). This receives the information for a single monitor. **Batching loggers** should save the information they need to into `self.batch_data`, which should (but does not have to be) a dict of `str: Any` using the monitor name as the key. This is automatically initialised to an empty dict at the start of the batch. You should extend the `start_batch` method from `Logger` to customise it. .. code-block:: python @register class MyLogger(Logger): # ... def save_result2(self, name: str, monitor: Monitor) -> None: self.batch_data[name] = monitor.state **Non-batching loggers** can perform whatever logging action they are designed for at this point. .. code-block:: python @register class MyLogger(Logger): # ... def save_result2(self, name: str, monitor: Monitor) -> None: self._my_logger_action(f"Monitor {name} is in state {monitor.state}") 6. **Batching loggers** only should provide a ``process_batch`` method, which is called after all the monitors have been processed. This is where you should perform your batched logging operation. .. code-block:: python @register class MyLogger(Logger): # ... def process_batch(self) -> None: with open(self.filename, "w") as file_handle: for monitor, state in self.batch_data.iteritems(): file_handle.write(f"Monitor {monitor} is in state {state}\n") 7. You should also give a ``describe`` function, which explains what this logger does: .. code-block:: python @register class MyLogger(Logger): # ... def describe(self) -> str: return f"writing monitor info to {self.filename}" 7. In :file:`simplemonitor/Loggers/__init__.py`, add your Logger to the list of imports. That's it! You should now be able to use ``type=my_thing`` in your Loggers configuration to use your logger. simplemonitor-1.13.0/docs/creating-monitors.rst000066400000000000000000000101011464501162400216170ustar00rootroot00000000000000Creating Monitors ================= To create your own Monitor, you need to: 1. Create a Python file in :file:`simplemonitor/Monitors` (or pick a suitable existing one to add it to) 2. If you're creating a new file, you'll need a couple of imports: .. code-block:: python from .monitor import Monitor, register 3. Define your monitor class, which should subclass ``Monitor`` and be decorated by ``@register``. Set a class attribute for the "type" which will be used in the monitor configuration to use it. .. code-block:: python @register class MonitorMyThing(Monitor): monitor_type = "my_thing" 4. Define your initialiser. It should call the superclass's initialiser, and then read its configuration values from the supplied dict. You can also do any other initialisation here. This code should be safe to re-run, as if SimpleMonitor reloads its configuration, it will call ``__init__()`` with the new configuration dict. Use the :py:func:`get_config_option` helper to read config values. .. code-block:: python @register class MonitorMyThing(Monitor): monitor_type = "my_thing" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.my_setting = self.get_config_option("my_setting", required=True) 5. Add a ``run_test`` function. This should perform the test for your monitor, and call ``record_fail()`` or ``record_success()`` as appropriate. It must also return ``False`` or ``True`` to match. The two ``record_*()`` methods return the right value, so you can just use them as the value to ``return``. You can use ``self.monitor_logger`` to perform logging (it's a standard Python :py:mod:`logging` object). You should catch any suitable exceptions and handle them as a failure of the monitor. The main loop will handle any uncaught exceptions and fail the monitor with a generic message. .. code-block:: python @register class MonitorMyThing(Monitor): # ... def run_test(self) -> bool: # my test logic here if test_succeeded: return self.record_success("it worked") return self.record_fail(f"failed with message {test_result}") 6. You should also give a ``describe`` function, which explains what this monitor is checking for: .. code-block:: python @register class MonitorMyThing(Monitor): # ... def describe(self) -> str: return f"checking that thing f{my_setting} does foo" 7. You should also provide a ``get_params()`` method that sends back a tuple of the configuration entries of your Monitor. It will be used by Loggers as an input of which information to log. .. code-block:: python @register class MonitorMyThing(Monitor): def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.some_configuration = cast(str, self.get_config_option("some_configuration")) self.some_other_configuration = cast(str, self.get_config_option("some_other_configuration")) # ... def get_params(self) -> Tuple: return ( self.some_configuration, self.some_other_configuration, ) 8. In :file:`simplemonitor/Monitors/__init__.py`, add your Monitor to the list of imports. That's it! You should now be able to use ``type=my_thing`` in your Monitors configuration to use your monitor. If you'd like to share your monitor back via a PR, please also: 1. Use type decorators, and verify with `mypy `_. You may need to use ``cast(TYPE, self.get_config_option(...))`` in your ``__init__()`` to get things to settle down. See existing monitors for examples. 2. Use `Black `_ to format the code. 3. Add documentation for your monitor. Create a file in `docs/monitors/` called `my_thing.rst` and follow the pattern in the other files to document it. There's a `pre-commit `_ configuration in the repo which you can use to check things over. simplemonitor-1.13.0/docs/creds-warning.rst000066400000000000000000000000761464501162400207300ustar00rootroot00000000000000.. warning:: Do not commit your credentials to a public repo! simplemonitor-1.13.0/docs/get-config-values.rst000066400000000000000000000027471464501162400215130ustar00rootroot00000000000000.. _get-config-option-helper: Getting configuration values ============================ When loading configuration values for Monitors, Alerters and Loggers, you can use the `get_config_option()` function to perform sanity checks on the loaded config. .. py:function:: get_config_option(config_options: dict, key: str, [default=None[, required=False[, required_type="str"[, allowed_values=None[, allow_empty=True[, minimum=None[,maximum=None]]]]]]]) Get a config value out of a dict, and perform basic validation on it. :param dict config_options: The dict to get the value from :param str key: The key to the value in the dict :param default: The default value to return if the key is not found :param bool required: Throw an exception if the value is not present (and default is None) :param str required_type: One of str, int, float, bool, [int] (list of int), [str] (list of str) :param allowed_values: A list of allowed values :param bool allow_empty: Allow the empty string when required_type is "str" :param minimum: The minimum allowed value for int and float :param maximum: The maximum allowed value for int and float :type minimum: integer, float or None :type maximum: integer, float or None :return: the fetched configuration value (or the default) Note that the return type of the function signature covers all supported types, so you should use :py:func:`typing.cast` to help mypy understand. Do not use :ref:`assert`. simplemonitor-1.13.0/docs/index.rst000066400000000000000000000120461464501162400172740ustar00rootroot00000000000000.. SimpleMonitor documentation master file, created by sphinx-quickstart on Sun Jan 31 19:23:11 2021. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to SimpleMonitor ======================== .. toctree:: :maxdepth: 2 :hidden: :caption: Getting started installation configuration .. toctree:: :maxdepth: 2 :hidden: :caption: Configuration Reference monitors alerters loggers configuration-examples .. toctree:: :maxdepth: 2 :hidden: :caption: Extending creating-monitors creating-alerters creating-loggers get-config-values SimpleMonitor is a Python script which monitors hosts and network connectivity and status. It is designed to be quick and easy to set up and lacks complex features that can make things like Nagios, OpenNMS and Zenoss overkill for a small business or home network. Remote monitor instances can send their results back to a central location. SimpleMonitor supports Python 3.6.2 and higher on Windows, Linux and FreeBSD. To get started, see :ref:`Installation`. Features ======== Things SimpleMonitor can monitor -------------------------------- For the complete list, see :ref:`Monitors`. * Host ping * Host open ports (TCP) * HTTP (is a URL fetchable without error? Does the page content it match a regular expression?) * DNS record return value * Services: Windows, Linux, FreeBSD services are supported * Disk space * File existence, age and time * FreeBSD portaudit (and pkg audit) for security notifications * Load average * Process existence * Exim queue size monitoring * APC UPS monitoring (requires apcupsd to be installed and configured) * Running an arbitrary command and checking the output * Compound monitors to combine any other types Adding your own Monitor type is straightforward with a bit of Python knowledge. Logging and Alerting -------------------- To SimpleMonitor, a Logger is something which reports the status of every monitor, each time it's checked. An Alerter sends a message about a monitor changing state. Some of the options include (for the complete list, see :ref:`Loggers` and :ref:`Alerters`): * Writing the state of each monitor at each iteration to a SQLite database * Sending an email alert when a monitor fails, and when it recovers, directly over SMTP or via Amazon SES * Writing a log file of all successes and failures, or just failures * Sending a message via BulkSMS, Amazon Simple Notification Service (SNS), Telegram, Slack, MQTT (with HomeBridge support) and more * Writing an HTML status page * Writing an entry to the syslog (non-Windows only) * Executing arbitrary commands on monitor failure and recovery Other features -------------- * Simple configuration file format: it’s a standard INI file for the overall configuration and another for the monitor definitions * Remote monitors: An instance running on a remote machine can send its results back to a central instance for central logging and alerting * Dependencies: Monitors can be declared as depending on the success of others. If a monitor fails, its dependencies will be skipped until it succeeds * Tolerance: Monitors checking things the other side of unreliable links or which have many transient failures can be configured to require their test to fail a number of times in a row before they report a problem * Escalation of alerts: Alerters can be configured to require a monitor to fail a number of times in a row (after its tolerance limit) before they fire, so alerts can be sent to additional addresses or people * Urgency: Monitors can be defined as non-urgent so that urgent alerting methods (like SMS) are not wasted on them * Per-host monitors: Define a monitor which should only run on a particular host and all other hosts will ignore it – so you can share one configuration file between all your hosts * Groups: Configure some Alerters to only react to some monitors * Monitor gaps: By default every monitor polls every interval (e.g. 60 seconds). Monitors can be given a gap between polls so that they only poll once a day (for example) * Alert periods: Alerters can be configured to only alert during certain times and/or on certain days * Alert catchup: ...and also to alert you to a monitor which failed when they were unable to tell you. (For example, I don’t want to be woken up overnight by an SMS, but if something’s still broken I’d like an SMS at 7am as I’m getting up.) Contributing ============ * Clone the GitHub repo * ``poetry install`` You can use `pre-commit `_ to ensure your code is up to my exacting standards ;) You can run tests with ``make unit-test``. See the Makefile for other useful targets. Licence ======= SimpleMonitor is released under the BSD Licence. Contact ======= * Open an issue or start a discussion on `GitHub `_ * Twitter: `@jamesoff `_ * Email: james at jamesoff dot net Indices and tables ================== * :ref:`genindex` * :ref:`search` simplemonitor-1.13.0/docs/installation.rst000066400000000000000000000060621464501162400206670ustar00rootroot00000000000000.. _Installation: Installation ============ SimpleMonitor is available via `PyPi `_:: pip install simplemonitor .. tip:: You may want to install it in a virtualenv, or you can use `pipx `_ which automatically manages virtualenvs for command-line tools. Create the configuration files: ``monitor.ini`` and ``monitors.ini``. See Configuration. .. warning:: I know the configuration file names are dumb, sorry. Running ------- Just run:: simplemonitor SimpleMonitor does not fork. For best results, run it with a service management tool such as daemontools, supervisor, or systemd. You can find some sample configurations for this purpose `on GitHub `_. SimpleMonitor will look for its configuration files in the current working directory. You can specify a different configuration file using ``-f``. You can verify the configuration files syntax with ``-t``. By default, SimpleMonitor's output is limited to errors and other issues, and it emits a ``.`` character every two loops. Use ``-H`` to disable the latter, and ``-v``, ``-d`` and ``-q`` (or ``-l``) to control the former. If you are using something like systemd or multilog which add their own timestamps to the start of the line, you may want ``--no-timestamps`` to avoid having unnecessary timestamps added. Command Line Options Reference ------------------------------ **General options** -h, --help show help message and exit --version show version number and exit **Execution options** -p PIDFILE, --pidfile PIDFILE Write PID into this file -N, --no-network Disable network listening socket (if enabled in config) -f CONFIG, --config CONFIG configuration file (this is the main config; you also need monitors.ini (default filename) -j THREADS, --threads THREADS number of threads to run for checking monitors (default is number of CPUs detected) **Output options** -v, --verbose Alias for ``--log-level=info`` -q, --quiet Alias for ``--log-level=critical`` -d, --debug Alias for ``--log-level=debug`` -H, --no-heartbeat Omit printing the ``.`` character when running checks -l LOGLEVEL, --log-level LOGLEVEL Log level: critical, error, warn, info, debug -C, --no-colour, --no-color Do not colourise log output --no-timestamps Do not prefix log output with timestamps **Testing options** -t, --test Test config and exit These options are really for testing SimpleMonitor itself, and you probably don't need them. -1, --one-shot Run the monitors once only, without alerting. Require monitors without "fail" in the name to succeed. Exit zero or non-zero accordingly. --loops LOOPS Number of iterations to run before exiting --dump-known-resources Print out loaded Monitor, Alerter and Logger types simplemonitor-1.13.0/docs/loggers.rst000066400000000000000000000034771464501162400176370ustar00rootroot00000000000000Logger Configuration ===================== Loggers record the state of every monitor after each interval. Loggers are defined in the main configuration file, which by default is :file:`monitor.ini`. The section name is the name of your logger, which you should then add to the ``loggers`` configuration value. .. contents:: Common options -------------- These options are common to all logger types. .. confval:: type :type: string :required: true the type of the logger; one of those in the list below. .. confval:: depend :type: comma-separated list of string :required: false :default: none a list of monitors this logger depends on. If any of them fail, no attempt will be made to log. .. confval:: groups :type: comma-separated list of string :required: false :default: ``default`` list of monitor groups this logger should record. Use the special value ``_all`` to match all groups. See the :ref:`group` setting for monitors. .. _logger-tz: .. confval:: tz :type: string :required: false :default: ``UTC`` the timezone to convert date/times to .. confval:: dateformat :type: string :required: false :default: ``timestamp`` the date format to write for log lines. (Note that the timezone is controlled by the :ref:`tz` configuration value.) Accepted values are: * ``timestamp`` (UNIX timestamp) * ``iso8601`` (``YYYY-MM-DDTHH:MM:SS``) .. confval:: heartbeat :type: bool :required: false :default: ``false`` if set, the logger only logs for monitors which executed on an iteration. Intended to be combined with the :ref:`gap` property of a Monitor. .. _loggers-list: Loggers ------- .. note:: The ``type`` of the logger is the first word in its heading. .. toctree:: :glob: loggers/* simplemonitor-1.13.0/docs/loggers/000077500000000000000000000000001464501162400170725ustar00rootroot00000000000000simplemonitor-1.13.0/docs/loggers/db.rst000066400000000000000000000007041464501162400202120ustar00rootroot00000000000000.. _logger-db: db - sqlite log of results ^^^^^^^^^^^^^^^^^^^^^^^^^^ Logs results to a SQLite database. The results are written to a table named ``results``. If you want to have a SQLite snapshot of the current state of the monitors (not a log of results), see the :ref:`dbstatus` logger. Automatically create the database schema. .. confval:: path :type: string :required: true the path to the database file to use simplemonitor-1.13.0/docs/loggers/dbstatus.rst000066400000000000000000000006771464501162400214670ustar00rootroot00000000000000.. _logger-dbstatus: dbstatus - sqlite status snapshot ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Stores a snapshot of monitor status in a SQLite database. The statuses are written to a table named ``status``. If you want to have a SQLite log of results (not a snapshot), see the :ref:`db` logger. Automatically creates the database schema. .. confval:: path :type: string :required: true the path to the database file to use simplemonitor-1.13.0/docs/loggers/html.rst000066400000000000000000000040601464501162400205700ustar00rootroot00000000000000.. _logger-html: html - HTML status page ^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst Writes an HTML status page. Can optionally display a map. The supplied template includes JavaScript to notify you if the page either doesn’t auto-refresh, or if SimpleMonitor has stopped updating it. This requires your machine running SimpleMonitor and the machine you are browsing from to agree on what the time is (timezone doesn’t matter)! The template is written using Jinja2. You can use the ``upload_command`` setting to specify a command to push the generated files to another location (e.g. a web server, an S3 bucket etc). I'd suggest putting the commands in a script and just specifying that script as the value for this setting. .. confval:: filename :type: string :required: true the html file to output. Will be stored in the ``folder`` .. confval:: folder :type: string :required: false :default: ``html`` the folder to write the output file(s) to. Must exist. .. confval:: copy_resources :type: boolean :required: false :default: true if true, copy supporting files (CSS, images, etc) to the ``folder`` .. confval:: source_folder :type: string :required: false the path to find the template and supporting files in. Defaults to those contained in the package. (In the package source, they are in :file:`simplemonitor/html/`.) .. confval:: upload_command :type: string :required: false if set, a command to execute each time the output is updated to e.g. upload the files to an external webserver .. confval:: map :type: boolean :required: false set to true to enable the map display instead of the table. You must set the :ref:`gps` value on your Monitors for them to show up! .. confval:: map_start :type: comma-separated list of float :required: if ``map`` is enabled three comma-separated values: the latitude the map display should start at, the longitude, and the zoom level. A good starting value for the zoom is probably between 10 and 15. simplemonitor-1.13.0/docs/loggers/json.rst000066400000000000000000000003071464501162400205750ustar00rootroot00000000000000json - write JSON status file ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Writes the status of monitors to a JSON file. .. confval:: filename :type: string :required: true the filename to write to simplemonitor-1.13.0/docs/loggers/logfile.rst000066400000000000000000000030251464501162400212450ustar00rootroot00000000000000logfile - write a logfile ^^^^^^^^^^^^^^^^^^^^^^^^^ Writes a log file of the status of monitors. The logfile format is:: datetime monitor-name: status; VFC=vfc (message) (execution-time) where the fields have the following meanings: datetime the datetime of the entry. Format is controlled by the ``dateformat`` configuration option. monitor-name the name of the monitor status either ``ok`` if the monitor succeeded, or ``failed since YYYY-MM-DD HH:MM:SS`` vfc the virtual failure count: the number of failures of the monitor beyond its :ref:`tolerance`. Not present for **ok** lines. message the message the monitor recorded as the reason for failure. Not present for **ok** lines. execution-time the time the monitor took to execute its check .. confval:: filename :type: string :required: true the filename to write to. Rotating this file underneath SimpleMonitor will likely result to breakage. If you would like the logfile to rotate automatically based on size or age, see the :ref:`logfileng` logger. .. confval:: buffered :type: boolean :required: false :default: true disable to use unbuffered writes to the logfile, allowing it to be watched in real time. Otherwise, you will find that updates don't appear in the file immediately. .. confval:: only_failures :type: boolean :required: false :default: false set to have only monitor failures written to the log file (almost, but not quite, turning it into an alerter) simplemonitor-1.13.0/docs/loggers/logfileng.rst000066400000000000000000000041501464501162400215720ustar00rootroot00000000000000.. _logger-logfileng: logfileng - write a logfile with rotation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Writes a log file of the status of monitors. Rotates and deletes old log files based on size or age. The logfile format is:: datetime monitor-name: status; VFC=vfc (message) (execution-time) where the fields have the following meanings: datetime the datetime of the entry. Format is controlled by the ``dateformat`` configuration option. monitor-name the name of the monitor status either ``ok`` if the monitor succeeded, or ``failed since YYYY-MM-DD HH:MM:SS`` vfc the virtual failure count: the number of failures of the monitor beyond its :ref:`tolerance`. Not present for **ok** lines. message the message the monitor recorded as the reason for failure. Not present for **ok** lines. execution-time the time the monitor took to execute its check .. confval:: filename :type: string :required: true the filename to write to. Rotated logs have either ``.N`` (where N is an incrementing number) or the date/time appended to the filename, depending on the rotation mode. .. confval:: rotation_type :type: string :required: true one of ``time`` or ``size`` .. confval:: when :type: string :required: false :default: ``h`` Only for rotation based on time. The units represented by ``interval``. One of ``s`` for seconds, ``m`` for minutes, ``h`` for hours, or ``d`` for days .. confval:: interval :type: integer :required: false :default: ``1`` Only for rotation based on time. The number of ``when`` between file rotations. .. confval:: max_bytes :type: :ref:`bytes` :required: yes, when rotation_type is ``size`` the maximum log file size before it is rotated. .. confval:: backup_count :type: integer :required: false :default: ``1`` the number of old files to keep .. confval:: only_failures :type: boolean :required: false :default: false set to have only monitor failures written to the log file (almost, but not quite, turning it into an alerter) simplemonitor-1.13.0/docs/loggers/mqtt.rst000066400000000000000000000017301464501162400206120ustar00rootroot00000000000000mqtt - send to MQTT server ^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst Sends monitor status to an MQTT server. Supports Home Assistant specifics (see https://www.home-assistant.io/docs/mqtt/discovery/ for more information). .. confval:: host :type: string :required: true the hostname/IP to connect to .. confval:: port :type: integer :required: false :default: ``1883`` the port to connect on .. confval:: hass :type: boolean :required: false :default: false enable Home Assistant specific features for MQTT discovery .. confval:: topic :type: string :required: false :default: see below the MQTT topic to post to. By default, if ``hass`` is not enabled, uses ``simplemonitor``, else ``homeassistant/binary_sensor`` .. confval:: username :type: string :required: false the username to use .. confval:: password :type: string :required: false the password to use simplemonitor-1.13.0/docs/loggers/network.rst000066400000000000000000000022161464501162400213160ustar00rootroot00000000000000network - remote SimpleMonitor logging ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../creds-warning.rst This logger is used to send status reports of all monitors to a remote instance. The remote instance must be configured to listen for connections. The ``key`` parameter is a shared secret used to generate a hash of the network traffic so the receiving instance knows to trust the data. .. warning:: Note that the traffic is not encrypted, just given a hash to validate it. The remote instance will need the ``remote``, ``remote_port``, and ``key`` :ref:`configuration values` set. If you want the remote instance to handle alerting for this instance's monitors, you need to set the :ref:`remote_alert` option on your monitors. This is a good candidate to go the ``[defaults]`` section of your monitors config file. .. confval:: host :type: string :required: true the remote hostname/IP to send to .. confval:: port :type: string :required: true the remote port to connect to .. confval:: key :type: string :required: true the shared secret to validate communications simplemonitor-1.13.0/docs/loggers/seq.rst000066400000000000000000000010641464501162400204150ustar00rootroot00000000000000seq - seq log server ^^^^^^^^^^^^^^^^^^^^ Sends the status of monitors to a **Seq** log server. See https://datalust.co/seq for more information on Seq. .. confval:: endpoint :type: string :required: true Full URI for the endpoint on the seq server, for example ``http:://localhost:5341/api/events/raw``. See the raw `API ingestion documentation ` for the curent endpoint URI. .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request to seq simplemonitor-1.13.0/docs/make.bat000066400000000000000000000014331464501162400170360ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd simplemonitor-1.13.0/docs/monitors.rst000066400000000000000000000101231464501162400200310ustar00rootroot00000000000000Monitor Configuration ===================== Monitors are defined in (by default) :file:`monitors.ini`. The monitor is named by its ``[section]`` heading. If you create a ``[defaults]`` section, the values are used as defaults for all the other monitors. Each monitor's configuration will override the values from the default. .. contents:: Common options -------------- These options are common to all monitor types. .. confval:: type :type: string :required: true the type of the monitor; one of those in the list below. .. confval:: runon :type: string :required: false :default: none a hostname on which the monitor should run. If not set, always runs. You can use this to share one config file among many hosts. (The value which is compared to is that returned by Python's :code:`socket.gethostname()`.) .. confval:: depend :type: comma-separated list of string :required: false :default: none the monitors on which this one depends. This monitor will run after those, unless one of them fails or is skipped, in which case this one will also skip. A skip does not trigger an alerter. .. _monitor-tolerance: .. confval:: tolerance :type: integer :required: false :default: 1 the number of times a monitor can fail before it enters the failed state. Handy for things which intermittently fail, such as unreliable links. The number of times the monitor has actually failed, minus this number, is its "Virtual Failure Count". See also the :ref:`limit` option on Alerters. .. confval:: urgent :type: boolean :required: false :default: true if this monitor is "urgent" or not. Non-urgent monitors do not trigger urgent alerters (e.g. BulkSMS) .. _gap: .. confval:: gap :type: integer :required: false :default: 0 the number of seconds this monitor should allow to pass before polling. Use it to make a monitor poll only once an hour (``3600``), for example. Setting this value lower than the ``interval`` will have no effect, and the monitor will run every loop like normal. Some monitors default to a higher value when it doesn't make sense to run their check too frequently because the underlying data will not change that often or quickly, such as :ref:`pkgaudit`. You can override their default to a lower value as required. .. hint:: Monitors which are in the failed state will poll every loop, regardless of this setting, in order to detect recovery as quickly as possible .. _monitor-remote-alert: .. confval:: remote_alert :type: boolean :required: false :default: false set to true to have this monitor's alerting handled by a remote instance instead of the local one. If you're using the remote feature, this is a good candidate to put in the ``[defaults]``. .. confval:: recover_command :type: string :required: false :default: none a command to execute once when this monitor enters the failed state. For example, it could attempt to restart a service. .. confval:: recovered_command :type: string :required: false :default: none a command to execute once when this monitor returns to the OK state. For example, it could restart a service which was affected by the failure of what this monitor checks. .. confval:: notify :type: boolean :required: false :default: true if this monitor should alert at all. .. _monitor-group: .. confval:: group :type: string :required: false :default: ``default`` the group the monitor belongs to. Alerters and Loggers will only fire for monitors which appear in their groups. .. confval:: failure_doc :type: string :required: false :default: none information to include in alerts on failure (e.g. a URL to a runbook) .. _monitor-gps: .. confval:: gps :type: string :required: no, unless you want to use the :ref:`html logger`'s map comma-separated latitude and longitude of this monitor .. _monitors-list: Monitors -------- .. note:: The ``type`` of the monitor is the first word in its heading. .. toctree:: :glob: monitors/* simplemonitor-1.13.0/docs/monitors/000077500000000000000000000000001464501162400173025ustar00rootroot00000000000000simplemonitor-1.13.0/docs/monitors/apcupsd.rst000066400000000000000000000006131464501162400214730ustar00rootroot00000000000000apcupsd - APC UPS status ^^^^^^^^^^^^^^^^^^^^^^^^ Uses an existing and configured ``apcupsd`` installation to check the UPS status. Any status other than ``ONLINE`` is a failure. .. confval:: path :type: string :required: false :default: none the path to the :file:`apcaccess` binary. On Windows, defaults to :file:`C:\\apcupsd\\bin`. On other platforms, looks in ``$PATH``. simplemonitor-1.13.0/docs/monitors/arlo_camera.rst000066400000000000000000000012301464501162400222750ustar00rootroot00000000000000arlo_camera - Arlo camera battery level ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Checks Arlo camera battery level is high enough. .. confval:: username :type: string :required: true Arlo username .. confval:: password :type: string :required: true Arlo password .. confval:: device_name :type: string :required: true the device to check (e.g. ``Front Camera``) .. confval:: base_station_id :type: integer :required: false :default: ``0`` the number of your base station. Only required if you have more than one. It's an array index, but figuring out which is which is an exercise left to the reader. simplemonitor-1.13.0/docs/monitors/command.rst000066400000000000000000000016121464501162400214520ustar00rootroot00000000000000command - run an external command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Run a command, and optionally verify its output. If the command exits non-zero, this monitor fails. .. confval:: command :type: string :required: true the command to run. .. confval:: result_regexp :type: string (regular expression) :required: false :default: none if supplied, the output of the command must match else the monitor fails. .. confval:: result_max :type: integer :required: false if supplied, the output of the command is evaluated as an integer and if greater than this, the monitor fails. If the output cannot be converted to an integer, the monitor fails. .. confval:: show_output :type: boolean :required: false if set to true, the output of the command will be captured as the message with a successful test or appended to the message on a failed test. simplemonitor-1.13.0/docs/monitors/compound.rst000066400000000000000000000014251464501162400216620ustar00rootroot00000000000000compound - combine monitors ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Combine (logical-and) multiple monitors. By default, if any monitor in the list is OK, this monitor is OK. If they all fail, this monitor fails. To change this limit use the ``min_fail`` setting. .. warning:: Do not specify the other monitors in this monitor's ``depends`` setting. The dependency handling for compound monitors is a special case and done for you. .. confval:: monitors :type: comma-separated list of string :required: true the monitors to combine .. confval:: min_fail :type: integer :required: false :default: the number of monitors in the list the number of monitors from the list which should be failed for this monitor to fail. The default is that all the monitors must fail. simplemonitor-1.13.0/docs/monitors/diskspace.rst000066400000000000000000000006661464501162400220120ustar00rootroot00000000000000diskspace - free disk space ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Checks the free space on the given partition/drive. .. confval:: partition :type: string :required: true the partition/drive to check. On Windows, give the drive letter (e.g. :file:`C:`). Otherwise, give the mountpoint (e.g. :file:`/usr`). .. confval:: limit :type: :ref:`bytes` :required: true the minimum allowed amount of free space. simplemonitor-1.13.0/docs/monitors/dns.rst000066400000000000000000000021321464501162400206160ustar00rootroot00000000000000dns - resolve record ^^^^^^^^^^^^^^^^^^^^ Attempts to resolve the DNS record, and optionally checks the result. Requires ``dig`` to be installed and on the PATH. .. confval:: record :type: string :required: true the DNS name to resolve .. confval:: record_type :type: string :required: false :default: ``A`` the type of record to request .. confval:: desired_val :type: string :required: false if not given, this monitor simply checks the record resolves. Give the special value ``NXDOMAIN`` to check the record **does not** resolve. If you need to check a multivalue response (e.g. MX records), format them like this (note the leading spaces on the continuation lines): .. code-block:: ini desired_val=10 a.mx.domain.com 20 b.mx.domain.com 30 c.mx.domain.com .. confval:: server :type: string :required: false the server to send the request to. If not given, uses the system default. .. confval:: port :type: integer :required: false :default: ``53`` the port on the DNS server to use simplemonitor-1.13.0/docs/monitors/eximqueue.rst000066400000000000000000000006141464501162400220440ustar00rootroot00000000000000eximqueue - Exim queue size ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Checks the output of ``exigrep`` to make sure the queue isn't too big. .. confval:: max_length :type: integer :required: false :default: ``1`` the maximum acceptable queue length .. confval:: path :type: string :required: false :default: ``/usr/local/sbin`` the path containing the ``exigrep`` binary simplemonitor-1.13.0/docs/monitors/fail.rst000066400000000000000000000002611464501162400207460ustar00rootroot00000000000000.. _fail: fail - alawys fails ^^^^^^^^^^^^^^^^^^^ This monitor fails 5 times in a row, then succeeds once. Use for testing. See the :ref:`null` monitor for the inverse. simplemonitor-1.13.0/docs/monitors/filestat.rst000066400000000000000000000010721464501162400216470ustar00rootroot00000000000000filestat - file size and age ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Examines a file's size and age. If neither of the age/size values are given, simply checks the file exists. .. confval:: filename :type: string :required: true the path of the file to monitor. .. confval:: maxage :type: integer :required: false the maximum allowed age of the file in seconds. If not given, not checked. .. confval:: minsize :type: :ref:`bytes` :required: false the minimum allowed size of the file in bytes. If not given, not checked. simplemonitor-1.13.0/docs/monitors/hass_sensor.rst000066400000000000000000000010141464501162400223570ustar00rootroot00000000000000hass_sensor - Home Automation Sensors ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This monitor checks for the existence of a home automation sensor. .. confval:: url :type: string :required: true API URL for the monitor .. confval:: sensor :type: string :required: true the name of the sensor .. confval:: token :type: string :required: true API token for the sensor .. confval:: timeout :type: int :required: false :default: ``5`` Timeout for HTTP request to HASS simplemonitor-1.13.0/docs/monitors/host.rst000066400000000000000000000017201464501162400210110ustar00rootroot00000000000000host - ping a host ^^^^^^^^^^^^^^^^^^ Check a host is pingable. .. tip:: This monitor relies on executing the ``ping`` command provided by your OS. It has known issues on non-English locales on Windows. You should use the :ref:`ping` monitor instead. The only reason to use this one is that it does not require SimpleMonitor to run as root. .. confval:: host :type: string :required: true the hostname/IP to ping .. confval:: ping_regexp :type: regexp :required: false :default: automatic the regexp which matches a successful ping. You may need to set this to use this monitor in a non-English locale. .. confval:: time_regexp :type: regexp :required: false :default: automatic the regexp which matches the ping time in the output. Must set a match group named ``ms``. You may need to set this as above. .. confval:: count :type: int :required: false :default: ``1`` the number of pings to send simplemonitor-1.13.0/docs/monitors/http.rst000066400000000000000000000025761464501162400210250ustar00rootroot00000000000000http - fetch and verify a URL ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Attempts to fetch a URL and makes sure the HTTP return code is (by default) 200/OK. Can also match the content of the page to a regular expression. .. confval:: url :type: string :required: true the URL to open .. confval:: regexp :type: regexp :required: false :default: none the regexp to look for in the body of the response .. confval:: allowed_codes :type: comma-separated list of integer :required: false :default: `200` a list of acceptable HTTP status codes .. confval:: allow_redirects :type: bool :required: false :default: `true` Follow redirects .. confval:: username :type: str :required: false :default: none Username for http basic auth .. confval:: password :type: str :required: false :default: none Password for http basic auth .. confval:: verify_hostname :type: boolean :required: false :default: true set to false to disable SSL hostname verification (e.g. with self-signed certificates) .. confval:: timeout :type: integer :required: false :default: ``5`` the timeout in seconds for the HTTP request to complete .. confval:: headers :type: JSON map as string :required: false :default: ``{}`` JSON map of HTTP header names and values to add to the request simplemonitor-1.13.0/docs/monitors/loadavg.rst000066400000000000000000000005511464501162400214520ustar00rootroot00000000000000loadavg - load average ^^^^^^^^^^^^^^^^^^^^^^ Check the load average on the host. .. confval:: which :type: integer :required: false :default: ``1`` the load average to monitor. ``0`` = 1min, ``1`` = 5min, ``2`` = 15min .. confval:: max :type: float :required: false :default: ``1.00`` the maximum acceptable load average simplemonitor-1.13.0/docs/monitors/memory.rst000066400000000000000000000003441464501162400213450ustar00rootroot00000000000000memory - free memory percent ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Check free memory percentage. .. confval:: percent_free :type: int :required: true the minimum percent of available (as per psutils’ definition) memory simplemonitor-1.13.0/docs/monitors/null.rst000066400000000000000000000003031464501162400210020ustar00rootroot00000000000000.. _null: null - always passes ^^^^^^^^^^^^^^^^^^^^ Monitor which always passes. Use for testing. See the :ref:`fail` monitor for the inverse. This monitor has no additional parameters. simplemonitor-1.13.0/docs/monitors/ping.rst000066400000000000000000000007661464501162400210020ustar00rootroot00000000000000.. _ping: ping - ping a host ^^^^^^^^^^^^^^^^^^ Pings a host to make sure it’s up. Uses a Python ping module instead of calling out to an external app, but needs to be run as root. .. confval:: host :type: string :required: true the hostname or IP to ping .. confval:: timeout :type: int :required: false :default: ``5`` the timeout for the ping in seconds .. confval:: count :type: int :required: false :default: ``1`` the number of pings to send simplemonitor-1.13.0/docs/monitors/pkgaudit.rst000066400000000000000000000004061464501162400216440ustar00rootroot00000000000000pkgaudit - FreeBSD pkg audit ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Fails if ``pkg audit`` reports any vulnerable packages installed. .. confval:: path :type: string :required: false :default: :file:`/usr/local/sbin/pkg` the path to the ``pkg`` binary simplemonitor-1.13.0/docs/monitors/portaudit.rst000066400000000000000000000004231464501162400220460ustar00rootroot00000000000000portaudit - FreeBSD port audit ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Fails if ``portaudit`` reports any vulnerable ports installed. .. confval:: path :type: string :required: false :default: :file:`/usr/local/sbin/portaudit` the path to the ``portaudit`` binary simplemonitor-1.13.0/docs/monitors/process.rst000066400000000000000000000011121464501162400215050ustar00rootroot00000000000000process - running process ^^^^^^^^^^^^^^^^^^^^^^^^^ Check for a running process. .. confval:: process_name :type: string :required: true the process name to check for .. confval:: min_count :type: integer :required: false :default: ``1`` the minimum number of matching processes .. confval:: max_count :type: integer :required: false :default: infinity the maximum number of matching processes .. confval:: username :type: string :required: false :default: any user limit matches to processes owned by this user. simplemonitor-1.13.0/docs/monitors/rc.rst000066400000000000000000000014061464501162400204410ustar00rootroot00000000000000rc - FreeBSD rc service ^^^^^^^^^^^^^^^^^^^^^^^ Checks a FreeBSD-style service is running, by running its rc script (in /usr/local/etc/rc.d) with the status command. .. tip:: You may want the :ref:`unix_service` monitor for a more generic check. .. confval:: service :type: string :required: true the name of the service to check. Should be the name of the rc.d script in :file:`/usr/local/etc/rc.d`. Any trailing ``.sh`` is optional and added if needed. .. confval:: path :type: string :required: false :default: :file:`/usr/local/etc/rc.d` the path of the folder containing the rc script. .. confval:: return_code :type: integer :required: false :default: ``0`` the required return code from the script simplemonitor-1.13.0/docs/monitors/ring_doorbell.rst000066400000000000000000000015471464501162400226640ustar00rootroot00000000000000ring_doorbell - Ring doorbell battery ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Check the battery level of a Ring doorbell. .. confval:: device_name :type: string :required: true the name of the Ring Doorbell to monitor. .. confval:: minimum_battery :type: integer :required: false :default: ``25`` the minimum battery percent allowed. .. confval:: username :type: string :required: true your Ring username (e.g. email address). Accounts using MFA are not supported. You can create a separate user for API access. .. confval:: password :type: string :required: true your Ring password. .. warning:: Do not commit credentials to source control! .. confval:: device_type :type: string :required: false :default: ``doorbell`` the device type. Acceptable values are ``doorbell`` or ``camera``. simplemonitor-1.13.0/docs/monitors/service.rst000066400000000000000000000013021464501162400214700ustar00rootroot00000000000000service - Windows Service ^^^^^^^^^^^^^^^^^^^^^^^^^ Checks a Windows service to make sure it's in the correct state. .. confval:: service :type: string :required: true the short name of the service to monitor (this is the "Service Name" on the General tab of the service Properties in the Services MMC snap-in). .. confval:: want_state :type: string :required: false :default: ``RUNNING`` the required status for the service. One of: * ``RUNNING`` * ``STOPPED`` * ``PAUSED`` * ``START_PENDING`` * ``PAUSE_PENDING`` * ``CONTINUE_PENDING`` * ``STOP_PENDING`` .. tip:: version 1.9 and earlier had a **host** parameter, which is no longer used. simplemonitor-1.13.0/docs/monitors/svc.rst000066400000000000000000000003751464501162400206340ustar00rootroot00000000000000svc - daemontools service ^^^^^^^^^^^^^^^^^^^^^^^^^ Checks a daemontools ``supervise``-managed service is running. .. confval:: path :type: string :required: true the path to the service's directory (e.g. :file:`/var/service/something`) simplemonitor-1.13.0/docs/monitors/swap.rst000066400000000000000000000003071464501162400210060ustar00rootroot00000000000000swap - available swap space ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Checks for available swap space. .. confval:: percent_free :type: integer :required: true minimum acceptable free swap percent simplemonitor-1.13.0/docs/monitors/systemd-unit.rst000066400000000000000000000014261464501162400225040ustar00rootroot00000000000000systemd-unit - systemd unit check ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Monitors a systemd unit status, via dbus. You may want the :ref:`unix_service` monitor instead if you just want to ensure a service is running. .. confval:: name :type: string :required: true the name of the unit to monitor .. confval:: load_states :type: comma-separated list of string :required: false :default: ``loaded`` desired load states for the unit .. confval:: active_states :type: comma-separated list of string :required: false :default: ``active,reloading`` desired active states for the unit .. confval:: sub_states :type: comma-separated list of string :required: false :default: none desired sub states for the service simplemonitor-1.13.0/docs/monitors/tcp.rst000066400000000000000000000005111464501162400206170ustar00rootroot00000000000000tcp - open TCP port ^^^^^^^^^^^^^^^^^^^ Checks a TCP port is connectible. Doesn't care what happens after the connection is opened. .. confval:: host :type: string :required: true the name/IP of the host to connect to .. confval:: port :type: integer :required: true the port number to connect to. simplemonitor-1.13.0/docs/monitors/tls_expiry.rst000066400000000000000000000016111464501162400222350ustar00rootroot00000000000000tls_expiry - TLS cert expiration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Checks an SSL/TLS certificate is not due to expire/has expired. .. note:: This monitor's :ref:`gap` defaults to 12 hours. .. warning:: Due to a limitation of the underlying Python modules in use, this does not currently support TLS 1.3. .. confval:: host :type: string :required: true the hostname to connect to .. confval:: port :type: integer :required: false :default: ``443`` the port number to connect on .. confval:: min_days :type: integer :required: false :default: ``7`` the minimum allowable number of days until expiry .. confval:: sni :type: string :required: false the hostname to send during TLS handshake for SNI. Use if you are serving multiple certificates from the same host/port. If empty, will just get the default certificate from the server simplemonitor-1.13.0/docs/monitors/unifi_failover.rst000066400000000000000000000017031464501162400230360ustar00rootroot00000000000000unifi_failover - USG failover WAN status ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Checks a Unifi Security Gateway for failover WAN status. Connects via SSH; the USG must be in your :file:`known_hosts` file. Requires the specified interface to have the carrier up, a gateway, and not be in the ``failover`` state. .. confval:: router_address :type: string :required: true the address of the USG .. confval:: router_username :type: string :required: true the username to log in as .. confval:: router_password :type: string :required: conditional the password to log in with. Required if not using ``ssh_key``. .. confval:: ssh_key :type: string :required: conditional path to the SSH private key to log in with. Required if not using ``router_password``. .. confval:: check_interface :type: string :required: false :default: ``eth2`` the interface which should be ready for failover. simplemonitor-1.13.0/docs/monitors/unifi_watchdog.rst000066400000000000000000000021301464501162400230220ustar00rootroot00000000000000unifi_watchdog - USG failover watchdog ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Checks a Unifi Security Gateway to make sure the failover WAN is healthy. Connects via SSH; the USG must be in your :file:`known_hosts` file. Requires the specified interface to have status ``Running`` and the ping target to be ``REACHABLE``. .. confval:: router_address :type: string :required: true the address of the USG .. confval:: router_username :type: string :required: true the username to log in as .. confval:: router_password :type: string :required: conditional the password to log in with. Required if not using ``ssh_key``. .. confval:: ssh_key :type: string :required: conditional path to the SSH private key to log in with. Required if not using ``router_password``. .. confval:: primary_interface :type: string :required: false :default: ``pppoe0`` the primary WAN interface .. confval:: secondary_interface :type: string :required: false :default: ``eth2`` the secondary (failover) WAN interface .. _unix_service: simplemonitor-1.13.0/docs/monitors/unix_service.rst000066400000000000000000000006651464501162400225460ustar00rootroot00000000000000unix_service - generic UNIX service ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Generic UNIX service check, by running ``service ... status``. .. confval:: service :type: string :required: true the name of the service to check .. confval:: state :type: string :required: false :default: ``running`` the state of the service; either ``running`` (status command exits 0) or ``stopped`` (status command exits 1). simplemonitor-1.13.0/docs/requirements.txt000066400000000000000000000003121464501162400207100ustar00rootroot00000000000000sphinx==7.2.6 sphinx_rtd_theme==2.0.0 setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability simplemonitor-1.13.0/docs/simplemonitor.1000066400000000000000000000036101464501162400204130ustar00rootroot00000000000000.TH SIMPLEMONITOR 1 .SH NAME simplemonitor \- Monitor hosts status and network connectivity .SH SYNOPSIS \fBsimplemonitor [OPTIONS]\fR .SH DESCRIPTION simplemonitor monitors hosts (disk space, load average, services, HTTP availability and much more) and network connectivity (based on ping replies). This manual page is for the \fBsimplemonitor\fR executable options. The full documentation for how to add monitors and alerters can be found in \fI/usr/share/doc/simplemonitor/html\fR. .SH OPTIONS .TP \fB-h, --help\fR Show help and exit .TP \fB-p PIDFILE, --pidfile PIDFILE\fR Write PID into PIDFILE .TP \fB-N, --no-network\fR Disable network listening socket (if enabled in config) .TP \fB-f CONFIG, --config CONFIG\fR Configuration file (this is the main config; monitors.ini is also needed (default filename)) .SH OUTPUT CONTROLS .TP \fB-v, --verbose\fR Alias for --log-level=info .TP \fB-q, --quiet\fR Alias for --log-level=critical .TP \fB-d, --debug\fR Alias for --log-level=debug .TP \fB-l LOGLEVEL, --log-level LOGLEVEL\fR Log level: critical, error, warn, info, debug .TP \fB-C, --no-colour, --no-color\fR Do not colourise log output .TP \fB--no-timestamps\fR Do not prefix log output with timestamps .SH TEST AND DEBUG TOOLS .TP \fB-t, --test\fR Test config and exit .TP \fB-l, --one-shot\fR Run the monitors once only, without alerting. Require monitors without "fail" in the name, to succeed. Exit zero or non-zero accordingly .TP \fB--loops LOOPS\fR Number of iterations to run before exiting .TP \fB--dump-known-resources\fR Print out loaded Monitor, Alerter and Logger types .SH AUTHOR This manual page was written by Carles Pina i Estany for the \fBDebian\fR system (but may be used by others). Permission is granted to copy, distribute and/or modify this document under the terms of the BSD-3-clause. .SH SEE ALSO Full documentation in \fI/usr/share/doc/simplemonitor/html\fP simplemonitor-1.13.0/monitor.py000066400000000000000000000000621464501162400165370ustar00rootroot00000000000000from simplemonitor import monitor monitor.main() simplemonitor-1.13.0/monitors.ini-dist000066400000000000000000000000611464501162400200110ustar00rootroot00000000000000[localhost] type=host host=localhost tolerance=2 simplemonitor-1.13.0/monitors.ini-sample000066400000000000000000000000611464501162400203270ustar00rootroot00000000000000[localhost] type=host host=localhost tolerance=2 simplemonitor-1.13.0/old_docs/000077500000000000000000000000001464501162400162665ustar00rootroot00000000000000simplemonitor-1.13.0/old_docs/Gemfile000066400000000000000000000003111464501162400175540ustar00rootroot00000000000000source 'https://rubygems.org' gem 'github-pages', '>= 224' gem 'rouge' gem "jekyll", ">= 3.9.1" gem "ffi", ">= 1.9.24" gem "nokogiri", ">= 1.8.5" gem "yajl-ruby", ">= 1.3.1" gem "kramdown", ">= 2.3.1" simplemonitor-1.13.0/old_docs/Gemfile.lock000066400000000000000000000170071464501162400205150ustar00rootroot00000000000000GEM remote: https://rubygems.org/ specs: activesupport (7.1.2) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) minitest (>= 5.1) mutex_m tzinfo (~> 2.0) addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) base64 (0.2.0) bigdecimal (3.1.4) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) commonmarker (0.23.10) concurrent-ruby (1.2.2) connection_pool (2.4.1) dnsruby (1.70.0) simpleidn (~> 0.2.1) drb (2.2.0) ruby2_keywords em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) execjs (2.9.1) faraday (2.7.11) base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) ffi (1.16.3) forwardable-extended (2.6.0) gemoji (3.0.1) github-pages (228) github-pages-health-check (= 1.17.9) jekyll (= 3.9.3) jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) jekyll-commonmark-ghpages (= 0.4.0) jekyll-default-layout (= 0.1.4) jekyll-feed (= 0.15.1) jekyll-gist (= 1.5.0) jekyll-github-metadata (= 2.13.0) jekyll-include-cache (= 0.2.1) jekyll-mentions (= 1.6.0) jekyll-optional-front-matter (= 0.3.2) jekyll-paginate (= 1.1.0) jekyll-readme-index (= 0.3.0) jekyll-redirect-from (= 0.16.0) jekyll-relative-links (= 0.6.1) jekyll-remote-theme (= 0.4.3) jekyll-sass-converter (= 1.5.2) jekyll-seo-tag (= 2.8.0) jekyll-sitemap (= 1.4.0) jekyll-swiss (= 1.0.0) jekyll-theme-architect (= 0.2.0) jekyll-theme-cayman (= 0.2.0) jekyll-theme-dinky (= 0.2.0) jekyll-theme-hacker (= 0.2.0) jekyll-theme-leap-day (= 0.2.0) jekyll-theme-merlot (= 0.2.0) jekyll-theme-midnight (= 0.2.0) jekyll-theme-minimal (= 0.2.0) jekyll-theme-modernist (= 0.2.0) jekyll-theme-primer (= 0.6.0) jekyll-theme-slate (= 0.2.0) jekyll-theme-tactile (= 0.2.0) jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) jemoji (= 0.12.0) kramdown (= 2.3.2) kramdown-parser-gfm (= 1.1.0) liquid (= 4.0.4) mercenary (~> 0.3) minima (= 2.5.1) nokogiri (>= 1.13.6, < 2.0) rouge (= 3.26.0) terminal-table (~> 1.4) github-pages-health-check (1.17.9) addressable (~> 2.3) dnsruby (~> 1.60) octokit (~> 4.0) public_suffix (>= 3.0, < 5.0) typhoeus (~> 1.3) html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) i18n (1.14.1) concurrent-ruby (~> 1.0) jekyll (3.9.3) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) jekyll-avatar (0.7.0) jekyll (>= 3.0, < 5.0) jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) coffee-script-source (~> 1.11.1) jekyll-commonmark (1.4.0) commonmarker (~> 0.22) jekyll-commonmark-ghpages (0.4.0) commonmarker (~> 0.23.7) jekyll (~> 3.9.0) jekyll-commonmark (~> 1.4.0) rouge (>= 2.0, < 5.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) jekyll-feed (0.15.1) jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) jekyll-github-metadata (2.13.0) jekyll (>= 3.4, < 5.0) octokit (~> 4.0, != 4.4.0) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) jekyll-mentions (1.6.0) html-pipeline (~> 2.3) jekyll (>= 3.7, < 5.0) jekyll-optional-front-matter (0.3.2) jekyll (>= 3.0, < 5.0) jekyll-paginate (1.1.0) jekyll-readme-index (0.3.0) jekyll (>= 3.0, < 5.0) jekyll-redirect-from (0.16.0) jekyll (>= 3.3, < 5.0) jekyll-relative-links (0.6.1) jekyll (>= 3.3, < 5.0) jekyll-remote-theme (0.4.3) addressable (~> 2.0) jekyll (>= 3.5, < 5.0) jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) rubyzip (>= 1.3.0, < 3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) jekyll-seo-tag (2.8.0) jekyll (>= 3.8, < 5.0) jekyll-sitemap (1.4.0) jekyll (>= 3.7, < 5.0) jekyll-swiss (1.0.0) jekyll-theme-architect (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-cayman (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-dinky (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-hacker (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-leap-day (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-merlot (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-midnight (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-minimal (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-modernist (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-primer (0.6.0) jekyll (> 3.5, < 5.0) jekyll-github-metadata (~> 2.9) jekyll-seo-tag (~> 2.0) jekyll-theme-slate (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-tactile (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-time-machine (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-titles-from-headings (0.5.3) jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) jemoji (0.12.0) gemoji (~> 3.0) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) kramdown (2.3.2) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) liquid (4.0.4) listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) mini_portile2 (2.8.6) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.20.0) mutex_m (0.2.0) nokogiri (1.16.5) mini_portile2 (~> 2.8.2) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (4.0.7) racc (1.7.3) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) rexml (3.2.8) strscan (>= 3.0.9) rouge (3.26.0) ruby2_keywords (0.0.5) rubyzip (2.3.2) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) simpleidn (0.2.1) unf (~> 0.1.4) strscan (3.1.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) typhoeus (1.4.0) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unf (0.1.4) unf_ext unf_ext (0.0.9) unicode-display_width (1.8.0) yajl-ruby (1.4.3) PLATFORMS ruby DEPENDENCIES ffi (>= 1.9.24) github-pages (>= 224) jekyll (>= 3.9.1) kramdown (>= 2.3.1) nokogiri (>= 1.8.5) rouge yajl-ruby (>= 1.3.1) BUNDLED WITH 1.17.3 simplemonitor-1.13.0/old_docs/_config.yml000066400000000000000000000001031464501162400204070ustar00rootroot00000000000000--- highlighter: rouge baseurl: /simplemonitor exclude: - vendor simplemonitor-1.13.0/old_docs/_data/000077500000000000000000000000001464501162400173365ustar00rootroot00000000000000simplemonitor-1.13.0/old_docs/_data/monitors.yml000066400000000000000000000301771464501162400217430ustar00rootroot00000000000000- name: host oneline: Pings a host (once per iteration) to see if it’s available. Multiplatform, but can break on non-English ping output without additional config. See also the "ping" monitor. params: - name: host desc: The hostname to ping. required: 'yes' - name: ping_regexp desc: The regexp which matches a successful ping line. You may need to set this if your ping output is not in English required: 'no' default: auto - name: time_regexp desc: The regexp which matches the ping time in the output. Must set a match group named "ms". you may need to set this if your ping output is not in English. required: 'no' default: auto - name: service oneline: Checks a Windows service to make sure it’s running. Windows only. params: - name: service desc: The short name of the service to monitor. This is the “Service name” on the General tab of the service properties (in the Services MMC snap-in). required: 'yes' - name: host desc: The hostname to check the service on. required: 'no' default: localhost - name: tcp oneline: Checks that a TCP port is open. Doesn’t care what happens after the connection is opened. Multiplatform. params: - name: host desc: The name of the host to connect to. required: 'yes' - name: port desc: The port to connect to. Integer only (no service names). required: 'yes' - name: rc oneline: Checks a FreeBSD-style service is running, by running its rc script (in /usr/local/etc/rc.d) with the `status` command. May work for other types of rc.d/init.d system. Not for Windows. params: - name: service desc: The name of the service to check. This is the name of the rc.d script in /usr/local/etc/rc.d/. Any trailing “.sh” is optional and will be added if needed. required: 'yes' - name: path desc: The path of the folder containing the rc script required: 'no' default: /usr/local/etc/rc.d - name: return_code desc: The integer return code required from the script required: 'no' default: '0' - name: svc oneline: Checks a supervise service is running. Not for Windows. params: - name: path desc: The path to the service’s directory (e.g. `/var/service/something`). required: 'yes' - name: diskspace oneline: Checks the free space on a partition is above a given limit. Multiplatform. params: - name: partition desc: The partition to check for space on. On Windows, this is the drive letter (e.g. C:). On non-Windows, this is the mount point (e.g. /usr). required: 'yes' - name: limit desc: The minimum amount of free space. Give a number in bytes, or suffix K, M or G for kilobytes, megabytes or gigabytes. Required, no default. required: 'yes' - name: http oneline: Attempts to fetch a URL and makes sure the HTTP return code is 200 OK. Can also look through the content of the page trying to match a regular expression. Multiplatform. params: - name: url desc: The URL to open. required: 'yes' - name: regexp desc: The regexp to look for in the page (only if the page loads with status `200 OK`). If the regexp does not match, the monitor reports a failure. See Python’s `re` module for syntax. required: 'no' default: 'none' - name: allowed_codes desc: A list of HTTP codes which are acceptable in addition to `200 OK` required: 'no' - name: verify_hostname desc: If set to false, no SSL hostname verification will be made. Use with the https protocol and self-signed certificates. required: 'no' default: 'True' - name: timeout desc: The timeout for the HTTP request to complete required: 'no' default: '5' - name: headers desc: JSON map of HTTP header names and values to add to the request required: 'no' - name: dns oneline: Attempts to resolve a DNS record, and optionally checks the result. Requires the DNS utility `dig` to be in the `$PATH`. params: - name: record desc: The DNS name to resolve. required: 'yes' - name: record_type desc: The type of the record. required: 'no' default: 'A' - name: desired_val desc: > The expected value for the record to resolve to. For results with newlines (e.g. MX records), you should format them like: desired_val: 10 a.mx.domain.com 20 b.mx.domain.com 30 c.mx.domain.com Note the leading spaces on the continuation lines. required: 'no' - name: server desc: The server to send the request to. If absent, the system default is used. required: 'no' - name: apcupsd oneline: Uses (an existing and correctly configured) apcupsd to check that a UPS is not running from batteries or having some other problem. Multiplatform. params: - name: path desc: The path to the `apcaccess` binary. You should only need to specify this if you’ve installed apcupsd somewhere exotic. required: 'no' default: 'UNIX: $PATH; Windows: C:\apcupsd\bin' - name: fail oneline: This monitor fails 5 times in a row and then succeeds once. Use for testing. Multiplatform. - name: portaudit oneline: Fails if `portaudit` reports any vulnerable ports installed. params: - name: path desc: The path for for the portaudit binary. required: 'no' default: '/usr/local/sbin/portaudit' - name: pkgaudit oneline: Fails if `pkg audit` reports any vulnerable packages installed. params: - name: path desc: The path to the package binary. required: 'no' default: '/usr/local/sbin/pkg' - name: loadavg oneline: Check the load average on the host. params: - name: which desc: 'The load average to monitor: 0 = 1min, 1 = 5min, 2 = 15min' required: 'no' default: '1' - name: max desc: The maximum acceptable value for the given load average. required: 'no' default: '1.00' - name: command oneline: 'Run a command and optionally verify the output. If the command exits non-zero, the monitor fails.' params: - name: command desc: The command (and params) to execute required: 'yes' - name: result_regexp desc: A regular expression against which the output of the command is matched. required: 'no' - name: result_max desc: 'A maximum value for the command to output (on stdout)' required: 'no' - name: compound oneline: Combine (logical-and) multiple failures of other monitors for emergency escalation params: - name: monitors desc: A comma-separated list of other monitors required: 'yes' - name: min_fail desc: Number of monitors which should fail for this monitor to fail too default: all required: 'no' - name: filestat oneline: Examine size and age of a file params: - name: filename desc: The path to the file to monitor required: 'yes' - name: maxage desc: Maximum allowed age of the file in seconds required: 'no' default: None; age is ignored - name: minsize desc: Minimum allowed size of the file in bytes; can be expressed using "KB" etc suffixes required: 'no' default: None; size is ignored - name: hass_sensor oneline: Monitor the existence of a home automation sensor params: - name: url desc: The API URL for the monitor required: 'yes' - name: sensor desc: Name of the sensor required: 'yes' - name: token desc: API token for the sensor required: 'yes' - name: 'null' oneline: Monitor which always passes. Use for testing. - name: systemd-unit oneline: Monitor a systemd unit status params: - name: name desc: The name of the unit to monitor required: 'yes' - name: load_states desc: Comma-separated list of desired load states for the unit required: 'no' default: "`loaded`" - name: active_states desc: Comma-separated list of desired active states for the unit required: 'no' default: "`active, reloading`" - name: sub_states desc: Comma-separates list of desired sub states for the unit required: 'no' - name: ring oneline: Check battery level of Ring Doorbell params: - name: device_name desc: The name of the Ring Doorbell to monitor required: 'yes' - name: minimum_battery desc: The minimum battery percent allowed required: 'no' default: 25 - name: username desc: Your Ring username (e.g. email address). Accounts using MFA are not supported. You can create a separate account for API access. required: 'yes' - name: password desc: Your Ring password required: 'yes' - name: memory oneline: Check free memory percentage params: - name: percent_free desc: The minimum percent of available (as per psutils' definition) memory required: 'yes' - name: arlo_camera oneline: Check Arlo camera battery level params: - name: username desc: Arlo username required: 'yes' - name: password desc: Arlo password required: 'yes' - name: device_name desc: Camera device name (e.g. "Front") required: 'yes' - name: base_station_id desc: The number of your base station; only required if you have more than one. It's an array index, but figuring out which one is which is an exercise left the reader required: 'no' default: 0 - name: unix_service oneline: Check a generic unix service with the "service" command params: - name: service desc: Name of the service to check required: 'yes' - name: state desc: The state the service should be in; either `running` (command exits 0) or `stopped` (command exits 1) required: 'no' default: running - name: process oneline: Check for a running process params: - name: process_name desc: The process name to check for requried: 'yes' - name: min_count desc: The minimum number of processes to require required: 'no' default: 1 - name: max_count desc: The maximum number of processes allowed required: 'no' default: infinity - name: username desc: Limit matches to processes owned by this username required: 'no' default: blank (any user) - name: ping oneline: Pings a host to make sure it's up. Uses a Python ping module instead of calling out to an external app, but needs to be run as root. params: - name: host desc: The host/IP to ping. required: 'yes' - name: timeout desc: The timeout for the ping in seconds required: 'no' default: 5 - name: unifi_failover oneline: Checks a Unifi Security Gateway for failover WAN status. (The USG must be in your known_hosts file.) params: - name: router_address desc: The address of the USG required: 'yes' - name: router_username desc: The username to log in as required: 'yes' - name: router_password desc: The password to log in with (if not using ssh key) required: 'no' - name: ssh_key desc: The SSH private key to log in with (if not using password) required: 'no' - name: check_interface desc: The name of the failover interface to check required: 'no' default: eth2 - name: unifi_watchdog oneline: Checks a Unifi Security Gateway to make sure the WAN failover is healthy (The USG must be in your known_host file.) params: - name: router_address desc: The address of the USG required: 'yes' - name: router_username desc: The username to log in as required: 'yes' - name: router_password desc: The password to log in with (if not using ssh key) required: 'no' - name: ssh_key desc: The SSH private key to log in with (if not using password) required: 'no' - name: primary_interface desc: The name of the primary interface required: 'no' default: pppoe0 - name: secondary_interface desc: The name of the secondary interface required: 'no' default: eth2 simplemonitor-1.13.0/old_docs/_includes/000077500000000000000000000000001464501162400202335ustar00rootroot00000000000000simplemonitor-1.13.0/old_docs/_includes/monitors.html000066400000000000000000000055461464501162400230050ustar00rootroot00000000000000{% assign m = site.data.monitors | sort: 'name' %} The types of monitor available are: All monitor types share the following configuration options: | setting | description | required | default| |---|---|---|---| | type| One of the types from the above list | yes | | | runon| A hostname (as returned from Python’s `socket.gethostname()`) on which the monitor should run. All other hosts will ignore this monitor completely. If unset (default) all hosts will run the monitor. | no | | | depend| A comma-separated list of other monitors on which this one depends. If one of the dependencies fails (or is skipped), this monitor will also skip. A skip does not trigger an alert. | no | | | tolerance| The number of times a monitor can fail before it’s actually considered failed (and generates an alert). Handy for things which intermittently fail to poll (the host monitor is guilty of this). This also interacts with the limit option on alerters. | no | 1 (i.e. on first failure) | | urgent| If this monitor is urgent or not. Non-urgent monitors cannot trigger urgent alerters (e.g. the SMS alerter). Set to 0 to make a monitor non-urgent. | no | 1 | | gap| The number of seconds gap between polls for this monitor. Setting this lower than the global interval will have no effect. Use it to make a monitor poll only once an hour, for example. | no | 0 | | remote_alert| This monitor wants a remote host to handle alerting instead of the local host. Set to 1 to enable. This is a good candidate for putting in defaults if you want to use remote alerting for all your monitors. | no | 0 | | recover_command| A command to execute once when this monitor fails. It could, for example, restart a service if an HTTP check fails. | no | | | recovered_command| A command to execute once when this monitor succeeds the first time after being failed. | no | | | group | The group the monitor belongs to. Alerters and Loggers will only fire for monitors which appear in their groups. | no | `default` | | notify | If the monitor should alert at all | no | 1 | | failure_doc | Information to include in alerts on failure (e.g. a URL to a runbook) | no | | | gps | comma-separated latitude, longitude of this morning, for the HTML logger's map | no | | {% for monitor in m %} ## {{monitor.name}} {{monitor.oneline}} {% if monitor.params %} {% for param in monitor.params %} {% endfor %}
setting description required default
{{param.name}} {{param.desc | markdownify}} {{param.required}} {% if param.default != nil %}{{param.default | markdownify}}{% endif %}
{% else %} This monitor has no additional parameters. {% endif %} {{monitor.notes}} {% endfor %} simplemonitor-1.13.0/old_docs/_layouts/000077500000000000000000000000001464501162400201255ustar00rootroot00000000000000simplemonitor-1.13.0/old_docs/_layouts/monitors.html000066400000000000000000000001251464501162400226630ustar00rootroot00000000000000
    {% for monitor in site.data.monitors %}
  • {{monitor}}
  • {% endfor %}
simplemonitor-1.13.0/old_docs/_layouts/page.html000066400000000000000000000041361464501162400217330ustar00rootroot00000000000000 {{ page.title }}

Simplemonitor

A Python-based network and host monitor

{% if page.show_downloads %}
Download as .zip Download as .tar.gz View on GitHub
{% endif %}
{{ content }}
Fork me on GitHub simplemonitor-1.13.0/old_docs/alerting.md000066400000000000000000000273511464501162400204250ustar00rootroot00000000000000--- layout: page title: Alerting order: 40 --- Alerters send one-off alerts when a monitor fails. They can also send an alert when it succeeds again. An alerter knows if it is urgent or not; if a monitor defined as non-urgent fails, an urgent alerter will not trigger for it. This means you can avoid receiving SMS alerts for things which don’t require your immediate attention. Alerters can also have a time configuration for hours when they are or are not allowed to alert. They can also send an alert at the end of the silence period for any monitors which are currently failed. The types of alerter are: * [sns](#sns): Sends an alert with Amazon Simple Notification Service * [email](#email): Sends an email when a monitor fails. Sends an email when it succeeds again. Requires an SMTP server to talk to. Non-urgent (all monitors will trigger this alerter.) * [bulksms](#bulksms): Sends an SMS alert when a monitor fails. Does not send an alert for when it succeeds again. Uses the [BulkSMS](http://www.bulksms.co.uk) service, which requires subscription. The messages are sent over HTTP on port 5567. (Urgent, so urgent=0 monitors will not trigger an SMS.) * [syslog](#syslog): Writes an entry to the syslog when something fails or succeeds. Not supported on Windows. * [execute](#execute): Executes an arbitrary command when something fails or recovers. * [slack](#slack): Sends notifications to a Slack channel using a webhook. * [ses](#ses): Sends notifications via the Amazon Simple Email Service * [46elks](#46elks): Sends notifications via the [46elks](https://46elks.com) service * [pushbullet](#pushbullet): Sends notifications via [Pushbullet](https://www.pushbullet.com) * [pushover](#pushover): Sends notifications via [Pushover](https://pushover.net) * [nc](#nc): Sends notifications via macOS Notification Center ## Defining an alerter The section name should be the name of your alerter. This is the name you should give in the "alerters" setting in the reporting section of the main configuration. All alerters share these settings: | setting | description | required | default | |---|---|---|---| | type | the type of the alerter, from the list above | yes| | | depend | a list of monitors this alerter depends on. If any of them fail, no attempt will be made to send the alert. (For example, there's no point trying to send an email alert to an external address if your route(s) to the Internet are down.) | no| | | limit | the number of times a monitor must fail before this alerter will fire. You can use this to escalate an alert to another email address if the problem is ongoing for too long, for example. | no | 1 | | dry_run | makes an alerter do everything except actually send the message. Instead it will print some information about what it would do. Use when you want to test your configuration without generating emails/SMSes. Set to 1 to enable. | no | 0 | | ooh_success | makes an alerter trigger its success action even if out-of-hours (0 or 1) | no | 0 | | groups | comma-separated list of group names this alerter will fire for. Use `_all` to match all groups. See the `group` setting for monitors | no | `default` | | only_failures | set to 1 to only fire this alerters for failure notifications (or catchups), not recoveries | no | 0 | | tz | timezone to use in alert messages | no | UTC | The *limit* uses the virtual fail count of a monitor, which means if a monitor has a tolerance of 3 and the alerter has a limit of 2, the monitor must fail 5 times before an alert is sent. ## Time periods All alerters accept time period configuration. By default, an alerter is active at all times, so you will always immediately receive an alert at the point where a monitor has failed enough (more times than the *limit*). To set limits on when an alerter can send: | setting | description | required | default | |---|---|---|---| | day | Which days an alerter can operate on. This is a comma-separated list of integers. 0 is Monday and 6 is Sunday. | no | (all days)| | times_type | Set to one of always, only, or not. “Only” means that the limits define the period that an alerter can operate. “Not” means that the limits define the period during which it will not operate. | no | always | | time_lower and time_upper| If *times_type* is only or not, these two settings define the time limits. time_lower must always be the lower time. The time format is hh:mm using 24-hour clock. Both are required if times_type is anything other than always. | when *times_type* is not `always` | | | times_tz | the timezone you want time_lower and time_upper interpreted as | no | local | | delay | If any kind of time/day restriction applies, the alerter will notify you of any monitors that failed while they were unable to alert you and are still failed. If a monitor fails and recovers during the restricted period, no catch-up alert is generated. Set to 1 to enable. | no | 0 | Here’s a quick example of setting time periods (some other configuration values omitted): Don’t send me SMSes while I’m in the office (8:30am to 5:30pm Mon-Fri): {% highlight ini %} [out_of_hours] type=bulksms times_type=not time_lower=08:30 time_upper=17:30 days=0,1,2,3,4 {% endhighlight %} Don’t send me SMSes at antisocial times, but let me know later if anything broke and didn’t recover: {% highlight ini %} [nice_alerter] type=bulksms times_type=only time_lower=07:30 time_upper=22:00 delay=1 {% endhighlight %} ## SNS alerters *DO NOT COMMIT YOUR CREDENTIALS TO A PUBLIC REPO* | setting | description | required | default | |---|---|---|---| | topic | The ARN of the topic to publish to. Specify this OR number, not both | no | | | number | The phone number to SMS. Give the number as e.g. 447777123456 (country code then number) | no | | | aws_region | The AWS region to use | no | | | aws_access_key | The AWS access key to use | no | | | aws_secret_access_key | The AWS secret access key | no | | You do not need to specify the `aws_*` settings if suitable values are available in a way that boto3 can find them (e.g. in the environment, or specified in a profile). To send an SMS, you must use a region with supports SMS sending (e.g. us-east-1). ## Email alerters *DO NOT COMMIT YOUR CREDENTIALS TO A PUBLIC REPO* | setting | description | required | default | |---|---|---|---| | host | the email server to send the message to (via SMTP). | yes | | | port | the port the email server is listening to. | no | 25 | | from | the email address the email should come from. | yes | | | to | the email address to email should go to. You can set multiple addresses separated by ; | yes | | | cc | the email address to cc the mail to. You can set multiple addresses separated by ; | no | | | username | username to log into the SMTP server | no | | | password | password to log into the SMTP server | no | | | ssl | `starttls` to use StartTLS; `yes` to use SMTP_SSL (untested); otherwise no SSL is used at all | no | | ## BulkSMS alerters *DO NOT COMMIT YOUR CREDENTIALS TO A PUBLIC REPO* | setting | description | required | default | |---|---|---|---| | sender | who the SMS should appear to be from. Max 11 chars. Try to avoid non alphanumeric characters. | no | SmplMntr | | username | your BulkSMS username. | yes | | | password | your BulkSMS password. | yes | | | target | the number to send the SMS to. Prefix the country code but drop the +. UK example: 447777123456. | yes | | ## Syslog alerters Syslog alerters have no additional options. ## Execute alerters | setting | description | required | default | |---|---|---|---| | fail_command | The command to execute when a monitor fails. | no | | | success_command | The command to execute when a monitor recovered. | no | | | catchup_command | THe command to execute when a previously-failed but not-alerted monitor enters a time period when it can alert. See the `delay` option above. | no | | You can use the string `fail_command` for catchup_command to make it use the value of fail_command. The following variables will be replaced in the string when the command is executed: * hostname: the host the monitor is running on * name: the monitor's name * days, hours, minutes, seconds: the monitor's downtime * failed_at: the date and time the monitor failed at * virtual_fail_count: the virtual fail count of the monitor * info: the additional information the monitor recorded about its status * description: a description of what the monitor is checking for You may need to quote parameters - e.g. `fail_command=say "Oh no, monitor {name} has failed at {failed_at}"`. The commands are executed directly by Python. If you require shell features, such as piping, then you should use something like `/bin/bash -c "/usr/bin/printf \"The simplemonitor for {name} has failed on {hostname}.\n\nTime: {failed_at}\nInfo: {info}\n\" | /usr/bin/mailx -A gmail -s \"PROBLEM: simplemonitor {name} has failed on {hostname}.\" email@address"`. ## Slack alerters First, set up a webhook for this to use. 1. Go to 2. Add a new webhook 3. Configure it to taste (channel, name, icon) 4. Copy the webhook URL for your configuration below This alerter requires the `requests` library to be installed. You can install it with `pip install -r requirements.txt`. *DO NOT COMMIT YOUR WEBHOOK URL TO A PUBLIC REPO* | setting | description | required | default | |---|---|---|---| | url | The Slack webhook URL as configured on your account | yes | | | channel | The channel to send to | no | uses the channel configured on the webhook | | username | A username to send to | no | | ## ses alerters You will need AWS credentials. Signing up for and configuring an AWS account is beyond the scope of this document. Credentials can come from any of the usual ways the AWS SDKs can find them, or can be specified in the configuration file. This alerter requires the `boto3` library to be installed. *DO NOT COMMIT YOUR AWS ACCESS KEYS TO A PUBLIC REPO* | setting | description | required | default | |---|---|---|---| | from | The email address to send from | yes | | | to | The address to send to | yes | | | aws_access_key | The AWS access key id | no | (the SDK will look for credentials in the usual locations) | | aws_secret_access_key | The AWS secret access key | no | (the SDK will look for credentials in the usual locations) | ## 46elks alerters You will need to register for an account at [46elks](https://46elks.com). *DO NOT COMMIT YOUR CREDENTIALS TO A PUBLIC REPO* | setting | description | required | default | |---|---|---|---| | username| your 46elks username | yes | | | password| your 46elks password | yes | | | target| 46elks target value | yes | | | sender| your SMS sender field; start with + if using a phone number | no | SmplMntr | | api_host| 46elks API endpoint | no | api.46elks.com | ## pushbullet alerters You will need to be registered at [pushbullet](https://www.pushbullet.com). *DO NOT COMMIT YOUR CREDENTIALS TO A PUBLIC REPO* | setting | description | required | default | |---|---|---|---| | token | your pushbullet token | yes | | ## pushover alerters You will need to be registered at [pushover](https://pushover.net). *DO NOT COMMIT YOUR CREDENTIALS TO A PUBLIC REPO* | setting | description | required | default | |---|---|---|---| | user | your pushover username | yes | | | token | your pushover token| yes | | ## nc alerters Publish alerts to the macOS Notification Center. Only for macOS. Requires the `pync` package. No configuration options. ## telegram alerters Send alerts to a Telegram chat. | setting | description | required | default | |---|---|---|---| | token | The token to access telegram | yes | | | chat_id | The chat to send to | yes | | simplemonitor-1.13.0/old_docs/configuration.md000066400000000000000000000127751464501162400214730ustar00rootroot00000000000000--- layout: page title: Configuring SimpleMonitor order: 20 --- The main configuration lives in monitor.ini in the same directory as the code. Section names are lowercase in square brackets. Settings are defined as key=value. Lines can be commented with #. Section names and option values (but not option names) support environment variable injection. To include the value of an environment variable, use `%env:VARIABLE%`, which will inject the value of `$VARIABLE`. You can use this to e.g. share a common configuration file across multiple hosts, but have each host name its monitors differently. ## Monitor section | setting | description | required | default | |---|---|---|---| | interval | defines how many seconds to wait between running all the monitors. Note that the time taken to run the monitors is not subtracted from the interval, so the next iteration will run at `interval + time_to_run_monitors` seconds. | yes | | | monitors | defines the filename to load the monitors themselves from. | no | `monitors.ini` | pidfile | gives a path to write a pidfile in. | no | | | remote | enables the listener for receiving data from remote instances. Set to 1 to enable. | no | 0 | | remote_port | gives the TCP port to listen on for data. | if `remote` is enabled | | | key | shared secret for validating data from remote instances. | if `remote` is enabled | | | hup_file | a file to watch the modification time on, and if it increases, reload the config | no | | | bind_host | the local address to bind to listen for data. | no | all interfaces | The `hup_file` setting really exists for platforms which don't have SIGHUP (e.g. Windows). On platforms which do, you should send the SimpleMonitor process SIGHUP to trigger a config reload. Note: The config reload will pick up new, modified and removed monitors, loggers, and alerters. Other than the `interval` setting, no other configuration options are reloaded. Note also that monitors, loggers and alerters cannot change type during a reload. ## Reporting section *loggers* lists (comma-separated, no spaces) the names of the loggers you have defined. (You can define loggers and not add them to this setting.) Not required; no default. *alerters* lists the names of the alerters you have defined. Not required; no default. If you do not define any loggers or alerters, then the only way to monitor the status of your network will be to watch the window the script is running in! * [Configuring logging](logging.html) * [Configuring alerting](alerting.html) ## Monitors Monitors go in monitors.ini (or another file, if you changed the *monitors* setting above). Let’s have a look at an example configuration. Here’s monitor.ini: {% highlight ini %} [monitor] interval=60 [reporting] loggers=logfile alerters=email,email_escalate,sms [logfile] type=logfile filename=monitor.log only_failures=1 [email] type=email host=mailserver.domain.local from=monitor@domain.local to=administrator@domain.local [email_escalate] type=email host=mailserver.domain.local from=monitor@domain.local to=boss@domain.local limit=5 [sms] type=bulksms username=some_username password=some_password target=some_mobile_number limit=10 {% endhighlight %} What does this configuration do? Firstly, it only polls every minute. It has one logger, writing a logfile, and three alerters – two emails and one SMS. The logfile is written to monitor.log and only contains failures. An email is sent to administrator@domain.local when a monitor fails. After a monitor has failed another four times, an email is sent to my boss. After it’s failed another five times (for a total of ten), I get an SMS. Now we need to write our monitors.ini: {% highlight ini %} [london-ping] type=host host=london-vpn-endpoint.domain.local tolerance=2 [london-server] type=host host=london-server.domain.local tolerance=2 depend=london-ping [website-http] type=http url=http://www.domain.local urgent=0 gap=300 [webmail-http] type=http url=http://webmail.domain.local allowed_codes=401 [local-diskspace] type=diskspace partition=/spool limit=500M [local-exim] type=rc runon=mailserver.domain.local service=exim [local-smtp] type=service runon=exchange.domain.local service=smtpsvc {% endhighlight %} This is what it all means: * A monitor called london-ping pings the endpoint of our VPN to the London office. This sometimes gets lost in transit even if the link is up, so the tolerance for this monitor is 2. * We also ping london-server. As it’s the other end of the VPN, we also give it a tolerance of 2. We declare that it depends on london-ping, so if the VPN is down we don’t get additional alerts for london-server. * Next we use an HTTP monitor to check our website is working. I don’t need to be SMSed if it breaks, so we set it as not urgent. Also, we’ll only check it every 5 minutes (300 seconds). * We want to check our webmail interface is responding, but it needs authentication. We’ll allow the HTTP error 401 Authentication Required to count as success. * We need to make sure the /spool partition on this server always has at least 500MB of free space. * We also want to make sure that exim is running on our FreeBSD server mailserver.domain.local. This monitor won’t try to run anywhere else. * Finally, we want to check the SMTP service is running on our Exchange server. This example configuration contains several combinations of monitors you probably won’t use on the same server – particularly a diskspace check for a mounted partition (not a drive letter) and a Windows service monitor. I just put them all together here as an example :) simplemonitor-1.13.0/old_docs/images/000077500000000000000000000000001464501162400175335ustar00rootroot00000000000000simplemonitor-1.13.0/old_docs/images/bkg.png000066400000000000000000000023021464501162400210010ustar00rootroot00000000000000PNG  IHDRx'PLTE! wIDATx^=0 `SeYY*KLf^T܋4e1"rMD=>cyzqauMc/S|1<7$p 0cʵIjyTe3v vkXZ"6co7h@p1Db%~%5X-A<mN?xu4hxEG.Q \tHYo 4a#[ÃnР1\i{ 4"mxx@0y1co7hА ,w 3R3~ <|E VozpZv V@>.DmР'[U L\v Ŭ 4r0` k O|l] . Q]Qcv qx*а=&O_ 0ê3}RDb|8svæ׆*qY! < 4,hn@ݠAz Z_۠AO[(.z E/|7h@ V5X{)GAzQZ`m&q sf+_o4h mO1,"q]Hym7h@G|vrT4hDJqšzlcn xyڂu ؜U.>+*_WAL_XnРqaP4$U%K_Q֝ɼzU )5S#h3?vpmNIENDB`simplemonitor-1.13.0/old_docs/index.md000066400000000000000000000142501464501162400177210ustar00rootroot00000000000000--- layout: page title: SimpleMonitor show_downloads: true order: 10 --- SimpleMonitor is a Python script which monitors hosts and network connectivity. It is designed to be quick and easy to set up and lacks complex features that can make things like Nagios, OpenNMS and Zenoss overkill for a small business or home network. Remote monitor instances can send their results back to a central location. ### SimpleMonitor supports: * Ping monitoring (is a host pingable?) * TCP monitoring (is a host listening on a TCP port?) * HTTP monitoring (is a URL fetchable without error? Optionally, does the page content it match a regular expression?) * DNS record monitoring * Service monitoring: FreeBSD 'rc' (and potenially others), Windows services, daemontools service * Disk space monitoring * File existence, age and time * FreeBSD portaudit (and pkg audit) * Load average monitoring * Exim queue size monitoring * Windows DHCP scope (available IPs) * APC UPS monitoring (requires apcupsd to be installed and configured) * Running an arbitrary command and checking the output * A monitor which is a compound of a number of the above Adding more monitor types is quite simple if you are able to code in Python. ### Logging and alerting options are: * Writing the state of each monitor at each iteration to a SQLite database (i.e. a history of results) * Maintaining a snapshot of the current state of the monitors in a SQLite database * Sending an email alert when a monitor fails, and when it recovers, directly over SMTP or via Amazon SES * Writing a log file of all successes and failures, or just failures * Sending a text message via BulkSMS (subscription required) * Writing an HTML status page * Writing an entry to the syslog (non-Windows only) * Posting notifications to Slack, 46elks, Notify My Android, Pushbullet, and Pushover * Executing arbitary commands on monitor failure and recovery Again, adding more logging/alerting methods is simply a case of writing some Python. ### SimpleMonitor also features: * Simple configuration file format: it’s a standard INI file for the overall configuration and another for the monitor definitions * Dependencies: Monitors can be declared as depending on the success of others. If a monitor fails, its dependencies will be skipped until it succeeds. * Tolerance: Monitors checking things the other side of unreliable links or which have many transient failures can be configured to require their test to fail a number of times in a row before they report a problem. * Escalation of alerts: Alerters can be configured to require a monitor to fail a number of times in a row (after its tolerance limit) before they fire, so alerts can be sent to additional addresses or people. * Urgency: Monitors can be defined as non-urgent so that urgent alerting methods (like SMS) are not wasted on them. * Per-host monitors: Define a monitor which should only run on a particular host and all other hosts will ignore it – so you can share one configuration file between all your hosts. * Monitor gaps: By default every monitor polls every interval (e.g. 60 seconds). Monitors can be given a gap between polls so that they only poll once a day (for example). * Alert periods: Alerters can be configured to only alert during certain times and/or on certain days… * Alert catchup: …and also to alert you to a monitor which failed when they were unable to tell you. (For example, I don’t want to be woken up overnight by an SMS, but if something’s still broken I’d like an SMS at 7am as I’m getting up.) * Remote monitors: An instance running on a remote machine can send its results back to a central instance for logging and alerting. ## Getting started SimpleMonitor requires Python >= 3.6.2 (check with `python -V`). You may need to install something like `python3-pip` with your package manager to get the right version installed. Make sure that the `pip` you use in the command below is the one for Python 3. (Again, check with `pip -V`). It might be called `pip3`. * `pip install simplemonitor` or `pip install --user simplemonitor` If you want to be able to query the Ring API for your doorbell battery level, use `simplemonitor[ring]` (Ubuntu 20.04 users may find [this page](https://linuxize.com/post/how-to-install-pip-on-ubuntu-20.04/) helpful for setting up Python 3.) I quite like [pyenv](https://github.com/pyenv/pyenv) for installing different versions of Python, instead of using the system package manager. You may also want to check out [pipx](https://pypi.org/project/pipx/) which lets you easily install tools in their own virtualenv. * Create `monitor.ini` and `monitors.ini`. See [Configuration](configuration.html). ## Running SimpleMonitor Assuming your $PATH covers where `pip` installs things, you can just run `simplemonitor`. SimpleMonitor does not fork. In the [`scripts`](https://github.com/jamesoff/simplemonitor/tree/develop/scripts) folder you can find startup scripts for various systems. You will need to adjust paths, usernames and options before using them! If you want to run it as a Windows Service, you want `winmonitor.exe`. I am not a Windows user so cannot offer advice on correctly setting this up. ## Command line options * `-h`, `--help`: display help ### Configuration * `-f CONFIG`, `--config=CONFIG`: configuration file (monitor.ini) * `-p PIDFILE`, `--pidfile=PIDFILE`: Write PID into this file * `-N`, `--no-network`: Disable network listening socket (if enabled in config) ### Testing These options exist mainly for automated testing and validation. * `-t`, `--test`: Test config and exit. Exits non-zero if config is broken * `-1`, `--one-shot`: Run the monitors once only, without alerting. Require monitors without "fail" in the name to succeed. Require monitors with "skip" in the name to skip. Exit zero or non-zero accordingly. * `--loops`: (Undocumented) Run this many loops of checks/logging/alerting and exit ### Output SimpleMonitor currently outputs everything to stdout. * `-v`, `--verbose`: Be more verbose * `-q`, `--quiet`: Don't output anything except errors * `-d`, `--debug`: Enable debug output (and enable verbose mode) * `-H`, `--no-heartbeat`: Omit printing the '.' character when running checks ## Licence SimpleMonitor is released under the BSD licence. simplemonitor-1.13.0/old_docs/javascripts/000077500000000000000000000000001464501162400206175ustar00rootroot00000000000000simplemonitor-1.13.0/old_docs/javascripts/main.js000066400000000000000000000000001464501162400220670ustar00rootroot00000000000000simplemonitor-1.13.0/old_docs/logging.md000066400000000000000000000146651464501162400202520ustar00rootroot00000000000000--- layout: page title: Logging order: 30 --- Loggers are used by SimpleMonitor to record the state of all monitors after each interval. The types of loggers are: * [db](#db): Records the result of every monitor, every iteration (maintaining a history) in a SQLite database. * [dbstatus](#dbstatus): Records a snapshot of the current state of every monitor in a SQLite database. * [logfile](#logfile): Records a logfile of the result of every monitor, or only the monitors which failed. Each line is preceeded by the current UNIX timestamp. * [html](#html): Writes an HTML file showing the status of all monitors (including remote ones). * [network](#network): Sends status of all monitors to a remote host. * [seq](#seq): Sends status of all monitors to a seq log server * [json](#json): Writes a JSON file describing the state of all the monitors * [mqtt](#mqtt): Send monitor state via MQTT ## Defining a logger The section name should be the name of your logger. This is the name you should give in the "loggers" setting in the "reporting" section of the configuration. All loggers take these two parameters. | setting | description | required | default | |---|---|---|---| | type | the type of logger to create. Choose one of the five in the list above. | yes | | | depend | lists (comma-separated, no spaces) the names of the monitors this logger depends on. Use this if the database file lives over the network. If a monitor it depends on fails, no attempt will be made to update the database.| no | | | groups | comma-separated list of monitor groups this logger should operate for. Use `_all` to match all groups. | no | "default" | | tz | The [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) the logger should convert date/times to. | no | UTC | | heartbeat | If set, the logger only logs for monitors which executed on an iteration. Intended to be combined with the `gap` property of a Monitor. | no | 0 | ### db and dbstatus loggers | setting | description | required | default | |---|---|---|---| | path | the path/filename of the SQLite database file. You should initialise the schema of this file using the monitor.sql file in the distribution. You can use the same database file for many loggers.| yes | | ### logfile loggers | setting | description | required | default | |---|---|---|---| | filename | the filename to write to. Rotating this file underneath SimpleMonitor will likely result in breakage (this will be addressed later). | yes | | | buffered | set to 1 if you aren’t going to watch the logfile in real time. If you want to watch it with something like tail -f then set this to 0. | no | 1 | | only_failures | set to 1 if you only want failures to be written to the file. | no | 0 | | dateformat | The date format to write for log lines. Supported values are "timestamp" (UNIX timestamp) or "iso8601" (YYYY-MM-DDTHH:MM:SS). | no | timestamp | ### html loggers | setting | description | required | default | |---|---|---|---| | source_folder | the folder in which all the needed files live. You only need this if you're customising the files | no | "html" in the distribution | | folder | the folder in which to write the file(s). Must already exist. | yes | | | filename | the filename to write out. The file will be updated once per interval (as defined in the main configuration). Relative to the *folder*. | yes | | | upload_command | a command to run to e.g. upload the generated files to another location | no | | | copy_resources | set to 0 if simplemonitor should not copy needed supporting files (e.g. CSS) to the output folder | no | 1 | | map | set to 1 to replace the table with a map. Set the "gps" property on your monitors. | no | 0 | | map_start | comma-separated latitude, longitude, zoom level for the initial map view. Try something like 10-15 for the map view to start | yes | | | map_token | an API token for mapbox.com in order to make the map work. Don't commit it to a public repo | yes | | The supplied template includes JavaScript to notify you if the page either doesn’t auto-refresh, or if SimpleMonitor has stopped updating it. This requires your machine running SimpleMonitor and the machine you are browsing from to agree on what the time is (timezone doesn’t matter)! The template is written using Jinja2. You can use the `upload_command` setting to specify a command to push the generated files to another location (e.g. a web server, an S3 bucket etc). I'd suggest putting the commands in a script and just specifying that script as the value for this setting. ### seq logger This logger is used to send status reports of all monitors to a seq log server. The logger must be configured with the seq *endpoint* parameter, for example http://10.0.0.100:5341/api/events/raw | setting | description | required | default | |---|---|---|---| | endpoint | Full URI for the endoint on the seq server (e.g. http://localhost:5341/api/events/seq) | yes | | From their website, 'Seq creates the visibility you need to quickly identify and diagnose problems in complex applications and microservices'. See https://datalust.co for more information on Seq ### network logger This logger is used to send status reports of all monitors to a remote instance. The remote instance must be configured to listen for connections. The *key* parameter is a shared secret used to generate a hash of the network traffic so the receiving instance knows to trust the data. (Note that the traffic is not encrypted, just given a hash.) | setting | description | required | default | |---|---|---|---| | host | the remote host to send to. | yes | | | port | the port on the remote host to connect to. | yes | | | key | shared secret to protect communications | yes | | ### json logger | setting | description | required | default | |---|---|---|---| | filename | the path of the JSON file to write. | yes | | ### mqtt logger | setting | description | required | default | |---|---|---|---| | host | The host to connect to | yes | | | port | The port to connect on | no | 1883 | | hass | Specific configuration for Home Assistant MQTT discovery | no | false | | topic | The topic to post to | no | `simplemonitor` (`homeassistant/binary_sensor` if hass is set) | | username | The username to use | no | | | password | The password to use | no | | See for more information on HASS/ simplemonitor-1.13.0/old_docs/monitors.md000066400000000000000000000001141464501162400204560ustar00rootroot00000000000000--- layout: page title: Monitors order: 50 --- {% include monitors.html %} simplemonitor-1.13.0/old_docs/params.json000066400000000000000000000037231464501162400204510ustar00rootroot00000000000000{"name":"Simplemonitor","tagline":"A Python-based network and host monitor","body":"### Welcome to GitHub Pages.\r\nThis automatic page generator is the easiest way to create beautiful pages for all of your projects. Author your page content here [using GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/), select a template crafted by a designer, and publish. After your page is generated, you can check out the new `gh-pages` branch locally. If you’re using GitHub Desktop, simply sync your repository and you’ll see the new branch.\r\n\r\n### Designer Templates\r\nWe’ve crafted some handsome templates for you to use. Go ahead and click 'Continue to layouts' to browse through them. You can easily go back to edit your page before publishing. After publishing your page, you can revisit the page generator and switch to another theme. Your Page content will be preserved.\r\n\r\n### Creating pages manually\r\nIf you prefer to not use the automatic generator, push a branch named `gh-pages` to your repository to create a page manually. In addition to supporting regular HTML content, GitHub Pages support Jekyll, a simple, blog aware static site generator. Jekyll makes it easy to create site-wide headers and footers without having to copy them across every page. It also offers intelligent blog support and other advanced templating features.\r\n\r\n### Authors and Contributors\r\nYou can @mention a GitHub username to generate a link to their profile. The resulting `` element will link to the contributor’s GitHub Profile. For example: In 2007, Chris Wanstrath (@defunkt), PJ Hyett (@pjhyett), and Tom Preston-Werner (@mojombo) founded GitHub.\r\n\r\n### Support or Contact\r\nHaving trouble with Pages? Check out our [documentation](https://help.github.com/pages) or [contact support](https://github.com/contact) and we’ll help you sort it out.\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."} simplemonitor-1.13.0/old_docs/stylesheets/000077500000000000000000000000001464501162400206425ustar00rootroot00000000000000simplemonitor-1.13.0/old_docs/stylesheets/github-dark.css000066400000000000000000000052401464501162400235560ustar00rootroot00000000000000/* Copyright 2014 GitHub Inc. 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. */ .pl-c /* comment */ { color: #969896; } .pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */, .pl-s .pl-v /* string variable */ { color: #0099cd; } .pl-e /* entity */, .pl-en /* entity.name */ { color: #9774cb; } .pl-s .pl-s1 /* string source */, .pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ { color: #ddd; } .pl-ent /* entity.name.tag */ { color: #7bcc72; } .pl-k /* keyword, storage, storage.type */ { color: #cc2372; } .pl-pds /* punctuation.definition.string, string.regexp.character-class */, .pl-s /* string */, .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, .pl-sr /* string.regexp */, .pl-sr .pl-cce /* string.regexp constant.character.escape */, .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */, .pl-sr .pl-sre /* string.regexp source.ruby.embedded */ { color: #3c66e2; } .pl-v /* variable */ { color: #fb8764; } .pl-id /* invalid.deprecated */ { color: #e63525; } .pl-ii /* invalid.illegal */ { background-color: #e63525; color: #f8f8f8; } .pl-sr .pl-cce /* string.regexp constant.character.escape */ { color: #7bcc72; font-weight: bold; } .pl-ml /* markup.list */ { color: #c26b2b; } .pl-mh /* markup.heading */, .pl-mh .pl-en /* markup.heading entity.name */, .pl-ms /* meta.separator */ { color: #264ec5; font-weight: bold; } .pl-mq /* markup.quote */ { color: #00acac; } .pl-mi /* markup.italic */ { color: #ddd; font-style: italic; } .pl-mb /* markup.bold */ { color: #ddd; font-weight: bold; } .pl-md /* markup.deleted, meta.diff.header.from-file */ { background-color: #ffecec; color: #bd2c00; } .pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { background-color: #eaffea; color: #55a532; } .pl-mdr /* meta.diff.range */ { color: #9774cb; font-weight: bold; } .pl-mo /* meta.output */ { color: #264ec5; } simplemonitor-1.13.0/old_docs/stylesheets/stylesheet.css000066400000000000000000000110021464501162400235370ustar00rootroot00000000000000body { margin: 0; padding: 0; background: #151515 url("../images/bkg.png") 0 0; color: #eaeaea; font: 16px; line-height: 1.5; font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; } /* General & 'Reset' Stuff */ .container { width: 90%; max-width: 900px; margin: 0 auto; } section { display: block; margin: 0 0 20px 0; } h1, h2, h3, h4, h5, h6 { margin: 0 0 20px; } li { line-height: 1.4 ; } /* Header,
header - container h1 - project name h2 - project description */ header { background: rgba(0, 0, 0, 0.1); width: 100%; border-bottom: 1px dashed #b5e853; padding: 20px 0; margin: 0 0 40px 0; } header h1 { font-size: 30px; line-height: 1.5; margin: 0 0 0 -40px; font-weight: bold; font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; color: #b5e853; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1), 0 0 5px rgba(181, 232, 83, 0.1), 0 0 10px rgba(181, 232, 83, 0.1); letter-spacing: -1px; -webkit-font-smoothing: antialiased; } header h1:before { content: "./ "; font-size: 24px; } header h2 { font-size: 18px; font-weight: 300; color: #666; } #downloads .btn { display: inline-block; text-align: center; margin: 0; } /* Main Content */ #main_content { width: 100%; -webkit-font-smoothing: antialiased; } section img { max-width: 100% } h1, h2, h3, h4, h5, h6 { font-weight: normal; font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; color: #b5e853; letter-spacing: -0.03em; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1), 0 0 5px rgba(181, 232, 83, 0.1), 0 0 10px rgba(181, 232, 83, 0.1); } #main_content h1 { font-size: 30px; } #main_content h2 { font-size: 24px; } #main_content h3 { font-size: 18px; } #main_content h4 { font-size: 14px; } #main_content h5 { font-size: 12px; text-transform: uppercase; margin: 0 0 5px 0; } #main_content h6 { font-size: 12px; text-transform: uppercase; color: #999; margin: 0 0 5px 0; } dt { font-style: italic; font-weight: bold; } ul li { list-style: none; } ul li:before { content: ">>"; font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; font-size: 13px; color: #b5e853; margin-left: -37px; margin-right: 21px; line-height: 16px; } blockquote { color: #aaa; padding-left: 10px; border-left: 1px dotted #666; } pre { background: rgba(0, 0, 0, 0.9); border: 1px solid rgba(255, 255, 255, 0.15); padding: 10px; font-size: 14px; color: #b5e853; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; text-wrap: normal; overflow: auto; overflow-y: hidden; } table { width: 100%; margin: 0 0 20px 0; } th { text-align: left; border-bottom: 1px dashed #b5e853; padding: 5px 10px; } td { padding: 5px 10px; vertical-align: top; background-color: rgba(30, 30, 30, 0.9); } td p { margin: 0; } hr { height: 0; border: 0; border-bottom: 1px dashed #b5e853; color: #b5e853; } /* Buttons */ .btn { display: inline-block; background: -webkit-linear-gradient(top, rgba(40, 40, 40, 0.3), rgba(35, 35, 35, 0.3) 50%, rgba(10, 10, 10, 0.3) 50%, rgba(0, 0, 0, 0.3)); padding: 8px 18px; border-radius: 50px; border: 2px solid rgba(0, 0, 0, 0.7); border-bottom: 2px solid rgba(0, 0, 0, 0.7); border-top: 2px solid rgba(0, 0, 0, 1); color: rgba(255, 255, 255, 0.8); font-family: Helvetica, Arial, sans-serif; font-weight: bold; font-size: 13px; text-decoration: none; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.75); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); } .btn:hover { background: -webkit-linear-gradient(top, rgba(40, 40, 40, 0.6), rgba(35, 35, 35, 0.6) 50%, rgba(10, 10, 10, 0.8) 50%, rgba(0, 0, 0, 0.8)); } .btn .icon { display: inline-block; width: 16px; height: 16px; margin: 1px 8px 0 0; float: left; } .btn-github .icon { opacity: 0.6; background: url("../images/blacktocat.png") 0 0 no-repeat; } /* Links a, a:hover, a:visited */ a { color: #63c0f5; text-shadow: 0 0 5px rgba(104, 182, 255, 0.5); } /* Clearfix */ .cf:before, .cf:after { content:""; display:table; } .cf:after { clear:both; } .cf { zoom:1; } ul.menu { list-style: none; padding: 0; } ul.menu li:before { content: none; } ul.menu li { display: inline; } .banner { background-color: #fffacd; color: black; margin: 0; padding: 5px 10px; position: fixed; width: 100%; } simplemonitor-1.13.0/old_docs/stylesheets/syntax.css000066400000000000000000000072271464501162400227120ustar00rootroot00000000000000.highlight { background: #000; } .highlight .c { color: #999988; font-style: italic } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { font-weight: bold } /* Keyword */ .highlight .o { font-weight: bold } /* Operator */ .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ .highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #999999 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #aaaaaa } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { font-weight: bold } /* Keyword.Constant */ .highlight .kd { font-weight: bold } /* Keyword.Declaration */ .highlight .kp { font-weight: bold } /* Keyword.Pseudo */ .highlight .kr { font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #009999 } /* Literal.Number */ .highlight .s { color: #d14 } /* Literal.String */ .highlight .na { color: #008080 } /* Name.Attribute */ .highlight .nb { color: #0086B3 } /* Name.Builtin */ .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ .highlight .no { color: #008080 } /* Name.Constant */ .highlight .ni { color: #800080 } /* Name.Entity */ .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ .highlight .nn { color: #555555 } /* Name.Namespace */ .highlight .nt { color: #000080 } /* Name.Tag */ .highlight .nv { color: #008080 } /* Name.Variable */ .highlight .ow { font-weight: bold } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mf { color: #009999 } /* Literal.Number.Float */ .highlight .mh { color: #009999 } /* Literal.Number.Hex */ .highlight .mi { color: #009999 } /* Literal.Number.Integer */ .highlight .mo { color: #009999 } /* Literal.Number.Oct */ .highlight .sb { color: #d14 } /* Literal.String.Backtick */ .highlight .sc { color: #d14 } /* Literal.String.Char */ .highlight .sd { color: #d14 } /* Literal.String.Doc */ .highlight .s2 { color: #d14 } /* Literal.String.Double */ .highlight .se { color: #d14 } /* Literal.String.Escape */ .highlight .sh { color: #d14 } /* Literal.String.Heredoc */ .highlight .si { color: #d14 } /* Literal.String.Interpol */ .highlight .sx { color: #d14 } /* Literal.String.Other */ .highlight .sr { color: #009926 } /* Literal.String.Regex */ .highlight .s1 { color: #d14 } /* Literal.String.Single */ .highlight .ss { color: #990073 } /* Literal.String.Symbol */ .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ .highlight .vc { color: #008080 } /* Name.Variable.Class */ .highlight .vg { color: #008080 } /* Name.Variable.Global */ .highlight .vi { color: #008080 } /* Name.Variable.Instance */ .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ simplemonitor-1.13.0/poetry.lock000066400000000000000000004637731464501162400167200ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" optional = false python-versions = ">=3.6" files = [ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = "*" files = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] [[package]] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" optional = false python-versions = ">=3.6" files = [ {file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"}, {file = "arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1"}, ] [package.dependencies] python-dateutil = ">=2.7.0" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "astroid" version = "2.11.7" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.6.2" files = [ {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"}, {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"}, ] [package.dependencies] lazy-object-proxy = ">=1.4.0" setuptools = ">=20.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} wrapt = ">=1.11,<2" [[package]] name = "babel" version = "2.12.1" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, ] [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [[package]] name = "bandit" version = "1.7.5" description = "Security oriented static analyser for python code." optional = false python-versions = ">=3.7" files = [ {file = "bandit-1.7.5-py3-none-any.whl", hash = "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549"}, {file = "bandit-1.7.5.tar.gz", hash = "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"}, ] [package.dependencies] colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" PyYAML = ">=5.3.1" rich = "*" stevedore = ">=1.20.0" [package.extras] test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"] toml = ["tomli (>=1.1.0)"] yaml = ["PyYAML"] [[package]] name = "bcrypt" version = "4.0.1" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.6" files = [ {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, ] [package.extras] tests = ["pytest (>=3.2.1,!=3.3.0)"] typecheck = ["mypy"] [[package]] name = "black" version = "23.3.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.7" files = [ {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" version = "1.26.83" description = "The AWS SDK for Python" optional = false python-versions = ">= 3.7" files = [ {file = "boto3-1.26.83-py3-none-any.whl", hash = "sha256:c07ed643aa72940f92b2c807591e1ba803c1b79d20ca129a6f8cccf846c40ff8"}, {file = "boto3-1.26.83.tar.gz", hash = "sha256:df81bfa4f8dfb3a645c43b7fd317f48df3e7fa402115ef21fc2f6430d73a1821"}, ] [package.dependencies] botocore = ">=1.29.83,<1.30.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.6.0,<0.7.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" version = "1.29.83" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">= 3.7" files = [ {file = "botocore-1.29.83-py3-none-any.whl", hash = "sha256:b7df2a1413f32992f350dd66bc5c8747b7211b776cf6500f64aebba7eddbe4f0"}, {file = "botocore-1.29.83.tar.gz", hash = "sha256:2e3d215b94df4e4c0ce3da250db43fb39ef7834f8f8fd19e5b223b35edb4d073"}, ] [package.dependencies] jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = ">=1.25.4,<1.27" [package.extras] crt = ["awscrt (==0.16.9)"] [[package]] name = "certifi" version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = "*" files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] [package.dependencies] pycparser = "*" [[package]] name = "charset-normalizer" version = "3.0.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = "*" files = [ {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, ] [[package]] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "cloudscraper" version = "1.2.71" description = "A Python module to bypass Cloudflare's anti-bot page." optional = false python-versions = "*" files = [ {file = "cloudscraper-1.2.71-py2.py3-none-any.whl", hash = "sha256:76f50ca529ed2279e220837befdec892626f9511708e200d48d5bb76ded679b0"}, {file = "cloudscraper-1.2.71.tar.gz", hash = "sha256:429c6e8aa6916d5bad5c8a5eac50f3ea53c9ac22616f6cb21b18dcc71517d0d3"}, ] [package.dependencies] pyparsing = ">=2.4.7" requests = ">=2.9.2" requests-toolbelt = ">=0.9.1" [[package]] name = "codecov" version = "2.1.13" description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "codecov-2.1.13-py2.py3-none-any.whl", hash = "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5"}, {file = "codecov-2.1.13.tar.gz", hash = "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c"}, ] [package.dependencies] coverage = "*" requests = ">=2.7.9" [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "colorlog" version = "6.8.2" description = "Add colours to the output of Python's logging module." optional = false python-versions = ">=3.6" files = [ {file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"}, {file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] development = ["black", "flake8", "mypy", "pytest", "types-colorama"] [[package]] name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.7" files = [ {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "cryptography" version = "42.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449"}, {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18"}, {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2"}, {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1"}, {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b"}, {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1"}, {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992"}, {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885"}, {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824"}, {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b"}, {file = "cryptography-42.0.4-cp37-abi3-win32.whl", hash = "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925"}, {file = "cryptography-42.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923"}, {file = "cryptography-42.0.4-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7"}, {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52"}, {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a"}, {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9"}, {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764"}, {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff"}, {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257"}, {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929"}, {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0"}, {file = "cryptography-42.0.4-cp39-abi3-win32.whl", hash = "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129"}, {file = "cryptography-42.0.4-cp39-abi3-win_amd64.whl", hash = "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854"}, {file = "cryptography-42.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298"}, {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88"}, {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20"}, {file = "cryptography-42.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce"}, {file = "cryptography-42.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74"}, {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd"}, {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b"}, {file = "cryptography-42.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660"}, {file = "cryptography-42.0.4.tar.gz", hash = "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "dill" version = "0.3.6" description = "serialize all of python" optional = false python-versions = ">=3.7" files = [ {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, ] [package.extras] graph = ["objgraph (>=1.7.2)"] [[package]] name = "distlib" version = "0.3.6" description = "Distribution utilities" optional = false python-versions = "*" files = [ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] [[package]] name = "docutils" version = "0.17.1" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] [[package]] name = "exceptiongroup" version = "1.1.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "filelock" version = "3.9.0" description = "A platform independent file lock." optional = false python-versions = ">=3.7" files = [ {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, ] [package.extras] docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] [[package]] name = "flake8" version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.6.1" files = [ {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, ] [package.dependencies] importlib-metadata = {version = ">=1.1.0,<4.3", markers = "python_version < \"3.8\""} mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.9.0,<2.10.0" pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "freezegun" version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, ] [package.dependencies] python-dateutil = ">=2.7" [[package]] name = "gitdb" version = "4.0.10" description = "Git Object Database" optional = false python-versions = ">=3.7" files = [ {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, ] [package.dependencies] smmap = ">=3.0.1,<6" [[package]] name = "gitpython" version = "3.1.41" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} [package.extras] test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] [[package]] name = "icmplib" version = "3.0.4" description = "Easily forge ICMP packets and make your own ping and traceroute." optional = false python-versions = ">=3.7" files = [ {file = "icmplib-3.0.4-py3-none-any.whl", hash = "sha256:336b75c6c23c5ce99ddec33f718fab09661f6ad698e35b6f1fc7cc0ecf809398"}, {file = "icmplib-3.0.4.tar.gz", hash = "sha256:57868f2cdb011418c0e1d5586b16d1fabd206569fe9652654c27b6b2d6a316de"}, ] [[package]] name = "idna" version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "importlib-metadata" version = "4.2.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.6" files = [ {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, ] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "isort" version = "5.11.5" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.7.0" files = [ {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, ] [package.extras] colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "jinja2" version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" optional = false python-versions = ">=3.7" files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] [[package]] name = "lazy-object-proxy" version = "1.9.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.7" files = [ {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, ] [[package]] name = "markdown-it-py" version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.7" files = [ {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, ] [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "mypy" version = "1.4.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.7" files = [ {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = false python-versions = ">=3.6" files = [ {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, ] [package.extras] rsa = ["cryptography (>=3.0.0)"] signals = ["blinker (>=1.4.0)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "packaging" version = "23.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] [[package]] name = "paho-mqtt" version = "1.6.1" description = "MQTT version 5.0/3.1.1 client class" optional = false python-versions = "*" files = [ {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, ] [package.extras] proxy = ["PySocks"] [[package]] name = "paramiko" version = "3.4.0" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" files = [ {file = "paramiko-3.4.0-py3-none-any.whl", hash = "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7"}, {file = "paramiko-3.4.0.tar.gz", hash = "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"}, ] [package.dependencies] bcrypt = ">=3.2" cryptography = ">=3.3" pynacl = ">=1.5" [package.extras] all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=2.0)"] [[package]] name = "pathspec" version = "0.11.0" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, ] [[package]] name = "pbr" version = "5.11.1" description = "Python Build Reasonableness" optional = false python-versions = ">=2.6" files = [ {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"}, {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, ] [[package]] name = "ping3" version = "4.0.8" description = "A pure python3 version of ICMP ping implementation using raw socket." optional = false python-versions = ">=3.5" files = [ {file = "ping3-4.0.8-py3-none-any.whl", hash = "sha256:b5c294851968b4229e9af69d252778241cb84221594b436a8677419c31925594"}, {file = "ping3-4.0.8.tar.gz", hash = "sha256:9ab460eb57f006b71f977a693d157215aabd6bbcbc6cd6428b6951eaae0c2875"}, ] [package.extras] dev = ["build", "twine"] [[package]] name = "platformdirs" version = "3.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ {file = "platformdirs-3.0.0-py3-none-any.whl", hash = "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"}, {file = "platformdirs-3.0.0.tar.gz", hash = "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9"}, ] [package.dependencies] typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} [package.extras] docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.6" files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "psutil" version = "6.0.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, ] [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] [[package]] name = "pyaarlo" version = "0.8.0.7" description = "PyAarlo is a library that provides asynchronous access to Arlo security cameras." optional = false python-versions = ">=3.7" files = [ {file = "pyaarlo-0.8.0.7-py3-none-any.whl", hash = "sha256:42e5fe83af002370daf490e688cc84c8abb85dee8b35be46227c542aeca36cd8"}, {file = "pyaarlo-0.8.0.7.tar.gz", hash = "sha256:9a19527453739c38fd5aa0ebea8ef57f41015138595125ec247db9f66481ceb3"}, ] [package.dependencies] click = "*" cloudscraper = ">=1.2.71" cryptography = "*" paho-mqtt = "*" pycryptodome = "*" requests = "*" unidecode = "*" [[package]] name = "pycodestyle" version = "2.9.1" description = "Python style guide checker" optional = false python-versions = ">=3.6" files = [ {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, ] [[package]] name = "pycparser" version = "2.21" description = "C parser in Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] [[package]] name = "pycryptodome" version = "3.19.1" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "pycryptodome-3.19.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:694020d2ff985cd714381b9da949a21028c24b86f562526186f6af7c7547e986"}, {file = "pycryptodome-3.19.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:4464b0e8fd5508bff9baf18e6fd4c6548b1ac2ce9862d6965ff6a84ec9cb302a"}, {file = "pycryptodome-3.19.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:420972f9c62978e852c74055d81c354079ce3c3a2213a92c9d7e37bbc63a26e2"}, {file = "pycryptodome-3.19.1-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1bc0c49d986a1491d66d2a56570f12e960b12508b7e71f2423f532e28857f36"}, {file = "pycryptodome-3.19.1-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e038ab77fec0956d7aa989a3c647652937fc142ef41c9382c2ebd13c127d5b4a"}, {file = "pycryptodome-3.19.1-cp27-cp27m-win32.whl", hash = "sha256:a991f8ffe8dfe708f86690948ae46442eebdd0fff07dc1b605987939a34ec979"}, {file = "pycryptodome-3.19.1-cp27-cp27m-win_amd64.whl", hash = "sha256:2c16426ef49d9cba018be2340ea986837e1dfa25c2ea181787971654dd49aadd"}, {file = "pycryptodome-3.19.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6d0d2b97758ebf2f36c39060520447c26455acb3bcff309c28b1c816173a6ff5"}, {file = "pycryptodome-3.19.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:b8b80ff92049fd042177282917d994d344365ab7e8ec2bc03e853d93d2401786"}, {file = "pycryptodome-3.19.1-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd4e7e8bf0fc1ada854688b9b309ee607e2aa85a8b44180f91021a4dd330a928"}, {file = "pycryptodome-3.19.1-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:8cf5d3d6cf921fa81acd1f632f6cedcc03f5f68fc50c364cd39490ba01d17c49"}, {file = "pycryptodome-3.19.1-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:67939a3adbe637281c611596e44500ff309d547e932c449337649921b17b6297"}, {file = "pycryptodome-3.19.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:11ddf6c9b52116b62223b6a9f4741bc4f62bb265392a4463282f7f34bb287180"}, {file = "pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3e6f89480616781d2a7f981472d0cdb09b9da9e8196f43c1234eff45c915766"}, {file = "pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e1efcb68993b7ce5d1d047a46a601d41281bba9f1971e6be4aa27c69ab8065"}, {file = "pycryptodome-3.19.1-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c6273ca5a03b672e504995529b8bae56da0ebb691d8ef141c4aa68f60765700"}, {file = "pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b0bfe61506795877ff974f994397f0c862d037f6f1c0bfc3572195fc00833b96"}, {file = "pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:f34976c5c8eb79e14c7d970fb097482835be8d410a4220f86260695ede4c3e17"}, {file = "pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7c9e222d0976f68d0cf6409cfea896676ddc1d98485d601e9508f90f60e2b0a2"}, {file = "pycryptodome-3.19.1-cp35-abi3-win32.whl", hash = "sha256:4805e053571140cb37cf153b5c72cd324bb1e3e837cbe590a19f69b6cf85fd03"}, {file = "pycryptodome-3.19.1-cp35-abi3-win_amd64.whl", hash = "sha256:a470237ee71a1efd63f9becebc0ad84b88ec28e6784a2047684b693f458f41b7"}, {file = "pycryptodome-3.19.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:ed932eb6c2b1c4391e166e1a562c9d2f020bfff44a0e1b108f67af38b390ea89"}, {file = "pycryptodome-3.19.1-pp27-pypy_73-win32.whl", hash = "sha256:81e9d23c0316fc1b45d984a44881b220062336bbdc340aa9218e8d0656587934"}, {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37e531bf896b70fe302f003d3be5a0a8697737a8d177967da7e23eff60d6483c"}, {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd4e95b0eb4b28251c825fe7aa941fe077f993e5ca9b855665935b86fbb1cc08"}, {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22c80246c3c880c6950d2a8addf156cee74ec0dc5757d01e8e7067a3c7da015"}, {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e70f5c839c7798743a948efa2a65d1fe96bb397fe6d7f2bde93d869fe4f0ad69"}, {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6c3df3613592ea6afaec900fd7189d23c8c28b75b550254f4bd33fe94acb84b9"}, {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08b445799d571041765e7d5c9ca09c5d3866c2f22eeb0dd4394a4169285184f4"}, {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:954d156cd50130afd53f8d77f830fe6d5801bd23e97a69d358fed068f433fbfe"}, {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b7efd46b0b4ac869046e814d83244aeab14ef787f4850644119b1c8b0ec2d637"}, {file = "pycryptodome-3.19.1.tar.gz", hash = "sha256:8ae0dd1bcfada451c35f9e29a3e5db385caabc190f98e4a80ad02a61098fb776"}, ] [[package]] name = "pyflakes" version = "2.5.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.6" files = [ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] [[package]] name = "pygments" version = "2.15.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, ] [package.extras] plugins = ["importlib-metadata"] [[package]] name = "pyjwt" version = "2.6.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.7" files = [ {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"}, {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, ] [package.extras] crypto = ["cryptography (>=3.4.0)"] dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylint" version = "2.13.9" description = "python code static checker" optional = false python-versions = ">=3.6.2" files = [ {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, ] [package.dependencies] astroid = ">=2.11.5,<=2.12.0-dev0" colorama = {version = "*", markers = "sys_platform == \"win32\""} dill = ">=0.2" isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] testutil = ["gitpython (>3)"] [[package]] name = "pynacl" version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" optional = false python-versions = ">=3.6" files = [ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, ] [package.dependencies] cffi = ">=1.4.1" [package.extras] docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pync" version = "2.0.3" description = "Python Wrapper for Mac OS 10.10 Notification Center" optional = false python-versions = "*" files = [ {file = "pync-2.0.3.tar.gz", hash = "sha256:38b9e61735a3161f9211a5773c5f5ea698f36af4ff7f77fa03e8d1ff0caa117f"}, ] [package.dependencies] python-dateutil = ">=2.0" [[package]] name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] [package.dependencies] six = ">=1.5" [[package]] name = "pytz" version = "2022.7.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, ] [[package]] name = "pywin32" version = "306" description = "Python for Window Extensions" optional = false python-versions = "*" files = [ {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-oauthlib" version = "1.3.1" description = "OAuthlib authentication support for Requests." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, ] [package.dependencies] oauthlib = ">=3.0.0" requests = ">=2.0.0" [package.extras] rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] name = "requests-toolbelt" version = "0.10.1" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "requests-toolbelt-0.10.1.tar.gz", hash = "sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d"}, {file = "requests_toolbelt-0.10.1-py2.py3-none-any.whl", hash = "sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7"}, ] [package.dependencies] requests = ">=2.0.1,<3.0.0" [[package]] name = "rich" version = "13.3.5" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ {file = "rich-13.3.5-py3-none-any.whl", hash = "sha256:69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704"}, {file = "rich-13.3.5.tar.gz", hash = "sha256:2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c"}, ] [package.dependencies] markdown-it-py = ">=2.2.0,<3.0.0" pygments = ">=2.13.0,<3.0.0" typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ring-doorbell" version = "0.7.3" description = "A Python library to communicate with Ring Door Bell (https://ring.com/)" optional = false python-versions = "*" files = [ {file = "ring_doorbell-0.7.3-py3-none-any.whl", hash = "sha256:59fdada07c9a2db634768065065d4cfccd726a9f0ba9494d32254f6f693b48ca"}, {file = "ring_doorbell-0.7.3.tar.gz", hash = "sha256:1d846e419a5db419157c505da90ba9023cd0a4b201bca1a6e36702ba6734dc63"}, ] [package.dependencies] oauthlib = ">=3.0.0,<4" pytz = "*" requests = ">=2.0.0" requests-oauthlib = ">=1.3.0,<2" [[package]] name = "ruff" version = "0.5.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ {file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"}, {file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"}, {file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"}, {file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"}, {file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"}, {file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"}, {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"}, {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"}, {file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"}, {file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"}, {file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"}, {file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"}, {file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"}, {file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"}, {file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"}, {file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"}, {file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"}, {file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"}, ] [[package]] name = "s3transfer" version = "0.6.0" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">= 3.7" files = [ {file = "s3transfer-0.6.0-py3-none-any.whl", hash = "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd"}, {file = "s3transfer-0.6.0.tar.gz", hash = "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"}, ] [package.dependencies] botocore = ">=1.12.36,<2.0a.0" [package.extras] crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] [[package]] name = "setuptools" version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.7" files = [ {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "smmap" version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.6" files = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "sphinx" version = "4.3.2" description = "Python documentation generator" optional = false python-versions = ">=3.6" files = [ {file = "Sphinx-4.3.2-py3-none-any.whl", hash = "sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851"}, {file = "Sphinx-4.3.2.tar.gz", hash = "sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} docutils = ">=0.14,<0.18" imagesize = "*" Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" requests = ">=2.5.0" setuptools = "*" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.920)", "types-pkg-resources", "types-requests", "types-typed-ast"] test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] [[package]] name = "sphinx-rtd-theme" version = "1.3.0" description = "Read the Docs theme for Sphinx" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, ] [package.dependencies] docutils = "<0.19" sphinx = ">=1.6,<8" sphinxcontrib-jquery = ">=4,<5" [package.extras] dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.6" files = [ {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" optional = false python-versions = ">=2.7" files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, ] [package.dependencies] Sphinx = ">=1.8" [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [package.extras] test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "stevedore" version = "3.5.2" description = "Manage dynamic plugins for Python applications" optional = false python-versions = ">=3.6" files = [ {file = "stevedore-3.5.2-py3-none-any.whl", hash = "sha256:fa2630e3d0ad3e22d4914aff2501445815b9a4467a6edc49387c667a38faf5bf"}, {file = "stevedore-3.5.2.tar.gz", hash = "sha256:cf99f41fc0d5a4f185ca4d3d42b03be9011b0a1ec1a4ea1a282be1b4b306dcc2"}, ] [package.dependencies] importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "tox" version = "3.28.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ {file = "tox-3.28.0-py2.py3-none-any.whl", hash = "sha256:57b5ab7e8bb3074edc3c0c0b4b192a4f3799d3723b2c5b76f1fa9f2d40316eea"}, {file = "tox-3.28.0.tar.gz", hash = "sha256:d0d28f3fe6d6d7195c27f8b054c3e99d5451952b54abdae673b71609a581f640"}, ] [package.dependencies] colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} filelock = ">=3.0.0" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" six = ">=1.14.0" tomli = {version = ">=2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""} virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" [package.extras] docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] [[package]] name = "tox-poetry" version = "0.5.0" description = "Tox poetry plugin" optional = false python-versions = "*" files = [ {file = "tox-poetry-0.5.0.tar.gz", hash = "sha256:5bf3bc8bcbcac0f52ff73c11062123cc1450cd3bdde4b3f71e015ce1020b3b36"}, {file = "tox_poetry-0.5.0-py2.py3-none-any.whl", hash = "sha256:072c994cd23e0a818c4bf9db10a2adcb7fbee85a23a0826a74d649701b9bdcab"}, ] [package.dependencies] pluggy = "*" toml = "*" tox = {version = ">=3.7.0,<4", markers = "python_version >= \"3\""} [package.extras] test = ["coverage", "pycodestyle", "pylint", "pytest"] [[package]] name = "twilio" version = "7.16.4" description = "Twilio API client and TwiML generator" optional = false python-versions = ">=3.6.0" files = [ {file = "twilio-7.16.4-py2.py3-none-any.whl", hash = "sha256:7dead87cf3b92fae5db07ce553b571b315a1bfff2cc6a13bdf5bdfc4675409e5"}, {file = "twilio-7.16.4.tar.gz", hash = "sha256:594b2b594d2181e6f765e8af37f1d28277fa54b0f651ca7e5c0f54aa45797fd3"}, ] [package.dependencies] PyJWT = ">=2.0.0,<3.0.0" pytz = "*" requests = ">=2.0.0" [[package]] name = "typed-ast" version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" optional = false python-versions = ">=3.6" files = [ {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] [[package]] name = "types-paramiko" version = "3.4.0.20240103" description = "Typing stubs for paramiko" optional = false python-versions = ">=3.7" files = [ {file = "types-paramiko-3.4.0.20240103.tar.gz", hash = "sha256:245c08e2bbe1aa23f5faa1810e897013aba24f5319fb2f8292f8cb0f0fbceaa2"}, {file = "types_paramiko-3.4.0.20240103-py3-none-any.whl", hash = "sha256:f15d6211ab2ab7e5e6806b94a3edb11c6981d252b564d2c18910c6ba821d9e33"}, ] [package.dependencies] cryptography = ">=37.0.0" [[package]] name = "types-pyopenssl" version = "23.3.0.0" description = "Typing stubs for pyOpenSSL" optional = false python-versions = ">=3.7" files = [ {file = "types-pyOpenSSL-23.3.0.0.tar.gz", hash = "sha256:5ffb077fe70b699c88d5caab999ae80e192fe28bf6cda7989b7e79b1e4e2dcd3"}, {file = "types_pyOpenSSL-23.3.0.0-py3-none-any.whl", hash = "sha256:00171433653265843b7469ddb9f3c86d698668064cc33ef10537822156130ebf"}, ] [package.dependencies] cryptography = ">=35.0.0" [[package]] name = "types-python-dateutil" version = "2.8.19.14" description = "Typing stubs for python-dateutil" optional = false python-versions = "*" files = [ {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, ] [[package]] name = "types-requests" version = "2.31.0.6" description = "Typing stubs for requests" optional = false python-versions = ">=3.7" files = [ {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, ] [package.dependencies] types-urllib3 = "*" [[package]] name = "types-setuptools" version = "69.0.0.0" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.7" files = [ {file = "types-setuptools-69.0.0.0.tar.gz", hash = "sha256:b0a06219f628c6527b2f8ce770a4f47550e00d3e8c3ad83e2dc31bc6e6eda95d"}, {file = "types_setuptools-69.0.0.0-py3-none-any.whl", hash = "sha256:8c86195bae2ad81e6dea900a570fe9d64a59dbce2b11cc63c046b03246ea77bf"}, ] [[package]] name = "types-urllib3" version = "1.26.25.8" description = "Typing stubs for urllib3" optional = false python-versions = "*" files = [ {file = "types-urllib3-1.26.25.8.tar.gz", hash = "sha256:ecf43c42d8ee439d732a1110b4901e9017a79a38daca26f08e42c8460069392c"}, {file = "types_urllib3-1.26.25.8-py3-none-any.whl", hash = "sha256:95ea847fbf0bf675f50c8ae19a665baedcf07e6b4641662c4c3c72e7b2edf1a9"}, ] [[package]] name = "typing-extensions" version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" files = [ {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] [[package]] name = "unidecode" version = "1.3.6" description = "ASCII transliterations of Unicode text" optional = false python-versions = ">=3.5" files = [ {file = "Unidecode-1.3.6-py3-none-any.whl", hash = "sha256:547d7c479e4f377b430dd91ac1275d593308dce0fc464fb2ab7d41f82ec653be"}, {file = "Unidecode-1.3.6.tar.gz", hash = "sha256:fed09cf0be8cf415b391642c2a5addfc72194407caee4f98719e40ec2a72b830"}, ] [[package]] name = "urllib3" version = "1.26.19" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, ] [package.extras] brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" version = "20.4.7" description = "Virtual Python Environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"}, ] [package.dependencies] appdirs = ">=1.4.3,<2" distlib = ">=0.3.1,<1" filelock = ">=3.0.0,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "xonsh (>=0.9.16)"] [[package]] name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, ] [[package]] name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.7" files = [ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "2.0" python-versions = "^3.7.0" content-hash = "22427910befd2919622722d2b8956803dbe825a61c2f41ff05e605a2456e3bd6" simplemonitor-1.13.0/pyproject.toml000066400000000000000000000037561464501162400174270ustar00rootroot00000000000000[tool.poetry] name = "simplemonitor" version = "1.13.0" description = "A simple network and host monitor" license = "BSD-3-Clause" authors = ["James Seward "] readme = "README.md" homepage = "https://github.com/jamesoff/simplemonitor" repository = "https://github.com/jamesoff/simplemonitor" documentation = "http://jamesoff.github.io/simplemonitor/" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: No Input/Output (Daemon)", "Intended Audience :: System Administrators", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", "Topic :: System :: Monitoring", "Typing :: Typed", ] include = [ "simplemonitor/html/dist/*", "simplemonitor/html/status-template.html", ] [tool.poetry.scripts] simplemonitor = "simplemonitor.monitor:main" winmonitor = "simplemonitor.winmonitor:main" [tool.poetry.dependencies] python = "^3.7.0" arrow = ">=0.17,<1.3" boto3 = "^1.15.16" colorlog = ">=4.4" paho-mqtt = "^1.5.1" ping3 = ">=2.6.6,<5.0.0" psutil = ">=5.7.2,<7.0.0" requests = "^2.24.0" pync = {version = "^2.0.3", platform = "darwin"} pywin32 = {version = ">=228,<307", platform = "win32"} Jinja2 = ">=2.11.2,<4.0.0" twilio = "^7.16.4" ring-doorbell = ">=0.6.0" paramiko = ">=2.7.2,<4.0.0" importlib-metadata = "<4.3" pyaarlo = ">=0.7.1.3,<0.9.0.0" icmplib = "^3.0.3" [tool.poetry.dev-dependencies] "flake8" = "^5.0.4" codecov = "*" coverage = "*" black = "*" mypy = "*" bandit = "*" freezegun = "*" pytest = "*" pylint = "^2.13.9" Sphinx = "^4.0.2" sphinx-rtd-theme = "^1.3.0" pytest-cov = "^4.1.0" tox = "^3.28.0" tox-poetry = "^0.5.0" types-paramiko = "^3.4.0" types-python-dateutil = "^2.8.19" types-requests = "^2.31.0" types-pyOpenSSL = "^23.3.0" types-setuptools = "^69.0.0" [tool.poetry.group.dev.dependencies] ruff = ">=0.5.1,<0.5.2" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" simplemonitor-1.13.0/scripts/000077500000000000000000000000001464501162400161675ustar00rootroot00000000000000simplemonitor-1.13.0/scripts/init.d/000077500000000000000000000000001464501162400173545ustar00rootroot00000000000000simplemonitor-1.13.0/scripts/init.d/simplemonitor000077500000000000000000000020011464501162400221740ustar00rootroot00000000000000#!/bin/sh # chkconfig: 234 90 10 # SimpleMonitor # . /etc/rc.d/init.d/functions . /etc/sysconfig/network [ "$NETWORKING" = "no" ] && exit 0 workdir="/usr/local/bin/simplemonitor" appname="simplemonitor" appbin="/usr/bin/python" apparg="monitor.py" appoptions="-q" pidfile="/var/run/$appname.pid" lockfile="/var/lock/subsys/$appname" start() { cd $workdir echo -n $"Starting $appname: " daemon --pidfile $pidfile "$appbin $apparg $appoptions &" RETVAL=$? echo [ $RETVAL = 0 ] && touch $lockfile return $RETVAL } stop() { echo -n $"Stopping $appname: " killproc $appbin RETVAL=$? echo [ $RETVAL -eq 0 ] && rm -f $lockfile return $RETVAL } case "$1" in start) $1 ;; stop) $1 ;; restart) stop start ;; *) echo "Usage: /etc/init.d/simplemonitor (start|stop|restart)" exit 1 esac exit 0 simplemonitor-1.13.0/scripts/init/000077500000000000000000000000001464501162400171325ustar00rootroot00000000000000simplemonitor-1.13.0/scripts/init/simplemonitor.conf000066400000000000000000000005051464501162400227020ustar00rootroot00000000000000# Simplemonitor upstart script # Assumes existence of 'simplemonitor' user/group description "simplemonitor startup script" start on runlevel [2345] stop on runlevel [016] console log respawn setuid simplemonitor setgid simplemonitor script cd /opt/simplemonitor exec python /opt/simplemonitor/monitor.py end script simplemonitor-1.13.0/scripts/systemd/000077500000000000000000000000001464501162400176575ustar00rootroot00000000000000simplemonitor-1.13.0/scripts/systemd/README.md000066400000000000000000000006101464501162400211330ustar00rootroot00000000000000This is a simple systemd service for `simplemonitor` # Installation Replace `/root/simplemonitor` with the location of your `simplemonitor` installation (two instances in the file) Start the service ``` systemctl start simplemonitor.service systemctl status simplemonitor.service ``` If everything looks good, enable a startup upon reboot ``` systemctl enable simplemonitor.service ``` simplemonitor-1.13.0/scripts/systemd/simplemonitor.service000066400000000000000000000003361464501162400241440ustar00rootroot00000000000000[Unit] Description=monitoring service After=network.target [Service] WorkingDirectory=/root/simplemonitor ExecStart=/usr/bin/python3 /root/simplemonitor/monitor.py Restart=on-failure [Install] WantedBy=multi-user.target simplemonitor-1.13.0/setup.cfg000066400000000000000000000013001464501162400163130ustar00rootroot00000000000000[metadata] version = attr: simplemonitor.__version__ [flake8] max-line-length = 88 ignore = E501,W503,E203,W503 per-file-ignores = simplemonitor/Monitors/__init__.py:F401 simplemonitor/Loggers/__init__.py:F401 simplemonitor/Alerters/__init__.py:F401 simplemonitor/__init__.py:F401 [isort] known_third_party = arrow,boto3,botocore,freezegun,importlib_metadata,jinja2,markupsafe,oauthlib,paho,paramiko,psutil,pyaarlo,requests,ring_doorbell,sphinx,sphinx_rtd_theme,twilio,win32event,win32service,win32serviceutil known_first_party = Alerters,Loggers,Monitors,envconfig,util,simplemonitor line_length=88 multi_line_output=3 include_trailing_comma=True [mypy] ignore_missing_imports = True simplemonitor-1.13.0/simplemonitor/000077500000000000000000000000001464501162400174015ustar00rootroot00000000000000simplemonitor-1.13.0/simplemonitor/Alerters/000077500000000000000000000000001464501162400211625ustar00rootroot00000000000000simplemonitor-1.13.0/simplemonitor/Alerters/__init__.py000066400000000000000000000017741464501162400233040ustar00rootroot00000000000000""" Alerters for SimpleMonitor """ from .bulksms import BulkSMSAlerter from .execute import ExecuteAlerter from .fortysixelks import FortySixElksAlerter from .mail import EMailAlerter from .nc import NotificationCenterAlerter from .nextcloud_notification import NextcloudNotificationAlerter from .ntfy import NtfyAlerter from .pushbullet import PushbulletAlerter from .pushover import PushoverAlerter from .ses import SESAlerter from .slack import SlackAlerter from .sms77 import SMS77Alerter from .sns import SNSAlerter from .syslogger import SyslogAlerter from .telegram import TelegramAlerter from .twilio import TwilioSMSAlerter __all__ = [ "BulkSMSAlerter", "ExecuteAlerter", "FortySixElksAlerter", "EMailAlerter", "NotificationCenterAlerter", "NextcloudNotificationAlerter", "PushbulletAlerter", "PushoverAlerter", "SESAlerter", "SlackAlerter", "SMS77Alerter", "SNSAlerter", "SyslogAlerter", "TelegramAlerter", "TwilioSMSAlerter", "NtfyAlerter", ] simplemonitor-1.13.0/simplemonitor/Alerters/alerter.py000066400000000000000000000462301464501162400231770ustar00rootroot00000000000000# coding=utf-8 """ Alerting for SimpleMonitor """ import datetime import logging import os import textwrap from enum import Enum from socket import gethostname from typing import Any, List, NoReturn, Optional, Tuple, Union, cast import arrow from ..Monitors.monitor import Monitor from ..util import ( MonitorState, check_group_match, format_datetime, get_config_option, short_hostname, subclass_dict_handler, ) class AlertType(Enum): """What type of alert should be sent""" NONE = "none" FAILURE = "failure" CATCHUP = "catchup" SUCCESS = "success" class AlertTimeFilter(Enum): """How should the Alerter times be handled""" ALWAYS = 0 # specified times are meaningless NOT = 1 # not allowed between the specified times ONLY = 2 # only allowed between the specified times class AlertLength(Enum): """How long should an Alert message be?""" NOTIFICATION = 0 # "Monitor has failed" SMS = 1 # <= 140 chars ONELINE = 5 # SMS but not length limited TERSE = 2 # Short but multiline FULL = 3 # Multiline ESSAY = 4 # Everything class Alerter: """BaseClass for Alerters""" alerter_type = "unknown" _dependencies = None # type: Optional[List[str]] hostname = gethostname() name = None # type: Optional[str] _ooh_failures = None # type: Optional[List[str]] # subclasses should set this to true if they support catchup notifications for delays support_catchup = False urgent = False def __init__(self, config_options: Optional[dict] = None) -> None: if config_options is None: config_options = {} self._config_options = config_options self.alerter_logger = logging.getLogger( "simplemonitor.alerter-" + self.alerter_type ) self.name = cast(str, self.get_config_option("name", default="unamed")) self.dependencies = cast( List[str], self.get_config_option("depend", required_type="[str]", default=[]), ) # require this many failures before firing self._limit = cast( int, self.get_config_option("limit", required_type="int", minimum=1, default=1), ) # fire every time, rather than just once when the Monitor fails self._repeat = self.get_config_option( "repeat", required_type="int", default=0, minimum=0 ) # only fire for Monitors with one of these groups self._groups = self.get_config_option( "groups", required_type="[str]", default=["default"] ) _times_type = cast( str, self.get_config_option( "times_type", required_type="str", allowed_values=["always", "only", "not"], default="always", ), ) self._times_type = AlertTimeFilter.ALWAYS # type: AlertTimeFilter if _times_type == "always": self._times_type = AlertTimeFilter.ALWAYS elif _times_type == "only": self._times_type = AlertTimeFilter.ONLY elif _times_type == "not": self._times_type = AlertTimeFilter.NOT else: raise ValueError("times_type is not recongnised: {}".format(_times_type)) self._time_info = ( None, None, ) # type: Tuple[Optional[datetime.time], Optional[datetime.time]] if self._times_type in [AlertTimeFilter.ONLY, AlertTimeFilter.NOT]: time_lower = str( self.get_config_option("time_lower", required_type="str", required=True) ) time_upper = str( self.get_config_option("time_upper", required_type="str", required=True) ) try: time_lower_split = list(map(int, time_lower.split(":"))) time_upper_split = list(map(int, time_upper.split(":"))) time_info = [ datetime.time(time_lower_split[0], time_lower_split[1]), datetime.time(time_upper_split[0], time_upper_split[1]), ] self._time_info = (time_info[0], time_info[1]) except Exception as error: raise RuntimeError("error processing time limit definition") from error self._days = cast( List[int], self.get_config_option( "days", required_type="[int]", allowed_values=list(range(0, 7)), default=list(range(0, 7)), ), ) self._delay_notification = self.get_config_option( "delay", required_type="bool", default=False ) self._dry_run = self.get_config_option( "dry_run", required_type="bool", default=False ) self._ooh_recovery = self.get_config_option( "ooh_recovery", required_type="bool", default=False ) if self.get_config_option("debug_times", required_type="bool", default=False): self._time_info = ( (datetime.datetime.utcnow() - datetime.timedelta(minutes=1)).time(), (datetime.datetime.utcnow() + datetime.timedelta(minutes=1)).time(), ) self.alerter_logger.debug("set times for alerter to %s", self._time_info) self._only_failures = self.get_config_option( "only_failures", required_type="bool", default=False ) self._tz = cast( str, self.get_config_option("tz", default=os.environ.get("TZ", "UTC")) ) self._times_tz = cast( str, self.get_config_option("times_tz", default=os.environ.get("TZ", "local")), ) self.urgent = cast( bool, self.get_config_option("urgent", default=self.urgent, required_type="bool"), ) if self._ooh_failures is None: self._ooh_failures = [] def get_config_option( self, key: str, *, default: Any = None, required: bool = False, required_type: str = "str", allowed_values: Any = None, allow_empty: bool = True, minimum: Optional[Union[int, float]] = None, maximum: Optional[Union[int, float]] = None, ) -> Any: """Get a config value. Throws the right flavour exception if something is wrong.""" return get_config_option( self._config_options, key, default=default, required=required, required_type=required_type, allowed_values=allowed_values, allow_empty=allow_empty, minimum=minimum, maximum=maximum, ) @property def dependencies(self) -> List[str]: """The Monitors we depend on. If a monitor we depend on fails, it means we can't reach the database, so we shouldn't bother trying to write to it.""" if self._dependencies is not None: return self._dependencies return [] @dependencies.setter def dependencies(self, dependency_list: List[str]) -> None: if not isinstance(dependency_list, list): raise TypeError("dependency_list must be a list") self._dependencies = dependency_list @property def groups(self) -> List[str]: """The groups for which we alert""" retval = cast(List[str], self._groups) return retval @groups.setter def groups(self, group_list: List[str]) -> None: if not isinstance(group_list, list): raise TypeError("group_list must be a list") self._groups = group_list def check_dependencies(self, failed_list: List[str]) -> bool: """Check if anything we depend on has failed.""" if self._dependencies is None or len(self._dependencies) == 0: return True for dependency in failed_list: if dependency in self._dependencies: return False return True def should_alert(self, monitor: Monitor) -> AlertType: """Check if we should bother alerting, and what type.""" out_of_hours = False if not check_group_match(monitor.group, self.groups): self.alerter_logger.debug( "not alerting for %s: group mismatch (monitor: %s; alerter: %s)", monitor.name, monitor.group, self.groups, ) return AlertType.NONE # Sanity check if not monitor.enabled: self.alerter_logger.debug( "not alerting for %s: monitor disabled", monitor.name ) return AlertType.NONE if self.urgent and not monitor.urgent: self.alerter_logger.debug( "not alerting for %s: alerter is urgent and monitor is not", monitor.name, ) return AlertType.NONE if not self._allowed_today(): out_of_hours = True if not self._allowed_time(): out_of_hours = True # ensure OOH list is initalised to the empty list if not done if self._ooh_failures is None: self._ooh_failures = [] virtual_failure_count = monitor.virtual_fail_count() if virtual_failure_count: self.alerter_logger.debug("monitor %s has failed", monitor.name) # Monitor has failed (not just first time) if self._delay_notification: # Delayed (catch-up) notifications are enabled if not out_of_hours: # Not out of hours try: self._ooh_failures.remove(monitor.name) # if it was in there and we support catchup alerts, do it if self.support_catchup: self.alerter_logger.debug( "alert for monitor %s is CATCHUP", monitor.name ) return AlertType.CATCHUP except ValueError: pass self.alerter_logger.debug( "alert for monitor %s is FAILURE", monitor.name ) return AlertType.FAILURE # Delayed notifications are not enabled (or are, and we didn't do anything above) if virtual_failure_count == self._limit or ( self._repeat and (virtual_failure_count % self._limit == 0) ): # This is the first time or nth time we've failed if out_of_hours: if monitor.name not in self._ooh_failures: self._ooh_failures.append(monitor.name) self.alerter_logger.debug("not alerting for %s: OOH", monitor.name) return AlertType.NONE self.alerter_logger.debug( "alert for monitor %s is FAILURE", monitor.name ) return AlertType.FAILURE self.alerter_logger.debug( "not alerting for monitor %s: not failed or repeated enough", monitor.name, ) return AlertType.NONE # Not failed if ( monitor.all_better_now() and monitor.last_virtual_fail_count() >= self._limit ): # was failed, and enough to have alerted self.alerter_logger.debug("monitor %s has recovered", monitor.name) try: self._ooh_failures.remove(monitor.name) except ValueError: pass if out_of_hours: if self._ooh_recovery and not self._only_failures: self.alerter_logger.debug( "alert for monitor %s is SUCCESS (OOH recovery)", monitor.name ) return AlertType.SUCCESS self.alerter_logger.debug( "not alerting for monitor %s: OOH and not recovery/only failures", monitor.name, ) return AlertType.NONE if not self._only_failures: self.alerter_logger.debug( "alert for monitor %s is SUCCESS", monitor.name ) return AlertType.SUCCESS return AlertType.NONE def send_alert(self, name: str, monitor: Any) -> Union[None, NoReturn]: """Abstract function to do the alerting.""" raise NotImplementedError def _allowed_today(self) -> bool: """Check if today is an allowed day for an alert.""" if arrow.now(self._times_tz).weekday() not in self._days: self.alerter_logger.debug("not allowed to alert today") return False return True def _allowed_time(self) -> bool: """Check if now is an allowed time for an alert.""" if self._times_type == AlertTimeFilter.ALWAYS: return True if self._time_info[0] is not None and self._time_info[1] is not None: now = arrow.now(self._times_tz).time() in_time_range = self._time_info[0] <= now < self._time_info[1] if self._times_type == AlertTimeFilter.ONLY: self.alerter_logger.debug("in_time_range: %s", in_time_range) return in_time_range if self._times_type == AlertTimeFilter.NOT: self.alerter_logger.debug( "in_time_range: %s (inverting due to AlertTimeFilter.NOT)", in_time_range, ) return not in_time_range self.alerter_logger.error( "this should never happen! Unknown times_type in alerter" ) return True @staticmethod def _get_verb(alert_type: AlertType) -> str: if alert_type == AlertType.CATCHUP: return "failed earlier" if alert_type == AlertType.FAILURE: return "failed" if alert_type == AlertType.SUCCESS: return "succeeded" return "unknowned" def build_message( self, length: AlertLength, alert_type: AlertType, monitor: Monitor ) -> str: """Create a message for an Alerter to send.""" if monitor.state() == MonitorState.FAILED: downtime = str(monitor.get_downtime()) elif monitor.state() == MonitorState.OK: downtime = str(monitor.get_wasdowntime()) else: downtime = "" host = " on {}".format( monitor.running_on if monitor.is_remote() else self.hostname ) max_length = None # type: Optional[int] if length == AlertLength.NOTIFICATION: message = "Monitor {monitor.name}{host} {alert_verb}".format( monitor=monitor, alert_verb=Alerter._get_verb(alert_type), host=host, ) elif length in [AlertLength.SMS, AlertLength.ONELINE]: host = " on {}".format( monitor.running_on if monitor.is_remote() else short_hostname() ) message = ( "{alert_type}: {monitor.name}{host} {alert_verb} " "at {failure_time} ({downtime}): {result}" ).format( alert_type=alert_type.value, alert_verb=Alerter._get_verb(alert_type), downtime=downtime, failure_time=format_datetime(monitor.first_failure_time(), self._tz), monitor=monitor, host=host, result=monitor.get_result(), ) if length == AlertLength.SMS: max_length = 160 elif length == AlertLength.TERSE: raise NotImplementedError elif length == AlertLength.FULL: if alert_type in [AlertType.CATCHUP, AlertType.FAILURE]: message = """ Monitor {monitor.name}{host} {alert_verb}! Failed at: {failure_time} (down {downtime}) Virtual failure count: {vfc} Additional info: {result} Description: {desc} """ if monitor.recover_info != "": message = message + "Recovery info: {}\n".format( monitor.recover_info ) if monitor.failure_doc: message = message + "Documentation: {}\n".format( monitor.failure_doc ) elif alert_type == AlertType.SUCCESS: message = """ Monitor {monitor.name}{host} {alert_verb}! Recovered at: {recovered_time} (was down for {downtime}) Additional info: {result} Description: {desc} """ if monitor.recovered_info != "": message = message + "Recovery info: {}".format( monitor.recovered_info ) else: raise ValueError( "Can't write a message for AlertType {}".format(alert_type) ) message = message.format( alert_type=alert_type, monitor=monitor, alert_verb=Alerter._get_verb(alert_type), failure_time=format_datetime(monitor.first_failure_time(), self._tz), downtime=downtime, result=monitor.get_result(), host=host, desc=monitor.describe(), vfc=monitor.virtual_fail_count(), recovered_time=format_datetime(monitor.last_update, self._tz), ) message = textwrap.dedent(message) else: raise NotImplementedError if max_length and len(message) > max_length: message = textwrap.shorten(message, width=max_length, placeholder="...") return message def _describe_times(self) -> str: """Return a string describing the times we're active.""" if self._times_type == AlertTimeFilter.ALWAYS: return "(always)" days_list = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] if self._days != list(range(0, 7)): allowed_days = ", ".join([days_list[day] for day in sorted(self._days)]) else: allowed_days = "any day" start, end = self._time_info if start is None or end is None: return "(misconfigured times)" message = "between {start} and {end} ({tz}) on {days}".format( start=start.strftime("%H:%M"), end=end.strftime("%H:%M"), days=allowed_days, tz=self._times_tz, ) if self._times_type == AlertTimeFilter.ONLY: return "only {}".format(message) return "any time except {}".format(message) def _describe_action(self) -> str: """Return a string explaining what we do. Should not include any time info""" raise NotImplementedError def describe(self) -> str: """Return a string explaining what we do.""" return "{desc} {when}".format( desc=self._describe_action(), when=self._describe_times() ) (register, get_class, all_types) = subclass_dict_handler( "simplemonitor.Alerters.alerter", Alerter, "alerter_type" ) simplemonitor-1.13.0/simplemonitor/Alerters/bulksms.py000066400000000000000000000053461464501162400232240ustar00rootroot00000000000000""" SimpleMonitor alerting via BulkSMS """ from typing import cast import requests from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class BulkSMSAlerter(Alerter): """ Send SMS alerts using the BulkSMS service Subscription required, see http://www.bulksms.co.uk""" alerter_type = "bulksms" urgent = True def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.username = cast( str, self.get_config_option("username", required=True, allow_empty=False) ) self.password = cast( str, self.get_config_option("password", required=True, allow_empty=False) ) self.target = cast( str, self.get_config_option("target", required=True, allow_empty=False) ) self.sender = cast(str, self.get_config_option("sender", default="SmplMntr")) if len(self.sender) > 11: self.alerter_logger.warning("truncating SMS sender name to 11 chars") self.sender = self.sender[:11] self.api_host = self.get_config_option("api_host", default="www.bulksms.co.uk") self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) self.support_catchup = True def send_alert(self, name: str, monitor: Monitor) -> None: """Send an SMS alert.""" alert_type = self.should_alert(monitor) if alert_type not in [AlertType.FAILURE, AlertType.SUCCESS]: return message = self.build_message(AlertLength.SMS, alert_type, monitor) url = f"https://{self.api_host}/eapi/submission/send_sms/2/2.0" params = { "username": self.username, "password": self.password, "message": message, "msisdn": self.target, "sender": self.sender, "repliable": "0", } if not self._dry_run: try: response = requests.get(url, params=params, timeout=self.timeout) status = response.text if not status.startswith("0"): self.alerter_logger.error( "Unable to send SMS: %s (%s)", status.split("|")[0], status.split("|")[1], ) except requests.exceptions.RequestException: self.alerter_logger.exception("SMS sending failed") else: self.alerter_logger.info( "dry_run: would send SMS: %s with message %s", url, message ) def _describe_action(self) -> str: return "SMSing {target} via BulkSMS".format(target=self.target) simplemonitor-1.13.0/simplemonitor/Alerters/execute.py000066400000000000000000000065571464501162400232130ustar00rootroot00000000000000""" SimpleMonitor command execution as alerts """ import shlex import subprocess # nosec from typing import Optional, cast from ..Monitors.monitor import Monitor from ..util import AlerterConfigurationError, format_datetime from .alerter import Alerter, AlertType, register @register class ExecuteAlerter(Alerter): """Execute an external command when a monitor fails or recovers""" alerter_type = "execute" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.fail_command = cast( str, self.get_config_option("fail_command", allow_empty=False) ) self.success_command = cast( str, self.get_config_option("success_command", allow_empty=False) ) self.catchup_command = cast( str, self.get_config_option("catchup_command", allow_empty=False) ) if ( self.fail_command is None and self.success_command is None and self.catchup_command is None ): raise AlerterConfigurationError("execute alerter has no commands defined") def send_alert(self, name: str, monitor: Monitor) -> None: """Execute the command""" alert_type = self.should_alert(monitor) command = None # type: Optional[str] downtime = monitor.get_downtime() if monitor.is_remote(): host = monitor.running_on else: host = self.hostname if alert_type == AlertType.NONE: return if alert_type == AlertType.FAILURE: command = self.fail_command elif alert_type == AlertType.SUCCESS: command = self.success_command elif alert_type == AlertType.CATCHUP: if self.catchup_command == "fail_command": command = self.fail_command else: self.alerter_logger.error("Unknown alert type %s", alert_type) return if command is None: return command = command.format( hostname=host, name=name, days=downtime.days, hours=downtime.hours, minutes=downtime.minutes, seconds=downtime.seconds, failed_at=format_datetime(monitor.first_failure_time()), virtual_fail_count=monitor.virtual_fail_count(), info=monitor.get_result(), description=monitor.describe(), last_virtual_fail_count=monitor.last_virtual_fail_count(), failure_doc=monitor.failure_doc, ) if not self._dry_run: self.alerter_logger.debug("About to execute command: %s", command) try: subprocess.call(shlex.split(command)) # nosec self.alerter_logger.debug("Command has finished.") except subprocess.SubprocessError: self.alerter_logger.exception( "Exception encountered running command: %s", command ) else: self.alerter_logger.info("Would run command: %s", command) def _describe_action(self) -> str: when = [] if self.success_command: when.append("success") if self.fail_command: when.append("failure") if self.catchup_command: when.append("catchup") return "running command(s) on {when}".format(when=", ".join(when)) simplemonitor-1.13.0/simplemonitor/Alerters/fortysixelks.py000066400000000000000000000054451464501162400243120ustar00rootroot00000000000000""" SimpleMonitor alerts via 46elks """ from typing import cast import requests from ..Monitors.monitor import Monitor from ..util import AlerterConfigurationError from .alerter import Alerter, AlertLength, AlertType, register @register class FortySixElksAlerter(Alerter): """ Send SMS alerts using the 46elks SMS service Account required, see https://www.46elks.com/ """ alerter_type = "46elks" urgent = True def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.username = cast( str, self.get_config_option("username", required=True, allow_empty=False) ) self.password = cast( str, self.get_config_option("password", required=True, allow_empty=False) ) self.target = cast( str, self.get_config_option("target", required=True, allow_empty=False) ) self.sender = cast(str, self.get_config_option("sender", default="SmplMntr")) if self.sender[0] == "+" and self.sender[1:].isdigit(): # sender is phone number pass elif len(self.sender) < 3: raise AlerterConfigurationError( "SMS sender name must be at least 3 chars long" ) elif len(self.sender) > 11: self.alerter_logger.warning("truncating SMS sender name to 11 chars") self.sender = self.sender[:11] self.api_host = self.get_config_option("api_host", default="api.46elks.com") self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) self.support_catchup = True def send_alert(self, name: str, monitor: Monitor) -> None: """Send an SMS alert.""" alert_type = self.should_alert(monitor) if alert_type not in [AlertType.CATCHUP, AlertType.FAILURE]: return message = self.build_message(AlertLength.SMS, alert_type, monitor) url = f"https://{self.api_host}/a1/SMS" auth = (self.username, self.password) params = {"from": self.sender, "to": self.target, "message": message} if not self._dry_run: try: response = requests.post( url, data=params, auth=auth, timeout=self.timeout ) status = response.json() if status["status"] not in ("created", "delivered"): self.alerter_logger.error("Unable to send SMS: %s", status) except requests.exceptions.RequestException: self.alerter_logger.exception("SMS sending failed") else: self.alerter_logger.info("dry_run: would send SMS: %s", url) def _describe_action(self) -> str: return "SMSing {target} via 46elks".format(target=self.target) simplemonitor-1.13.0/simplemonitor/Alerters/mail.py000066400000000000000000000072171464501162400224650ustar00rootroot00000000000000""" SimpleMonitor alerts via email/SMTP """ import email.utils import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from typing import Optional, cast from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class EMailAlerter(Alerter): """Send email alerts using SMTP to a mail server""" alerter_type = "email" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.mail_host = cast( str, self.get_config_option("host", required=True, allow_empty=False) ) self.from_addr = cast( str, self.get_config_option("from", required=True, allow_empty=False) ) self.to_addr = cast( str, self.get_config_option("to", required=True, allow_empty=False) ) self.cc_addr = cast(Optional[str], self.get_config_option("cc", required=False)) self.mail_port = cast( int, self.get_config_option("port", required_type="int", default=25) ) self.username = cast(str, self.get_config_option("username")) self.password = cast(str, self.get_config_option("password")) self.ssl = cast( Optional[str], self.get_config_option("ssl", allowed_values=["starttls", "yes", None]), ) self.support_catchup = True def send_alert(self, name: str, monitor: Monitor) -> None: """Send the email""" alert_type = self.should_alert(monitor) if alert_type == AlertType.NONE: return message = MIMEMultipart() message["From"] = self.from_addr message["To"] = self.to_addr.replace(";", ",") message["Date"] = email.utils.formatdate() envelope_to = self.to_addr.split(";") if self.cc_addr: message["CC"] = self.cc_addr.replace(";", ",") envelope_to.extend(self.cc_addr.split(";")) message["Subject"] = self.build_message( AlertLength.NOTIFICATION, alert_type, monitor ) body = self.build_message(AlertLength.FULL, alert_type, monitor) message.attach(MIMEText(body, "plain")) if not self._dry_run: try: if self.ssl is None or self.ssl == "starttls": server = smtplib.SMTP(self.mail_host, self.mail_port) elif self.ssl == "yes": server = smtplib.SMTP_SSL(self.mail_host, self.mail_port) else: self.alerter_logger.critical( "Cannot send mail, alerter's ssl configuration is broken" ) return if self.ssl == "starttls": server.starttls() if self.username is not None: try: server.login(self.username, self.password) except smtplib.SMTPNotSupportedError: self.alerter_logger.exception( "You may need to add ssl=starttls and/or port=587 to " "your alerter config" ) return server.sendmail(self.from_addr, envelope_to, message.as_string()) server.quit() except smtplib.SMTPException: self.alerter_logger.exception("couldn't send mail") else: self.alerter_logger.info( "dry_run: would send email: %s", message.as_string() ) def _describe_action(self) -> str: return "sending mail to {target}".format(target=self.to_addr) simplemonitor-1.13.0/simplemonitor/Alerters/nc.py000066400000000000000000000026601464501162400221400ustar00rootroot00000000000000""" SimpleMonitor alerts via macOS Notification Center """ import platform try: import pync PYNC_AVAILABLE = True except ImportError: PYNC_AVAILABLE = False from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class NotificationCenterAlerter(Alerter): """Send alerts to the macOS Notification Center""" alerter_type = "nc" def __init__(self, config_options: dict) -> None: super().__init__(config_options) if platform.system() != "Darwin" or not PYNC_AVAILABLE: self.alerter_logger.critical( "This alerter (currently) only works on Mac OS X!" ) return def send_alert(self, name: str, monitor: Monitor) -> None: """Send the message""" if not PYNC_AVAILABLE: self.alerter_logger.critical("Missing pync package") return alert_type = self.should_alert(monitor) message = "" if alert_type not in [AlertType.FAILURE, AlertType.CATCHUP]: return message = self.build_message(AlertLength.NOTIFICATION, alert_type, monitor) if not self._dry_run: pync.notify(message=message, title="SimpleMonitor") else: self.alerter_logger.info("dry_run: would send message: %s", message) def _describe_action(self) -> str: return "sending notifications via Notification Center" simplemonitor-1.13.0/simplemonitor/Alerters/nextcloud_notification.py000066400000000000000000000047761464501162400263250ustar00rootroot00000000000000""" SimpleMonitor alerts via Nextcloud Notifications """ from typing import cast import requests from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class NextcloudNotificationAlerter(Alerter): alerter_type = "nextcloud_notification" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.nextcloud_token = cast( str, self.get_config_option("token", required=True, allow_empty=False) ) self.nextcloud_user = cast( str, self.get_config_option("user", required=True, allow_empty=False) ) self.nextcloud_server = cast( str, self.get_config_option("server", required=True, allow_empty=False) ) self.nextcloud_receiver = cast( str, self.get_config_option("receiver", required=True, allow_empty=False) ) self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) self.support_catchup = True def send_nextcloud_notification(self, shortMessage: str, longMessage: str) -> None: uri = "ocs/v2.php/apps/notifications/api/v2/admin_notifications" requests.post( ( f"https://{self.nextcloud_user}:{self.nextcloud_token}" f"@{self.nextcloud_server}/{uri}/{self.nextcloud_receiver}" ), headers={"OCS-APIREQUEST": "true"}, data={ "shortMessage": shortMessage, "longMessage": longMessage, }, timeout=self.timeout, ) def send_alert(self, name: str, monitor: Monitor) -> None: """Build up the content for the push notification.""" alert_type = self.should_alert(monitor) if alert_type == AlertType.NONE: return short_message = self.build_message( AlertLength.NOTIFICATION, alert_type, monitor ) long_message = self.build_message(AlertLength.FULL, alert_type, monitor) if not self._dry_run: try: self.send_nextcloud_notification(short_message, long_message) except Exception: self.alerter_logger.exception("Couldn't send push notification") else: self.alerter_logger.info( "dry_run: would send push notification: %s", long_message ) def _describe_action(self) -> str: return "posting a Nextcloud Notification" simplemonitor-1.13.0/simplemonitor/Alerters/ntfy.py000066400000000000000000000076641464501162400225310ustar00rootroot00000000000000""" SimpleMonitor alerts via ntfy """ from typing import cast import requests from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class NtfyAlerter(Alerter): """Send push notification via ntfy.""" alerter_type = "ntfy" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.ntfy_token = cast(str, self.get_config_option("token")) self.ntfy_topic = cast(str, self.get_config_option("topic", required=True)) self.ntfy_server = cast( str, self.get_config_option("server", default="https://ntfy.sh") ) self.ntfy_priority = cast( str, self.get_config_option( "priority", required_type="str", allowed_values=[ "max", "urgent", "high", "default", "low", "min", "1", "2", "3", "4", "5", ], default="default", ), ) self.ntfy_tags = cast(str, self.get_config_option("tags", required_type="str")) self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) self.support_catchup = True # prefix icon to subject self.ntfy_icon_prefix = cast( str, self.get_config_option("icon_prefix", required_type="bool", default=False), ) self.ntfy_icon_failed = chr( int( cast( str, self.get_config_option( "icon_failed", required_type="str", default="274C" ), ), 16, ) ) self.ntfy_icon_succeeded = chr( int( cast( str, self.get_config_option( "icon_succeeded", required_type="str", default="2705" ), ), 16, ) ) def send_ntfy_notification(self, subject: str, body: str) -> None: """Send a push notification.""" requests.post( f"{self.ntfy_server}/{self.ntfy_topic}", data=body, headers={ "Title": subject.encode("UTF-8"), "Priority": self.ntfy_priority, **({"Tags": self.ntfy_tags} if self.ntfy_tags else {}), **( {"Authorization": f"Bearer {self.ntfy_token}"} if self.ntfy_token else {} ), }, timeout=self.timeout, ) def send_alert(self, name: str, monitor: Monitor) -> None: """Build up the content for the push notification.""" alert_type = self.should_alert(monitor) if alert_type == AlertType.NONE: return subject = self.build_message(AlertLength.NOTIFICATION, alert_type, monitor) body = self.build_message(AlertLength.FULL, alert_type, monitor) # prefix icon to subject when relevant if self.ntfy_icon_prefix and subject.endswith("failed"): subject = f"{self.ntfy_icon_failed} {subject}" if self.ntfy_icon_prefix and subject.endswith("succeeded"): subject = f"{self.ntfy_icon_succeeded} {subject}" if not self._dry_run: try: self.send_ntfy_notification(subject, body) except Exception: self.alerter_logger.exception("Couldn't send ntfy notification") else: self.alerter_logger.info("dry_run: would send nfty notification: %s", body) def _describe_action(self) -> str: return f"posting to ntfy topic {self.ntfy_topic}" simplemonitor-1.13.0/simplemonitor/Alerters/pushbullet.py000066400000000000000000000040541464501162400237260ustar00rootroot00000000000000""" SimpleMonitor alerts via pushbullet """ from typing import cast import requests import requests.auth from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class PushbulletAlerter(Alerter): """Send push notification via Pushbullet.""" alerter_type = "pushbullet" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.pushbullet_token = cast( str, self.get_config_option("token", required=True, allow_empty=False) ) self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) self.support_catchup = True def send_pushbullet_notification(self, subject: str, body: str) -> None: """Send a push notification.""" _payload = {"type": "note", "title": subject, "body": body} _auth = requests.auth.HTTPBasicAuth(self.pushbullet_token, "") response = requests.post( "https://api.pushbullet.com/v2/pushes", data=_payload, auth=_auth, timeout=self.timeout, ) if not response.status_code == requests.codes.ok: raise RuntimeError("Unable to send Pushbullet notification") def send_alert(self, name: str, monitor: Monitor) -> None: """Build up the content for the push notification.""" alert_type = self.should_alert(monitor) if alert_type == AlertType.NONE: return subject = self.build_message(AlertLength.NOTIFICATION, alert_type, monitor) body = self.build_message(AlertLength.FULL, alert_type, monitor) if not self._dry_run: try: self.send_pushbullet_notification(subject, body) except Exception: self.alerter_logger.exception("Couldn't send push notification") else: self.alerter_logger.info("dry_run: would send push notification: %s", body) def _describe_action(self) -> str: return "posting to pushbullet" simplemonitor-1.13.0/simplemonitor/Alerters/pushover.py000066400000000000000000000037741464501162400234220ustar00rootroot00000000000000""" SimpleMonitor alerts via pushover """ from typing import cast import requests from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class PushoverAlerter(Alerter): """Send push notification via Pushover.""" alerter_type = "pushover" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.pushover_token = cast( str, self.get_config_option("token", required=True, allow_empty=False) ) self.pushover_user = cast( str, self.get_config_option("user", required=True, allow_empty=False) ) self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) self.support_catchup = True def send_pushover_notification(self, subject: str, body: str) -> None: """Send a push notification.""" requests.post( "https://api.pushover.net/1/messages.json", data={ "token": self.pushover_token, "user": self.pushover_user, "title": subject, "message": body, }, timeout=self.timeout, ) def send_alert(self, name: str, monitor: Monitor) -> None: """Build up the content for the push notification.""" alert_type = self.should_alert(monitor) if alert_type == AlertType.NONE: return subject = self.build_message(AlertLength.NOTIFICATION, alert_type, monitor) body = self.build_message(AlertLength.FULL, alert_type, monitor) if not self._dry_run: try: self.send_pushover_notification(subject, body) except Exception: self.alerter_logger.exception("Couldn't send push notification") else: self.alerter_logger.info("dry_run: would send push notification: %s", body) def _describe_action(self) -> str: return "posting to pushover" simplemonitor-1.13.0/simplemonitor/Alerters/ses.py000066400000000000000000000050101464501162400223220ustar00rootroot00000000000000""" SimpleMonitor alerts via Amazon Simple Email Service """ import os from typing import Any, Dict, cast import boto3 from botocore.exceptions import ClientError from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class SESAlerter(Alerter): """Send email alerts using Amazon's SES service.""" alerter_type = "ses" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.from_addr = cast(str, self.get_config_option("from", allow_empty=False)) self.to_addr = cast(str, self.get_config_option("to", allow_empty=False)) self.support_catchup = True self.ses_client_params = {} # type: Dict[str, str] aws_region = cast(str, self.get_config_option("aws_region")) if aws_region: os.environ["AWS_DEFAULT_REGION"] = aws_region aws_access_key = cast(str, self.get_config_option("aws_access_key")) aws_secret_key = cast(str, self.get_config_option("aws_secret_access_key")) if aws_access_key and aws_secret_key: self.ses_client_params["aws_access_key_id"] = aws_access_key self.ses_client_params["aws_secret_access_key"] = aws_secret_key def send_alert(self, name: str, monitor: Monitor) -> None: """Send the email.""" alert_type = self.should_alert(monitor) mail = {} # type: Dict[str, Any] mail["Source"] = self.from_addr mail["Destination"] = {"ToAddresses": [self.to_addr]} message = {} # type: Dict[str, Any] if alert_type == AlertType.NONE: return message["Subject"] = { "Data": self.build_message(AlertLength.NOTIFICATION, alert_type, monitor) } message["Body"] = { "Text": {"Data": self.build_message(AlertLength.FULL, alert_type, monitor)} } mail["Message"] = message if not self._dry_run: try: client = boto3.client("ses", **self.ses_client_params) client.send_email(**mail) except ClientError: self.alerter_logger.exception("couldn't send mail") else: self.alerter_logger.info("dry_run: would send email:") self.alerter_logger.info(" Subject: %s", message["Subject"]["Data"]) self.alerter_logger.info(" Body: %s", message["Body"]["Text"]["Data"]) def _describe_action(self) -> str: return "emailing {target} via SES".format(target=self.to_addr) simplemonitor-1.13.0/simplemonitor/Alerters/slack.py000066400000000000000000000106501464501162400226330ustar00rootroot00000000000000""" SimpleMonitor alerts via Slack webhooks """ from typing import Any, Dict, cast import requests from ..Monitors.monitor import Monitor from ..util import format_datetime from .alerter import Alerter, AlertType, register @register class SlackAlerter(Alerter): """Send alerts to a Slack webhook""" alerter_type = "slack" channel = None username = None def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.url = cast( str, self.get_config_option("url", required=True, allow_empty=False) ) self.channel = cast(str, self.get_config_option("channel")) self.username = cast(str, self.get_config_option("username")) self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) def send_alert(self, name: str, monitor: Monitor) -> None: """Send the message""" alert_type = self.should_alert(monitor) downtime = monitor.get_downtime() message_json = {} # type: Dict[str, Any] if self.channel is not None: message_json = {"channel": self.channel} elif self.username is not None: message_json = {"username": self.username} else: message_json = {} message_json["attachments"] = [{}] if alert_type == AlertType.NONE: return if alert_type == AlertType.FAILURE: message_json["text"] = f"Monitor {name} failed!" message_json["attachments"][0]["color"] = "danger" fields = [ { "title": "Failed at", "value": format_datetime(monitor.first_failure_time()), "short": True, }, {"title": "Downtime", "value": str(downtime), "short": True}, { "title": "Virtual failure count", "value": monitor.virtual_fail_count(), "short": True, }, {"title": "Host", "value": self.hostname, "short": True}, {"title": "Additional info", "value": monitor.get_result()}, {"title": "Description", "value": monitor.describe()}, ] try: if monitor.recover_info != "": fields.append( { "title": "Recovery info", "value": f"Recovery info: {monitor.recover_info}", } ) message_json["attachments"][0]["color"] = "warning" except AttributeError: pass message_json["attachments"][0]["fields"] = fields elif alert_type == AlertType.SUCCESS: message_json["text"] = f"Monitor {name} succeeded." fields = [ { "title": "Failed at", "value": format_datetime(monitor.first_failure_time()), "short": True, }, {"title": "Downtime", "value": str(downtime), "short": True}, {"title": "Host", "value": self.hostname, "short": True}, {"title": "Description", "value": monitor.describe()}, ] message_json["attachments"][0]["color"] = "good" message_json["attachments"][0]["fields"] = fields else: self.alerter_logger.error("unknown alert type %s", alert_type) return if not self._dry_run: try: response = requests.post( self.url, json=message_json, timeout=self.timeout ) if not response.status_code == 200: self.alerter_logger.error( "POST to slack webhook failed: %s", response ) except requests.exceptions.RequestException: self.alerter_logger.exception("Failed to post to slack webhook") else: self.alerter_logger.info( "dry_run: would send slack: %s", message_json.__repr__() ) def _describe_action(self) -> str: target = "" if self.channel: target = self.channel elif self.username: target = self.username if target: return f"posting to {target} on Slack" return "posting to Slack" simplemonitor-1.13.0/simplemonitor/Alerters/sms77.py000066400000000000000000000043141464501162400225160ustar00rootroot00000000000000""" SimpleMonitor alerts via SMS77 """ from typing import cast import requests from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class SMS77Alerter(Alerter): """Send SMS alerts using the sms77 service""" alerter_type = "sms77" urgent = True def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.api_key = cast( str, self.get_config_option("api_key", required=True, allow_empty=False) ) self.target = cast( str, self.get_config_option("target", required=True, allow_empty=False) ) self.sender = cast(str, self.get_config_option("sender", default="SmplMntr")) if len(self.sender) > 11: self.alerter_logger.warning("truncating SMS sender name to 11 chars") self.sender = self.sender[:11] self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) self.support_catchup = True def send_alert(self, name: str, monitor: Monitor) -> None: """Send an SMS alert""" alert_type = self.should_alert(monitor) if alert_type not in [AlertType.FAILURE, AlertType.SUCCESS]: return message = self.build_message(AlertLength.SMS, alert_type, monitor) url = "https://gateway.sms77.io/api/sms" params = { "text": message, "to": self.target, "from": self.sender, "p": self.api_key, } if not self._dry_run: try: response = requests.get(url, params=params, timeout=self.timeout) status = response.text if not status.startswith("100"): self.alerter_logger.error( "Unable to send SMS: status code %s", status ) except requests.RequestException: self.alerter_logger.exception("SMS sending failed") else: self.alerter_logger.info("dry_run: would send SMS: %s", message) def _describe_action(self) -> str: return "SMSing {target} via SMS77".format(target=self.target) simplemonitor-1.13.0/simplemonitor/Alerters/sns.py000066400000000000000000000064551464501162400223510ustar00rootroot00000000000000""" SimpleMonitor alerts via Amazon SNS """ from typing import Dict, Optional, cast import boto3 from botocore.exceptions import ClientError from ..Monitors.monitor import Monitor from ..util import AlerterConfigurationError from .alerter import Alerter, AlertLength, AlertType, register @register class SNSAlerter(Alerter): """Send notifications using Amazon SNS""" alerter_type = "sns" urgent = True def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.topic = cast(str, self.get_config_option("topic", default="")) self.number = cast(str, self.get_config_option("number", default="")) if not self.topic and not self.number: raise AlerterConfigurationError("need one of topic or number to be set") if self.topic and self.number: raise AlerterConfigurationError("cannot set both topic and number") self.support_catchup = True self.sns_client_params = {} # type: Dict[str, str] aws_region = cast(str, self.get_config_option("aws_region", default="")) if aws_region: self.sns_client_params["region_name"] = aws_region aws_access_key = cast(str, self.get_config_option("aws_access_key", default="")) aws_secret_key = cast( str, self.get_config_option("aws_secret_access_key", default="") ) if aws_access_key and aws_secret_key: self.sns_client_params["aws_access_key_id"] = aws_access_key self.sns_client_params["aws_secret_access_key"] = aws_secret_key def send_alert(self, name: str, monitor: Monitor) -> None: """Send the alert""" alert_type = self.should_alert(monitor) if alert_type == AlertType.NONE: return subject = None # type: Optional[str] message = "Misconfiguration: could not build message" if self.topic: subject = self.build_message(AlertLength.NOTIFICATION, alert_type, monitor) message = self.build_message(AlertLength.FULL, alert_type, monitor) elif self.number: message = self.build_message(AlertLength.SMS, alert_type, monitor) if not self._dry_run: try: client = boto3.client("sns", **self.sns_client_params) if subject is None: client.publish(PhoneNumber=self.number, Message=message) else: client.publish( TopicArn=self.topic, Subject=subject, Message=message ) except ClientError: self.alerter_logger.exception("couldn't send notification") else: if subject is None: target = self.number else: target = self.topic self.alerter_logger.info("dry_run: would send notifiction to %s:", target) if subject is not None: self.alerter_logger.info(" Subject: %s", subject) self.alerter_logger.info(" Message: %s", message) def _describe_action(self) -> str: if self.topic: return "posting to SNS topic {topic}".format(topic=self.topic) if self.number: return "SMSing {target} via SNS".format(target=self.number) return "not sending anything via SNS" simplemonitor-1.13.0/simplemonitor/Alerters/syslogger.py000066400000000000000000000016401464501162400235530ustar00rootroot00000000000000""" SimpleMonitor alerts into syslog """ try: import syslog SYSLOG_AVAILABLE = True except ImportError: SYSLOG_AVAILABLE = False from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertType, register @register class SyslogAlerter(Alerter): """Send alerts to syslog""" alerter_type = "syslog" def send_alert(self, name: str, monitor: Monitor) -> None: if not SYSLOG_AVAILABLE: self.alerter_logger.critical("syslog not available") return alert_type = self.should_alert(monitor) if alert_type == AlertType.FAILURE: syslog.syslog( syslog.LOG_WARNING | syslog.LOG_USER, "Monitor %s failed %d times with message: %s" % (name, monitor.virtual_fail_count(), monitor.get_result()), ) def _describe_action(self) -> str: return "writing messages to syslog" simplemonitor-1.13.0/simplemonitor/Alerters/telegram.py000066400000000000000000000037431464501162400233430ustar00rootroot00000000000000""" SimpleMonitor alerts via Telegram """ from typing import cast import requests from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class TelegramAlerter(Alerter): """Send push notification via Telegram.""" alerter_type = "telegram" def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.telegram_token = cast( str, self.get_config_option("token", required=True, allow_empty=False) ) self.telegram_chatid = cast( str, self.get_config_option("chat_id", required=True, allow_empty=False) ) self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) self.support_catchup = True def send_telegram_notification(self, body: str) -> None: """Send a push notification.""" response = requests.post( f"https://api.telegram.org/bot{self.telegram_token}/sendMessage", data={"chat_id": self.telegram_chatid, "text": body}, timeout=self.timeout, ) if not response.status_code == requests.codes.ok: raise RuntimeError("Unable to send telegram notification") def send_alert(self, name: str, monitor: Monitor) -> None: """Build up the content for the push notification.""" alert_type = self.should_alert(monitor) if alert_type == AlertType.NONE: return body = self.build_message(AlertLength.FULL, alert_type, monitor) if not self._dry_run: try: self.send_telegram_notification(body) except Exception: self.alerter_logger.exception("Couldn't send push notification") else: self.alerter_logger.info("dry_run: would send push notification: %s", body) def _describe_action(self) -> str: return f"posting messages to {self.telegram_chatid} on Telegram" simplemonitor-1.13.0/simplemonitor/Alerters/twilio.py000066400000000000000000000041151464501162400230440ustar00rootroot00000000000000""" SimpleMonitor alerts via Twilio """ from typing import cast from twilio.base.exceptions import TwilioRestException from twilio.rest import Client from ..Monitors.monitor import Monitor from .alerter import Alerter, AlertLength, AlertType, register @register class TwilioSMSAlerter(Alerter): """Send SMS alerts using Twilio""" alerter_type = "twilio_sms" urgent = True def __init__(self, config_options: dict) -> None: super().__init__(config_options) account_sid = cast( str, self.get_config_option("account_sid", required=True, allow_empty=False) ) auth_token = cast( str, self.get_config_option("auth_token", required=True, allow_empty=False) ) self.target = cast( str, self.get_config_option("target", required=True, allow_empty=False) ) self.client = Client(account_sid, auth_token) self.sender = cast(str, self.get_config_option("sender", default="SmplMntr")) if not self.sender.startswith("+") and len(self.sender) > 11: self.alerter_logger.warning("truncating SMS sender name to 11 chars") self.sender = self.sender[:11] self.support_catchup = True def send_alert(self, name: str, monitor: Monitor) -> None: """Send an SMS alert.""" alert_type = self.should_alert(monitor) if alert_type not in [AlertType.FAILURE, AlertType.SUCCESS]: return message = self.build_message(AlertLength.SMS, alert_type, monitor) params = { "body": message, "to": self.target, "from_": self.sender, } if not self._dry_run: try: self.client.messages.create(**params) except TwilioRestException: self.alerter_logger.exception("SMS sending failed") else: self.alerter_logger.info( "dry_run: would send SMS with Twilio: %s", str(params) ) def _describe_action(self) -> str: return "SMSing {target} via Twilio".format(target=self.target) simplemonitor-1.13.0/simplemonitor/Loggers/000077500000000000000000000000001464501162400210035ustar00rootroot00000000000000simplemonitor-1.13.0/simplemonitor/Loggers/__init__.py000066400000000000000000000005451464501162400231200ustar00rootroot00000000000000""" Loggers for SimpleMonitor """ from .db import DBFullLogger, DBStatusLogger from .file import FileLogger from .mqtt import MQTTLogger from .network import Listener, NetworkLogger from .seq import SeqLogger __all__ = [ "DBFullLogger", "DBStatusLogger", "FileLogger", "Listener", "MQTTLogger", "NetworkLogger", "SeqLogger", ] simplemonitor-1.13.0/simplemonitor/Loggers/db.py000066400000000000000000000156701464501162400217530ustar00rootroot00000000000000""" SimpleMonitor logging with sqlite """ try: import sqlite3 SQLLITE_AVAILABLE = True except ImportError: SQLLITE_AVAILABLE = False import time from socket import gethostname from ..Monitors.monitor import Monitor from .logger import Logger, register CREATE_SQL = [ """ -- sqlite3 schema for monitor.db -- version 1 CREATE TABLE IF NOT EXISTS results( result_id integer primary key, monitor_host varchar(50), monitor_name varchar(50), monitor_type varchar(50), monitor_params varchar(100), monitor_result int, timestamp int, monitor_info varchar(255)); CREATE TABLE IF NOT EXISTS status ( monitor_host varchar(50), monitor_name varchar(50), monitor_result int, monitor_info varchar(255)); CREATE TABLE IF NOT EXISTS monitor_schema ( k varchar(50) primary key, v varchar(255) ); INSERT OR IGNORE INTO monitor_schema (k, v) VALUES ('monitor_schema_version', 1) """ ] class DBLogger(Logger): """Abstract class which uses a sqlite3 backend.""" hostname = gethostname() connected = False def __init__(self, config_options: dict) -> None: """Open the database connection.""" super().__init__(config_options) if not SQLLITE_AVAILABLE: self.logger_logger.critical( "Python sqlite support not available. Maybe you need to install " "sqlite-devel or libsqlite3-dev, or similar?" ) self.connected = False return self.db_path = self.get_config_option( "db_path", required=True, allow_empty=False ) self.db_handle = sqlite3.connect(self.db_path, isolation_level=None) self.db_handle.row_factory = sqlite3.Row self.connected = True self.check_schema() def check_schema(self) -> None: """Create tables if needed, and check the schema.""" self.db_handle.executescript(CREATE_SQL[0]) cursor = self.db_handle.cursor() current_schema = None expected_schema = len(CREATE_SQL) for row in cursor.execute( "SELECT v AS value FROM monitor_schema where k = 'monitor_schema_version'" ): current_schema = int(row["value"]) if current_schema is None: self.logger_logger.error( "Could not check current schema version! Expect weirdness." ) return if current_schema < expected_schema: self.logger_logger.warning( "Schema for %s is out of date: current is %d, latest is %d.", self.db_path, current_schema, expected_schema, ) self.roll_schema_forward(current_schema) elif current_schema > expected_schema: self.logger_logger.critical( "Schema for %s is newer than this code! Cannot use this database file.", self.db_path, ) self.connected = False else: self.logger_logger.debug("Schema for %s is current", self.db_path) def roll_schema_forward(self, start: int) -> None: """Update the db schema""" for sql in CREATE_SQL[start:]: self.logger_logger.info("Applying SQL schema update") self.logger_logger.debug(sql) try: self.db_handle.executescript(sql) except Exception: self.logger_logger.exception("Failed to apply schema update") self.logger_logger.critical( "Cannot use this DB logger until schema is fixed!" ) self.connected = False def save_result2(self, name: str, monitor: Monitor) -> None: raise NotImplementedError @register class DBFullLogger(DBLogger): """Logs results to a sqlite3 db.""" logger_type = "db" def save_result( self, monitor_name: str, monitor_type: str, monitor_params: str, monitor_result: int, monitor_info: str, hostname: str = "", ) -> None: """Write to the database.""" if not self.connected: self.logger_logger.warning("cannot send results, a dependency failed") return sql = ( "INSERT INTO results (result_id, monitor_host, monitor_name, monitor_type, " "monitor_params, monitor_result, timestamp, monitor_info) " "VALUES (null, ?, ?, ?, ?, ?, ?, ?)" ) cursor = self.db_handle.cursor() join_string = ":" timestamp = int(time.time()) if hostname == "": hostname = self.hostname params = ( hostname, monitor_name, monitor_type, join_string.join([str(x) for x in monitor_params]), monitor_result, timestamp, monitor_info, ) try: cursor.execute(sql, params) except sqlite3.OperationalError: self.logger_logger.exception("sqlite failed to write to database") def save_result2(self, name: str, monitor: Monitor) -> None: """new interface.""" if monitor.test_success(): result = 1 else: result = 0 self.save_result( name, monitor.monitor_type, str(monitor.get_params()), result, monitor.describe(), ) def describe(self) -> str: return "Logging results to {0}".format(self.db_path) @register class DBStatusLogger(DBLogger): """Maintains status snapshot in db.""" logger_type = "dbstatus" def _save_result( self, monitor_name: str, monitor_type: str, monitor_params: str, monitor_result: int, monitor_info: str, hostname: str = "", ) -> None: """Original implementation for saving a result""" if hostname == "": hostname = self.hostname cursor = self.db_handle.cursor() try: cursor.execute( "DELETE FROM status WHERE monitor_host = ? AND monitor_name = ?", (self.hostname, monitor_name), ) cursor.execute( ( "REPLACE INTO status (monitor_host, monitor_name, " "monitor_result, monitor_info) " "VALUES (?, ?, ?, ?)" ), (hostname, monitor_name, monitor_result, monitor_info), ) except sqlite3.OperationalError: self.logger_logger.exception("sqlite failed to write to database") def save_result2(self, name: str, monitor: Monitor) -> None: """Record a result""" if monitor.test_success(): result = 1 else: result = 0 self._save_result( name, monitor.monitor_type, str(monitor.get_params()), result, monitor.describe(), ) def describe(self) -> str: return f"Logging status to {self.db_path}" simplemonitor-1.13.0/simplemonitor/Loggers/file.py000066400000000000000000000450621464501162400223030ustar00rootroot00000000000000""" SimpleMonitor logging to files """ import glob import json import logging import logging.handlers import os import shutil import socket import stat import subprocess # nosec import sys import tempfile from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast import arrow from jinja2 import Environment, FileSystemLoader, select_autoescape from ..Monitors.monitor import Monitor from ..util import ( copy_if_different, format_datetime, short_hostname, size_string_to_bytes, ) from ..version import VERSION from .logger import Logger, register if TYPE_CHECKING: import datetime @register class FileLogger(Logger): """Log monitor status to a file.""" logger_type = "logfile" filename = "" only_failures = False buffered = True dateformat = None def __init__(self, config_options: Optional[Dict[str, Any]] = None) -> None: if config_options is None: config_options = {} super().__init__(config_options) self.filename = self.get_config_option( "filename", required=True, allow_empty=False ) self.file_handle = open( self.filename, "a+" ) # pylint: disable=consider-using-with self.only_failures = self.get_config_option( "only_failures", required_type="bool", default=False ) self.buffered = self.get_config_option( "buffered", required_type="bool", default=True ) self.file_handle.write( "{} simplemonitor starting\n".format(self._get_datestring()) ) if not self.buffered: self.file_handle.flush() def __del__(self) -> None: self.file_handle.close() def save_result2(self, name: str, monitor: Monitor) -> None: if self.only_failures and monitor.virtual_fail_count() == 0: return try: if monitor.virtual_fail_count() > 0: self.file_handle.write( "%s %s: failed since %s; VFC=%d (%s) (%0.3fs)" % ( self._get_datestring(), name, format_datetime(monitor.first_failure_time(), self.tz), monitor.virtual_fail_count(), monitor.get_result(), monitor.last_run_duration, ) ) else: self.file_handle.write( "%s %s: ok (%0.3fs)" % (self._get_datestring(), name, monitor.last_run_duration) ) self.file_handle.write("\n") if not self.buffered: self.file_handle.flush() except OSError: self.logger_logger.exception("Error writing to logfile %s", self.filename) def hup(self) -> None: """Close and reopen log file.""" try: self.file_handle.close() self.file_handle = open( self.filename, "a+" ) # pylint: disable=consider-using-with except OSError: self.logger_logger.exception( "Couldn't reopen log file %s after HUP", self.filename ) def describe(self) -> str: return "Writing log file to {0}".format(self.filename) @register class FileLoggerNG(Logger): """ Log monitor status to a file, Next Generation Uses the Python logging library to get features like rotation """ logger_type = "logfileng" only_failures = False def __init__(self, config_options: Optional[dict] = None) -> None: if config_options is None: config_options = {} super().__init__(config_options) self.only_failures = self.get_config_option( "only_failures", required_type="bool", default=False ) self._logger = logging.getLogger(f"logfileng-{self.name}") if self.only_failures: self._logger.setLevel(logging.WARNING) else: self._logger.setLevel(logging.INFO) rotation_type = self.get_config_option( "rotation_type", allowed_values=["time", "size"] ) self.filename = self.get_config_option("filename") self.backup_count = cast( int, self.get_config_option("backup_count", required_type="int", default=1) ) if rotation_type == "time": handler = logging.handlers.TimedRotatingFileHandler( filename=self.filename, when=self.get_config_option("when", default="h"), interval=self.get_config_option( "interval", default=1, required_type="int" ), backupCount=self.backup_count, utc=self.get_config_option("utc", required_type="bool", default=True), encoding="utf-8", ) # type: logging.handlers.BaseRotatingHandler elif rotation_type == "size": max_bytes = size_string_to_bytes(self.get_config_option("max_bytes")) if max_bytes is None: raise ValueError("Missing max_bytes") handler = logging.handlers.RotatingFileHandler( filename=self.filename, maxBytes=max_bytes, backupCount=self.backup_count, encoding="utf-8", ) else: raise ValueError(f"Invalid rotation_type {rotation_type}") self._logger.addHandler(handler) def save_result2(self, name: str, monitor: Monitor) -> None: if monitor.virtual_fail_count() == 0: self._logger.info( "%s %s: ok (%0.3fs) (%s)", self._get_datestring(), name, monitor.last_run_duration, monitor.get_result(), ) else: self._logger.warning( "%s %s: failed since %s; VFC=%d (%s) (%0.3fs)", self._get_datestring(), name, format_datetime(monitor.first_failure_time(), self.tz), monitor.virtual_fail_count(), monitor.get_result(), monitor.last_run_duration, ) @register class HTMLLogger(Logger): """A batching logger which writes a simple HTML page of the current state.""" logger_type = "html" supports_batch = True filename = "" count_data = "" def __init__(self, config_options: Optional[dict] = None) -> None: if config_options is None: config_options = {} super().__init__(config_options) package_path = sys.modules["simplemonitor"].__file__ or "." package_data_dir = os.path.join(os.path.dirname(package_path), "html") self.filename = cast( str, self.get_config_option("filename", required=True, allow_empty=False) ) self.source_folder = cast( str, self.get_config_option( "source_folder", required=False, default=package_data_dir ), ) self.folder = cast( str, self.get_config_option("folder", required=False, default="html") ) if not os.path.isdir(self.folder): self.logger_logger.critical("output folder %s does not exist", self.folder) self.copy_resources = cast( bool, self.get_config_option( "copy_resources", required_type="bool", default=True ), ) self.upload_command = cast( str, self.get_config_option("upload_command", required=False, allow_empty=False), ) self.map = cast( bool, self.get_config_option("map", required_type="bool", default=False) ) _map_start = cast( Optional[List[str]], self.get_config_option("map_start", required_type="[str]", default=None), ) if _map_start and len(_map_start) == 3: self.map_start = { "latitude": _map_start[0], "longitude": _map_start[1], "zoom": _map_start[2], } # type: Optional[Dict[str, str]] else: self.map_start = None if self.map and not self.map_start: raise RuntimeError( f"map is set for logger {self.name} but map_start is missing or badly formatted" ) self.map_token = cast(str, self.get_config_option("map_token")) if self.map_token: self.logger_logger.info( "map_token option for logger %s is no longer required; ignoring", self.name, ) self._resource_files = [ "dist/main.bundle.js*", "dist/maps.bundle.js*", "dist/*.png", ] self._my_host = short_hostname() self.status = "" self.header_class = "" self._env = Environment( loader=FileSystemLoader(self.source_folder), keep_trailing_newline=True, autoescape=select_autoescape("html"), ) def save_result2(self, name: str, monitor: Monitor) -> None: if not self.doing_batch: self.logger_logger.error( "HTMLLogger.save_result2() called while not doing batch." ) return if self.batch_data is None: self.batch_data = {} status = bool(monitor.virtual_fail_count() == 0) if not status: fail_time = format_datetime(monitor.first_failure_time(), self.tz) fail_count = monitor.virtual_fail_count() fail_data = monitor.get_result() downtime = monitor.get_downtime() else: fail_time = "" fail_count = 0 fail_data = monitor.get_result() downtime = monitor.get_uptime() # yes, I know failures = monitor.failures last_failure = format_datetime(monitor.last_failure, self.tz) gap = monitor.minimum_gap if gap == 0: # TODO: figure out a good way to know the interval value for both local and # remote monitors gap = 60 try: if monitor.last_update is not None: age = arrow.utcnow() - monitor.last_update # type: datetime.timedelta age_seconds = age.days * 3600 + age.seconds update = str(monitor.last_update) else: raise ValueError except ValueError: age_seconds = 0 update = "" row_class = "" cell_class = "" if not monitor.enabled: status_text = "DISABLED" elif age_seconds > gap + 60: status_text = "OLD" cell_class = "table-warning" elif status: status_text = "OK" cell_class = "table-success" else: status_text = "FAIL" row_class = "table-danger" data_line = { "monitor_name": monitor.name, "status": status, "status_text": status_text, "row_class": row_class, "cell_class": cell_class, "fail_time": fail_time, "fail_count": fail_count, "fail_data": fail_data, "downtime": downtime, "age": age_seconds, "update": update, "host": monitor.running_on, "failures": failures, "last_failure": last_failure, "gap": gap, "availability": monitor.availability, "description": monitor.describe(), "link": monitor.failure_doc, "enabled": monitor.enabled, "my_host": monitor.running_on == self._my_host, "gps": monitor.gps, } # type: Dict[str, Any] self.batch_data[monitor.name] = data_line def process_batch(self) -> None: """Save the HTML file.""" ok_count = 0 fail_count = 0 old_count = 0 remote_count = 0 disabled_count = 0 try: temp_file = tempfile.mkstemp() file_handle = os.fdopen(temp_file[0], "w") file_name = temp_file[1] except OSError: self.logger_logger.exception( "Couldn't create temporary file for HTML output" ) return fail_entries = [] # type: List[Dict[str, Any]] ok_entries = [] # type: List[Dict[str, Any]] if self.batch_data is None: return keys = list(self.batch_data.keys()) keys.sort() for entry in keys: this_entry = self.batch_data[entry] this_list = ok_entries if not this_entry["enabled"]: disabled_count += 1 elif this_entry["age"] > this_entry["gap"] + 60: old_count += 1 elif this_entry["status"]: ok_count += 1 else: fail_count += 1 this_list = fail_entries if this_entry["host"] != self._my_host: remote_count += 1 # output.write(self._make_html_row(entry, this_entry)) this_list.append(this_entry) if old_count > 0: self.header_class = "border-warning" self.status = "OLD" elif fail_count > 0: self.header_class = "border-danger" self.status = "FAIL" else: self.header_class = "border-success" self.status = "OK" template = self._env.get_template("status-template.html") if self._global_info: interval = max(30, self._global_info["interval"]) else: interval = 30 file_handle.write( template.render( status=self.status, status_border=self.header_class, host=socket.gethostname(), interval=interval, timestamp=str(arrow.now().int_timestamp), now=format_datetime(arrow.now(), self.tz), version=VERSION, ok_count=ok_count, fail_count=fail_count, disabled_count=disabled_count, old_count=old_count, remote_count=remote_count, fail_entries=fail_entries, ok_entries=ok_entries, map=self.map, map_start=self.map_start, map_token=self.map_token, ) ) try: file_handle.flush() file_handle.close() os.chmod( file_name, stat.S_IREAD | stat.S_IWRITE | stat.S_IRGRP | stat.S_IROTH ) if not os.path.isdir(self.folder): self.logger_logger.critical( "Target folder %s does not exist", self.folder ) return shutil.move(file_name, os.path.join(self.folder, self.filename)) if self.copy_resources: for fileglob in self._resource_files: for filename in glob.glob( os.path.join(self.source_folder, fileglob) ): if copy_if_different( os.path.join(self.source_folder, filename), self.folder ): self.logger_logger.info(f"copied {filename}") except OSError: self.logger_logger.exception("problem closing/moving files for HTML output") if self.upload_command: try: subprocess.run(self.upload_command.split(" "), check=True) # nosec except subprocess.SubprocessError: self.logger_logger.exception( "Failed to run upload command for HTML files" ) except FileNotFoundError: self.logger_logger.exception("Could not find upload command") def describe(self) -> str: return "Writing HTML page to {0}".format(self.filename) class MonitorResult: """Represent the current status of a Monitor.""" def __init__(self) -> None: self.virtual_fail_count = 0 self.result = None # type: Optional[str] self.first_failure_time = None # type: Optional[str] self.last_run_duration = None # type: Optional[int] self.status = "Fail" self.dependencies = [] # type: List[str] def json_representation(self) -> dict: """Get JSON representation""" return self.__dict__ class MonitorJsonPayload: def __init__(self) -> None: self.generated = None # type: Optional[str] self.monitors = {} # type: dict def json_representation(self) -> dict: """Get JSON res""presentation""" return self.__dict__ class PayloadEncoder(json.JSONEncoder): def default(self, o: Any) -> Any: if hasattr(o, "json_representation"): return o.json_representation() return json.JSONEncoder.default(self, o.__dict__) @register class JsonLogger(Logger): """Write monitor status to a JSON file.""" logger_type = "json" filename = "" # type: str supports_batch = True def __init__(self, config_options: Optional[dict] = None) -> None: if config_options is None: config_options = {} super().__init__(config_options) self.filename = self.get_config_option( "filename", required=True, allow_empty=False ) def save_result2(self, name: str, monitor: Monitor) -> None: if self.batch_data is None: self.batch_data = {} result = MonitorResult() result.first_failure_time = format_datetime(monitor.first_failure_time()) result.virtual_fail_count = monitor.virtual_fail_count() result.last_run_duration = monitor.last_run_duration result.result = monitor.get_result() if hasattr(monitor, "was_skipped") and monitor.was_skipped: result.status = "Skipped" elif monitor.virtual_fail_count() <= 0: result.status = "OK" result.dependencies = monitor.dependencies self.batch_data[name] = result def process_batch(self) -> None: payload = MonitorJsonPayload() payload.generated = format_datetime(arrow.now()) if self.batch_data is not None: payload.monitors = self.batch_data with open(self.filename, "w") as outfile: json.dump( payload, outfile, indent=4, separators=(",", ":"), ensure_ascii=False, cls=PayloadEncoder, ) self.batch_data = {} def describe(self) -> str: return "Writing JSON file to {0}".format(self.filename) simplemonitor-1.13.0/simplemonitor/Loggers/logger.py000066400000000000000000000135241464501162400226410ustar00rootroot00000000000000""" Logging for SimpleMonitor. Loggers process every monitor, every iteration, to record their state in some fashion. """ import logging import time from typing import Any, Dict, List, Optional, Union, cast import arrow from ..Monitors.monitor import Monitor from ..util import format_datetime, get_config_option, subclass_dict_handler class Logger: """Abstract class basis for loggers.""" logger_type = "unknown" supports_batch = False doing_batch = False batch_data = None # type: Optional[Dict[str, Any]] connected = True _global_info = None # type: Optional[Dict[str, Any]] def __init__(self, config_options: Dict[str, Any]) -> None: self._config_options = config_options self.name = cast(str, self.get_config_option("_name", default="unnamed")) self.logger_logger = logging.getLogger("simplemonitor.logger-" + self.name) self._dependencies = cast( List[str], self.get_config_option("depend", required_type="[str]", default=[]), ) # only log for Monitors with one of these groups self._groups = self.get_config_option( "groups", required_type="[str]", default=["default"] ) if self.batch_data is None: self.batch_data = {} self.tz = cast(Optional[str], self.get_config_option("tz", default="UTC")) self.dateformat = cast( Optional[str], self.get_config_option( "dateformat", required_type="str", allowed_values=["timestamp", "iso8601"], default="timestamp", ), ) if self._global_info is None: self._global_info = {} self.heartbeat = cast( bool, self.get_config_option("heartbeat", required_type="bool", default=False), ) def __enter__(self) -> None: """Context manager entry.""" self.start_batch() def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: """Context manager exit.""" self.end_batch() def set_global_info(self, info: dict) -> None: """Receive global info about the SimpleMonitor state. Includes but not limited to refresh interval, known remote instances, etc""" self._global_info = info def get_config_option( self, key: str, *, default: Any = None, required: bool = False, required_type: str = "str", allowed_values: Any = None, allow_empty: bool = True, minimum: Optional[Union[int, float]] = None, maximum: Optional[Union[int, float]] = None, ) -> Any: """Get a config value. Throws the right flavour exception if something is wrong.""" return get_config_option( self._config_options, key, default=default, required=required, required_type=required_type, allowed_values=allowed_values, allow_empty=allow_empty, minimum=minimum, maximum=maximum, ) def hup(self) -> None: """Close and reopen our log file, if supported. This should be overridden where needed.""" return # pragma: no cover def save_result2(self, name: str, monitor: Monitor) -> None: """Record a result. Subclasses must override this with their implementation.""" raise NotImplementedError def _get_datestring(self) -> str: """Format the current datetime according to the dateformat setting and timezone.""" if self.dateformat == "iso8601": return format_datetime(arrow.now(), self.tz) return str(int(time.time())) @property def dependencies(self) -> list: """The dependencies of this Logger.""" return self._dependencies @dependencies.setter def dependencies(self, dependency_list: List[str]) -> None: if not isinstance(dependency_list, list): raise TypeError("dependency_list must be a list") self._dependencies = dependency_list def check_dependencies(self, failed_list: List[str]) -> bool: """Compare a list of failed monitors to our dependencies, and mark the Logger as offline if one failed""" self.connected = True for dependency in failed_list: if dependency in self._dependencies: self.connected = False return self.connected @property def groups(self) -> List[str]: """The groups this Logger belongs to.""" return self._groups def start_batch(self) -> None: """Prepare to process a batch of results""" if not self.supports_batch: return if self.doing_batch: self.logger_logger.error( "starting a batch while one was already in progress" ) self.batch_data = {} self.doing_batch = True def end_batch(self) -> None: """End receiving a batch of results and process them""" if not self.supports_batch: return if not self.doing_batch: self.logger_logger.error("ending a batch when one wasn't in progress") self.process_batch() self.doing_batch = False def process_batch(self) -> None: """Process the batched data. This is blank for the base class.""" return # pragma: no cover def describe(self) -> str: """Explain what this logger does. We don't throw NotImplementedError here as it won't show up until something breaks, and we don't want to randomly die then.""" return "(Logger did not write an auto-biography.)" # pragma: no cover def __str__(self) -> str: return self.describe() (register, get_class, all_types) = subclass_dict_handler( "simplemonitor.Loggers.logger", Logger, "logger_type" ) simplemonitor-1.13.0/simplemonitor/Loggers/mqtt.py000066400000000000000000000137471464501162400223560ustar00rootroot00000000000000""" Simplemonitor logger for MQTT It is intended to be used with Home Assistant and its MQTT Discovery feature (this the default topic) but can be used in any other context contact: dev@swtk.info """ import json from typing import List, Optional, cast import paho.mqtt.publish from ..Monitors.monitor import Monitor from .logger import Logger, register @register class MQTTLogger(Logger): """Log to MQTT endpoints""" logger_type = "mqtt" only_failures = False buffered = False dateformat = None def __init__(self, config_options: Optional[dict] = None) -> None: if config_options is None: config_options = {} super().__init__(config_options) self.host = cast( str, self.get_config_option("host", required=True, allow_empty=False) ) self.port = cast( int, self.get_config_option( "port", required=False, allow_empty=False, required_type="int", default=1883, ), ) # specif configuration for Home Assistant MQTT discovery # https://www.home-assistant.io/docs/mqtt/discovery/ self.hass = cast( bool, self.get_config_option( "hass", required=False, allow_empty=False, required_type="bool", default=False, ), ) # topic to send information to self.topic = cast( str, self.get_config_option( "topic", required=False, allow_empty=False, default=( "simplemonitor" if not self.hass else "homeassistant/binary_sensor" ), ), ) # username for authentication self.username = cast( str, self.get_config_option( "username", required=False, allow_empty=True, ), ) # password for authentication self.password = cast( str, self.get_config_option( "password", required=False, allow_empty=True, ), ) self.device_class = cast( str, self.get_config_option("device_class", default=""), ) if self.username and self.password: self.auth = {"username": self.username, "password": self.password} else: self.auth = {} # registry of monitors which registered with HA # not used if not Home Assistant context # also see # https://github.com/jamesoff/simplemonitor/issues/236#issuecomment-462481900 # for rationale self.registered = [] # type: List[str] def save_result2(self, name: str, monitor: Monitor) -> None: safe_name = monitor.name if self.hass: if " " in safe_name: self.logger_logger.warning( ( "replacing spaces with underscores for monitor %s as spaces are " "not supported for MQTT/HASS names" ), monitor.name, ) safe_name = monitor.name.replace(" ", "_") if monitor.name not in self.registered: self.logger_logger.info( "attempting to register MQTT config topic for monitor %s", name ) config_payload = { "name": monitor.name, "state_topic": "{root}/simplemonitor_{monitor}/state".format( root=self.topic, monitor=safe_name ), } if self.device_class: config_payload["device_class"] = self.device_class try: paho.mqtt.publish.single( "{root}/simplemonitor_{monitor}/config".format( root=self.topic, monitor=safe_name, ), payload=json.dumps(config_payload), retain=True, hostname=self.host, port=self.port, auth=self.auth, client_id="simplemonitor_{monitor}".format( monitor=safe_name, ), ) except Exception: self.logger_logger.exception("cannot send %s to MQTT", monitor.name) else: self.registered.append(monitor.name) self.logger_logger.debug("registered %s in MQTT", safe_name) if self.only_failures and monitor.virtual_fail_count() == 0: return if self.hass: topic = "{root}/simplemonitor_{monitor}/state".format( root=self.topic, monitor=safe_name ) else: topic = "{root}/{monitor}".format(root=self.topic, monitor=safe_name) self.logger_logger.debug( "%s failed %d times", monitor.name, monitor.virtual_fail_count() ) payload = ( "ON" if monitor.virtual_fail_count() == 0 and not monitor.was_skipped else "OFF" ) try: paho.mqtt.publish.single( topic, payload=payload, retain=True, hostname=self.host, port=self.port, auth=self.auth, client_id="simplemonitor_{monitor}".format(monitor=safe_name), ) except Exception: self.logger_logger.exception("cannot send state %s to %s", payload, topic) else: self.logger_logger.debug("state %s sent to %s", payload, topic) def describe(self) -> str: return "Sends monitoring status to a MQTT broker" simplemonitor-1.13.0/simplemonitor/Loggers/network.py000066400000000000000000000236411464501162400230540ustar00rootroot00000000000000""" Network logging support for SimpleMonitor """ import hmac import logging import socket import struct from threading import Thread from typing import Any, Dict, Optional, Union, cast from ..Monitors.monitor import Monitor from ..util import LoggerConfigurationError from ..util.json_encoding import json_dumps, json_loads from .logger import Logger, register # From the docs: # Threads interact strangely with interrupts: the KeyboardInterrupt exception # will be received by an arbitrary thread. (When the signal module is # available, interrupts always go to the main thread.) _DIGEST_NAME = "md5" @register class NetworkLogger(Logger): """Send our results over the network to another instance.""" logger_type = "network" supports_batch = True def __init__(self, config_options: dict) -> None: super().__init__(config_options) self.host = cast( str, self.get_config_option("host", required=True, allow_empty=False) ) self.port = cast( int, self.get_config_option("port", required_type="int", required=True) ) self.hostname = cast(str, self.get_config_option("client_name")) self.key = bytearray( self.get_config_option("key", required=True, allow_empty=False), "utf-8" ) def describe(self) -> str: return "Sending monitor results to {0}:{1}".format(self.host, self.port) def save_result2(self, name: str, monitor: Monitor) -> None: if not self.doing_batch: # pragma: no cover self.logger_logger.error( "NetworkLogger.save_result2() called while not doing batch." ) return self.logger_logger.debug("network logger: %s %s", name, monitor) if monitor.monitor_type == "unknown": self.logger_logger.error( "Cannot serialize monitor %s, has type 'unknown'.", name ) return try: if monitor.monitor_type == "compound": self.logger_logger.error( "not pickling compound monitor - currently incompatible with network loggers" ) else: data = { "cls_type": monitor.monitor_type, "data": monitor.to_python_dict(), } if self.batch_data is not None: self.batch_data[monitor.name] = data else: self.batch_data = {monitor.name: data} # type: Dict[str, dict] except Exception: # pylint: disable=broad-except self.logger_logger.exception("Failed to serialize monitor %s", name) def process_batch(self) -> None: try: payload = json_dumps( { "version": 2, "name": self.hostname, "monitors": self.batch_data, } ) mac = hmac.new(self.key, payload, _DIGEST_NAME) send_bytes = struct.pack("B", mac.digest_size) + mac.digest() + payload sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: try: sock.connect((self.host, self.port)) except socket.error: sock.close() sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) sock.connect((self.host, self.port)) sock.send(send_bytes) finally: sock.close() except Exception as exception: # pylint: disable=broad-except self.logger_logger.exception("Failed to send network data: %s", exception) class Listener(Thread): """ Handle incoming remote connections. This class isn't actually a Logger, but is the receiving-end implementation for network logging. Here seemed a reasonable place to put it.""" def __init__( self, simplemonitor: Any, port: int, key: Optional[str] = None, bind_host: str = "", ipv4_only: bool = False, ) -> None: """Set up the thread. simplemonitor is a SimpleMonitor object which we will put our results into. """ if key is None or key == "": raise LoggerConfigurationError("Network logger key is missing") Thread.__init__(self, daemon=True) if ipv4_only: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) else: # try IPv6 and fallback to IPv4 try: self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) self.sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False) except OSError: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.bind((bind_host, port)) self.simplemonitor = simplemonitor self.key = bytearray(key, "utf-8") self.logger = logging.getLogger("simplemonitor.logger.networklistener") self.running = False # type: bool def run(self) -> None: """The main body of our thread. The loop here keeps going until we're killed by the main app. When the main app kills us (with join()), socket.listen throws socket.error. """ self.running = True while self.running: try: self.sock.listen(5) conn, addr = self.sock.accept() conn.settimeout(5.0) self.logger.debug("Got connection from %s", addr[0]) serialized = bytearray() while 1: data = conn.recv(1024) if not data: break serialized += data conn.close() if len(serialized) == 0: self.logger.debug("No data from %s", addr[0]) continue self.logger.debug("Finished receiving from %s", addr[0]) try: # first byte is the size of the MAC mac_size = serialized[0] # then the MAC their_digest = serialized[1 : mac_size + 1] # then the rest is the serialized data serialized = serialized[mac_size + 1 :] mac = hmac.new(self.key, serialized, _DIGEST_NAME) my_digest = mac.digest() except IndexError as error: # pragma: no cover raise ValueError( "Did not receive any or enough data from {}".format(addr[0]) ) from error if isinstance(my_digest, str): self.logger.debug( "Computed my digest to be %s; remote is %s", my_digest, their_digest, ) else: self.logger.debug( "Computed my digest to be %s; remote is %s", my_digest.hex(), their_digest.hex(), ) if not hmac.compare_digest(their_digest, my_digest): raise Exception( "Mismatched MAC for network logging data from %s\n" "Mismatched key? Old version of SimpleMonitor?\n" % addr[0] ) result = json_loads(bytes(serialized)) # type: dict version = result.get("version", 1) if version == 1: self.logger.debug("Received version 1 data from %s", addr[0]) self.simplemonitor.update_remote_monitor(result, addr[0]) elif version == 2: self.logger.debug("Received version 2 data from %s", addr[0]) self._handle_data_v2(result, addr[0]) else: self.logger.critical( "Received unknown version %s data from %s cannot process", version, addr[0], ) except socket.timeout: self.logger.warning("Timeout during recv from %s", addr[0]) conn.close() except socket.error as exception: if exception.errno == 4: # Interrupted system call self.logger.warning( "Interrupted system call in thread, I think that's a ^C" ) self.running = False self.sock.close() if self.running: self.logger.exception("Socket error caught in thread") except Exception: # pylint: disable=broad-except self.logger.exception("Listener thread caught exception") self.logger.warning("Listener stopped") def _handle_data_v2( self, data: Dict[str, Union[str, int, Dict[str, dict]]], source: str ) -> None: """Handle data in v2 format { "version": 2, "name": "remote_instance_name", "monitors": [ monitor data, ... ] } """ remote_instance_name = str(data.get("name", source)) if not remote_instance_name or remote_instance_name == "None": remote_instance_name = source remote_monitors = data.get("monitors", None) if remote_monitors is None: self.logger.error( "Received empty monitors list from remote instance %s", remote_instance_name, ) elif isinstance(remote_monitors, dict): self.simplemonitor.update_remote_monitor( remote_monitors, remote_instance_name ) else: self.logger.error( "Bad data type for monitors from remote instance %s", remote_instance_name, ) simplemonitor-1.13.0/simplemonitor/Loggers/seq.py000066400000000000000000000063061464501162400221520ustar00rootroot00000000000000""" Simplemonitor logger for seq Inspiration from https://raw.githubusercontent.com/eifinger/appdaemon-scripts/master/seqSink/seqSink.py """ import datetime import json from typing import Optional, cast import requests from ..Monitors.monitor import Monitor from .logger import Logger, register @register class SeqLogger(Logger): """Logging to seq""" logger_type = "seq" only_failures = False buffered = False dateformat = None def __init__(self, config_options: Optional[dict] = None) -> None: if config_options is None: config_options = {} super().__init__(config_options) # i.e. http://192.168.0.5:5341 self.endpoint = cast( str, self.get_config_option("endpoint", required=True, allow_empty=False) ) self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) # Potentially, would need to add a header for ApiKey # Send message to indicate we have started logging self.log_to_seq( self.endpoint, "SeqLogger", "simpleMonitor", "__init__", None, "logging enabled for simpleMonitor", False, ) def save_result2(self, name: str, monitor: Monitor) -> None: try: is_fail = monitor.test_success() is False self.log_to_seq( self.endpoint, name, monitor.name, monitor.monitor_type, str(monitor.get_params()), monitor.describe(), is_fail, ) except Exception: self.logger_logger.exception("Error sending to seq in %s", monitor.name) def describe(self) -> str: return "Sends simple log to seq using raw endpoint" def log_to_seq( self, endpoint, name, app_name, monitor_type, params, description, is_fail ): """Send an event to seq""" event_data = { "Timestamp": str(datetime.datetime.now()), "Level": "Error" if is_fail is True else "Information", "MessageTemplate": str(description), "Properties": { "Type": "simpleMonitor", "Name": name, "Monitor": str(app_name), "MonitorType": monitor_type, # "Params": params }, } if params is not None: event_data["Properties"]["Params"] = params request_body = {"Events": [event_data]} try: _ = json.dumps(request_body) # This just checks it is valid... except TypeError: self.logger_logger.error("Could not serialise %s", request_body) return try: response = requests.post( self.endpoint, json=request_body, timeout=self.timeout ) if not response.status_code == 200 and not response.status_code == 201: self.logger_logger.error( "POST to seq failed with status code: %s", response ) except requests.RequestException: self.logger_logger.exception("Failed to log to seq") simplemonitor-1.13.0/simplemonitor/Monitors/000077500000000000000000000000001464501162400212135ustar00rootroot00000000000000simplemonitor-1.13.0/simplemonitor/Monitors/__init__.py000066400000000000000000000030041464501162400233210ustar00rootroot00000000000000""" Monitors for SimpleMonitor """ from .arlo import MonitorArloCamera from .compound import CompoundMonitor from .file import MonitorBackup from .hass import MonitorSensor from .host import ( MonitorApcupsd, MonitorCommand, MonitorDiskSpace, MonitorFileStat, MonitorLoadAvg, MonitorMemory, MonitorPkgAudit, MonitorPortAudit, MonitorSwap, MonitorZap, ) from .network import ( MonitorDNS, MonitorHost, MonitorHTTP, MonitorPing, MonitorTCP, MonitorTLSCert, ) from .ring import MonitorRingDoorbell from .service import ( MonitorEximQueue, MonitorProcess, MonitorRC, MonitorService, MonitorSvc, MonitorSystemdUnit, MonitorUnixService, MonitorWindowsDHCPScope, ) from .unifi import MonitorUnifiFailover, MonitorUnifiFailoverWatchdog __all__ = [ "CompoundMonitor", "MonitorApcupsd", "MonitorArloCamera", "MonitorBackup", "MonitorCommand", "MonitorDNS", "MonitorDiskSpace", "MonitorEximQueue", "MonitorFileStat", "MonitorHTTP", "MonitorHost", "MonitorLoadAvg", "MonitorMemory", "MonitorPing", "MonitorPkgAudit", "MonitorPortAudit", "MonitorProcess", "MonitorRC", "MonitorRingDoorbell", "MonitorSensor", "MonitorService", "MonitorSvc", "MonitorSwap", "MonitorSystemdUnit", "MonitorTCP", "MonitorTLSCert", "MonitorUnifiFailover", "MonitorUnifiFailoverWatchdog", "MonitorUnixService", "MonitorWindowsDHCPScope", "MonitorZap", ] simplemonitor-1.13.0/simplemonitor/Monitors/arlo.py000066400000000000000000000063661464501162400225350ustar00rootroot00000000000000""" Arlo monitoring for SimpleMonitor """ from typing import Optional, cast import pyaarlo from ..Monitors.monitor import Monitor, register @register class MonitorArloCamera(Monitor): """Monitor the battery life on an Arlo camera.""" monitor_type = "arlo_camera" def __init__(self, name: str, config_options: dict) -> None: if "gap" not in config_options: config_options["gap"] = 21600 # 6 hours super().__init__(name, config_options) self.device_name = cast(str, self.get_config_option("device_name")) self.minimum_battery = cast( int, self.get_config_option("minimum_battery", required_type="int", default=25), ) self.arlo_username = cast( str, self.get_config_option("username", required=True) ) self.arlo_password = cast( str, self.get_config_option("password", required=True) ) self.base_station_id = cast( int, self.get_config_option("base_station_id", required_type="int", default=0), ) self.arlo: Optional[pyaarlo.PyArlo] = None self.arlo_base: Optional[pyaarlo.ArloBase] = None self.camera: Optional[pyaarlo.ArloCamera] = None def run_test(self) -> bool: if self.arlo is None: self.monitor_logger.info("logging in to Arlo") try: self.arlo = pyaarlo.PyArlo( username=self.arlo_username, password=self.arlo_password, synchronous_mode=True, ) except Exception: self.monitor_logger.exception("arlo login failed") return self.record_fail("could not log in to Arlo") if self.arlo is None: return self.record_fail("failed to get Arlo object") if self.arlo_base is None: try: base_stations = self.arlo.base_stations if base_stations: self.arlo_base = base_stations[self.base_station_id] except KeyError: self.monitor_logger.exception("arlo base station fetch failed") return self.record_fail("could not fetch base station") if self.arlo_base is None: return self.record_fail("failed to get ArloBaseStation object") if self.camera is None: cameras = self.arlo.cameras if cameras: for camera in cameras: if camera.name == self.device_name: self.camera = camera if self.camera is None: return self.record_fail( "could not find camera named {}".format(self.device_name) ) battery = self.camera.battery_level or 0 # type: int if battery < self.minimum_battery: return self.record_fail( "Battery is at {}% (limit: {}%)".format(battery, self.minimum_battery) ) return self.record_success( "Battery is at {}% (limit: {}%)".format(battery, self.minimum_battery) ) def describe(self) -> str: return "Checking Arlo camera {} has battery level of at least {}%".format( self.device_name, self.minimum_battery ) simplemonitor-1.13.0/simplemonitor/Monitors/compound.py000066400000000000000000000066271464501162400234240ustar00rootroot00000000000000""" Compound checks (logical and of failure of multiple probes) for SimpleMonitor """ from typing import Dict, List, Optional, Tuple, cast from weakref import WeakValueDictionary from .monitor import Monitor, register @register class CompoundMonitor(Monitor): """ Combine (logical-and) multiple failures for emergency escalation Check most recent proble of provided monitors, if all are fail, then report fail. """ monitor_type = "compound" m = None # type: Optional[WeakValueDictionary[str, Monitor]] mt = None # type: Optional[WeakValueDictionary[str, Monitor]] def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.monitors = cast( List[str], self.get_config_option( "monitors", required_type="[str]", required=True, default=[] ), ) self.min_fail = cast( int, self.get_config_option( "min_fail", required_type="int", default=len(self.monitors), minimum=1 ), ) def run_test(self) -> bool: # we depend on the other tests to run, just check them failcount = self.min_fail # this check actually doesn't work, since the sub-monitors run AFTER the compound ones... if self.m is not None: for i in self.monitors: if self.m[i].get_success_count() > 0 and self.m[i].tests_run > 0: failcount -= 1 if failcount < self.min_fail: return self.record_success( "{} monitors failed (min: {})".format(failcount, self.min_fail) ) return self.record_fail( "{} monitors failed (min: {})".format(failcount, self.min_fail) ) def describe(self) -> str: """Explains what we do.""" return "Checking that these monitors all succeeded: {0}".format( ", ".join(self.monitors) ) def get_params(self) -> Tuple: return (self.monitors,) def set_mon_refs(self, mmm: Dict[str, Monitor]) -> None: """stash a ref to the global monitor list so we can examine later""" self.all_monitors = WeakValueDictionary(mmm) def post_config_setup(self) -> None: """make a nice little dict of just the monitors we need""" if self.m is not None: return self.m = WeakValueDictionary() for i in list(self.all_monitors.keys()): if i in self.monitors: self.m[i] = self.all_monitors[i] # make sure we find all of our monitors or die during config for i in self.monitors: if i not in list(self.m.keys()): raise RuntimeError("No such monitor %s in compound monitor" % i) def fail_count(self) -> int: """increments the fail counter by 1 if a sub-monitor failed""" failcount = 0 if self.m is not None: for i in self.monitors: if self.m[i].virtual_fail_count() > 0: failcount += 1 return failcount def get_result(self) -> str: failcount = self.fail_count() monitorcount = self.monitors.__len__() if failcount > 0: return "{0} of {1} services failed. Fail after: {2}".format( failcount, monitorcount, self.min_fail ) else: return "All {0} services OK".format(monitorcount) simplemonitor-1.13.0/simplemonitor/Monitors/file.py000066400000000000000000000031321464501162400225030ustar00rootroot00000000000000""" File-based monitors for SimpleMonitor """ import os import os.path import time from .monitor import Monitor, register @register class MonitorBackup(Monitor): """ Monitor Veritas BackupExec May be out of date """ monitor_type = "backup" filename = os.path.join( "C:\\", "Program Files", "VERITAS", "Backup Exec", "status.txt" ) def run_test(self) -> bool: if not os.path.exists(self.filename): return self.record_fail("Status file missing") try: fh = open(self.filename, "r") except Exception: return self.record_fail("Unable to open status file") try: status = fh.readline() _timestamp = fh.readline() except Exception: return self.record_fail("Unable to read data from status file") fh.close() status = status.strip() timestamp = int(_timestamp.strip()) if status not in ("ok", "running"): return self.record_fail("Unknown status %s" % status) now = int(time.time()) if timestamp > now: return self.record_fail("Timestamp is ahead of now!") gap = now - timestamp if status == "ok": if gap > (3600 * 24): return self.record_fail("OK was reported %ds ago" % gap) else: if gap > (3600 * 7): return self.record_fail("Backup has been running for %ds" % gap) return self.record_success() def describe(self) -> str: return "Checking Backup Exec runs daily, and doesn't run for too long." simplemonitor-1.13.0/simplemonitor/Monitors/hass.py000066400000000000000000000046361464501162400225340ustar00rootroot00000000000000""" Home Automation monitors for SimpleMonitor """ from typing import Tuple, cast import requests from .monitor import Monitor, register @register class MonitorSensor(Monitor): """Monitor the existence of a HASS sensor""" monitor_type = "hass_sensor" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.url = cast(str, self.get_config_option("url", required=True)) self.sensor = cast(str, self.get_config_option("sensor", required=True)) self.token = cast(str, self.get_config_option("token", default=None)) self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) def describe(self) -> str: return "monitor the existence of a sensor" def run_test(self) -> bool: try: # retrieve the status from hass API self.monitor_logger.debug( requests.get( f"{self.url}/api/states/{self.sensor}", timeout=self.timeout ).text ) call = requests.get( f"{self.url}/api/states/{self.sensor}", timeout=self.timeout, headers={ "Authorization": f"Bearer {self.token}", "Content-Type": "application/json", }, ) if not call.ok: raise ValueError(call.text) response = call.json() self.monitor_logger.debug("retrieved JSON: %s", response) except requests.RequestException as error: # a general issue getting to the API # nothing special to report, this monitor should be configured to be # dependent of general hass API availability return self.record_fail(f"cannot get info from hass: {error}") else: # we have a response from the API # now: is the sensor defined at all in hass? If not the answer is basically empty if response.get("context"): if response["state"] == "unavailable": return self.record_fail( "the sensor exists but state is 'unavailable'" ) return self.record_success() return self.record_fail("sensor not found in hass") def get_params(self) -> Tuple: return (self.url, self.sensor) simplemonitor-1.13.0/simplemonitor/Monitors/host.py000066400000000000000000000447771464501162400225650ustar00rootroot00000000000000""" Monitor things on a host for SimpleMonitor """ import os import re import shlex import subprocess # nosec import time from typing import Tuple, cast from markupsafe import escape from ..util import bytes_to_size_string, size_string_to_bytes from .monitor import Monitor, register try: import psutil except ImportError: psutil = None try: import win32api WIN32_AVAILABLE = True except ImportError: WIN32_AVAILABLE = False @register class MonitorDiskSpace(Monitor): """Make sure we have enough disk space.""" monitor_type = "diskspace" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) if self.is_windows(allow_cygwin=False): self.use_statvfs = False if not WIN32_AVAILABLE: raise RuntimeError( "win32api is not available, but is needed for DiskSpace monitor." ) else: self.use_statvfs = True self.partition = self.get_config_option("partition", required=True) self.limit = size_string_to_bytes( self.get_config_option("limit", required=True) ) def run_test(self) -> bool: try: if self.use_statvfs: result = os.statvfs(self.partition) space = result.f_bavail * result.f_frsize percent = float(result.f_bavail) / float(result.f_blocks) * 100 else: win_result = win32api.GetDiskFreeSpaceEx(self.partition) space = win_result[2] percent = float(win_result[2]) / float(win_result[1]) * 100 except Exception as error: return self.record_fail("Couldn't get free disk space: %s" % error) if self.limit and space <= self.limit: return self.record_fail( "%s free (%d%%)" % (bytes_to_size_string(space), percent) ) return self.record_success( "%s free (%d%%)" % (bytes_to_size_string(space), percent) ) def describe(self) -> str: """Explains what we do.""" if self.limit is None: limit = "none" else: limit = bytes_to_size_string(self.limit) return "Checking for at least %s free space on %s" % (limit, self.partition) def get_params(self) -> Tuple: return (self.limit, self.partition) @register class MonitorFileStat(Monitor): """Make sure a file exists, isn't too old and/or isn't too small.""" monitor_type = "filestat" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.maxage = self.get_config_option("maxage", required_type="int", minimum=0) _minsize = self.get_config_option( "minsize", required_type="str", allow_empty=True ) if _minsize: self.minsize = size_string_to_bytes(_minsize) else: self.minsize = None _maxsize = self.get_config_option( "maxsize", required_type="str", allow_empty=True, default="" ) if _maxsize: self.maxsize = size_string_to_bytes(_maxsize) else: self.maxsize = None self.filename = self.get_config_option("filename", required=True) def run_test(self) -> bool: try: statinfo = os.stat(self.filename) except FileNotFoundError: return self.record_fail("File %s does not exist" % self.filename) except Exception as error: return self.record_fail("Unable to check file: %s" % error) if self.minsize: if statinfo.st_size < self.minsize: return self.record_fail( "Size is %d, should be >= %d bytes" % (statinfo.st_size, self.minsize) ) if self.maxsize: if statinfo.st_size > self.maxsize: return self.record_fail( "Size is %d, should be <= %d bytes" % (statinfo.st_size, self.maxsize) ) now = time.time() diff = now - statinfo.st_mtime if self.maxage: if diff > self.maxage: return self.record_fail( "Age is %d, should be < %d seconds" % (diff, self.maxage) ) return self.record_success( "File {} exists (age: {}, size: {})".format( self.filename, int(diff), statinfo.st_size ) ) def describe(self) -> str: """Explains what we do""" desc = "Checking %s exists" % self.filename if self.maxage: desc = desc + " and is not older than %d seconds" % self.maxage if self.minsize: desc = desc + " and is not smaller than %d bytes" % self.minsize return desc def get_params(self) -> Tuple: return (self.filename, self.minsize, self.maxage) @register class MonitorApcupsd(Monitor): """Monitor an APC UPS (with apcupsd) to make sure it's ONLINE. Note: You must have apcupsd successfully setup and working for this monitor to function. """ monitor_type = "apcupsd" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.path = self.get_config_option("path", default="") def run_test(self) -> bool: info = {} if self.path != "": executable = os.path.join(self.path, "apcaccess") else: if self.is_windows(): executable = os.path.join("c:\\", "apcupsd", "bin", "apcaccess.exe") else: executable = "apcaccess" try: _output = subprocess.check_output(executable) # nosec output = _output.decode("utf-8") # type: str except subprocess.CalledProcessError as error: output = error.output except OSError as error: return self.record_fail("Could not run {0}: {1}".format(executable, error)) except Exception as error: return self.record_fail("Error while getting UPS info: {0}".format(error)) for line in output.splitlines(): if line.find(":") > -1: bits = line.split(":") info[bits[0].strip()] = bits[1].strip() if "STATUS" not in info: return self.record_fail("Could not get UPS status") if info["STATUS"] != "ONLINE": if "TIMELEFT" in info: return self.record_fail( "%s: %s left" % (info["STATUS"], info["TIMELEFT"]) ) return self.record_fail(info["STATUS"]) data = "" if "TIMELEFT" in info: data = "%s left" % (info["TIMELEFT"]) if "LOADPCT" in info: if data != "": data += "; " data += "%s%% load" % info["LOADPCT"][0:4] return self.record_success(data) def describe(self) -> str: return "Monitoring UPS to make sure it's ONLINE." def get_params(self) -> Tuple: return (self.path,) @register class MonitorPortAudit(Monitor): """Check a host doesn't have outstanding security issues.""" monitor_type = "portaudit" regexp = re.compile(r"(\d+) problem\(s\) in your installed packages found") def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.path = self.get_config_option("path", default="") def describe(self) -> str: return "Checking for insecure ports." def get_params(self) -> Tuple: return (self.path,) def run_test(self) -> bool: try: # -X 1 tells portaudit to re-download db if one day out of date if self.path == "": self.path = "/usr/local/sbin/portaudit" try: # nosec _output = subprocess.check_output([self.path, "-a", "-X", "1"]) # nosec output = _output.decode("utf-8") except subprocess.CalledProcessError as error: output = error.output except OSError as error: return self.record_fail("Error running %s: %s" % (self.path, error)) except Exception as error: return self.record_fail("Error running portaudit: %s" % error) for line in output.splitlines(): matches = self.regexp.match(line) if matches: count = int(matches.group(1)) # sanity check if count == 0: return self.record_success() if count == 1: return self.record_fail("1 problem") return self.record_fail("%d problems" % count) return self.record_success() except Exception as error: return self.record_fail("Could not run portaudit: %s" % error) @register class MonitorPkgAudit(Monitor): """Check a host doesn't have outstanding security issues.""" monitor_type = "pkgaudit" regexp = re.compile(r"(\d+) problem\(s\) in \w+ installed package(s|\(s\)) found") path = "" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.path = self.get_config_option("path", default="") def describe(self) -> str: return "Checking for insecure packages." def get_params(self) -> Tuple: return (self.path,) def run_test(self) -> bool: try: if self.path == "": self.path = "/usr/local/sbin/pkg" try: _output = subprocess.check_output([self.path, "audit"]) # nosec output = _output.decode("utf-8") except subprocess.CalledProcessError as error: output = error.output.decode("utf-8") except OSError as error: return self.record_fail( "Failed to run %s audit: {0} {1}".format(self.path, error) ) except Exception as error: return self.record_fail("Error running pkg audit: {0}".format(error)) for line in output.splitlines(): matches = self.regexp.match(line) if matches: count = int(matches.group(1)) # sanity check if count == 0: return self.record_success() if count == 1: return self.record_fail("1 problem") return self.record_fail("%d problems" % count) return self.record_success() except Exception as error: return self.record_fail("Could not run pkg: %s" % error) @register class MonitorLoadAvg(Monitor): """Check a host's load average isn't too high.""" monitor_type = "loadavg" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) if self.is_windows(allow_cygwin=False): raise RuntimeError("loadavg monitor does not support Windows") # which time field we're looking at: 0 = 1min, 1 = 5min, 2=15min self.which = self.get_config_option( "which", required_type="int", default=1, minimum=0, maximum=2 ) self.max = self.get_config_option( "max", required_type="float", default=1.00, minimum=0 ) def describe(self) -> str: if self.which == 0: return "Checking 1min loadavg is <= %0.2f" % self.max if self.which == 1: return "Checking 5min loadavg is <= %0.2f" % self.max return "Checking 15min loadavg is <= %0.2f" % self.max def run_test(self) -> bool: try: loadavg = os.getloadavg() except Exception as error: return self.record_fail("Exception getting loadavg: %s" % error) if loadavg[self.which] > self.max: return self.record_fail("%0.2f" % loadavg[self.which]) return self.record_success("%0.2f" % loadavg[self.which]) def get_params(self) -> Tuple: return (self.which, self.max) @register class MonitorMemory(Monitor): """Check for available memory.""" monitor_type = "memory" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) if psutil is None: self.monitor_logger.critical("psutil is not installed.") self.monitor_logger.critical("Try: pip install -r requirements.txt") self.percent_free = cast( int, self.get_config_option("percent_free", required_type="int", required=True), ) def run_test(self) -> bool: if psutil is None: return self.record_fail("psutil is not installed") stats = psutil.virtual_memory() percent = int(stats.available / stats.total * 100) message = "{}% free".format(percent) if percent < self.percent_free: return self.record_fail(message) return self.record_success(message) def get_params(self) -> Tuple: return (self.percent_free,) def describe(self) -> str: return "Checking for at least {}% free memory".format(self.percent_free) @register class MonitorSwap(Monitor): """Check for available swap.""" monitor_type = "swap" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) if psutil is None: self.monitor_logger.critical("psutil is not installed.") self.monitor_logger.critical("Try: pip install -r requirements.txt") self.percent_free = cast( int, self.get_config_option("percent_free", required_type="int", required=True), ) def run_test(self) -> bool: if psutil is None: return self.record_fail("psutil is not installed") stats = psutil.swap_memory() percent = 100 - stats.percent message = f"{percent:.2f}% free" if percent < self.percent_free: return self.record_fail(message) return self.record_success(message) def get_params(self) -> Tuple: return (self.percent_free,) def describe(self) -> str: return f"Checking for at least {self.percent_free}% free swap" @register class MonitorZap(Monitor): """Checks a Zap channel to make sure it is ok""" monitor_type = "zap" r = re.compile("^alarms=(?P).+") def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.span = self.get_config_option( "span", required_type="int", default=1, minimum=1 ) def run_test(self) -> bool: try: _output = subprocess.check_output(["ztscan", str(self.span)]) # nosec output = _output.decode("utf-8") for line in output: matches = self.r.match(line) if matches: status = matches.group("status") if status != "OK": return self.record_fail("status is %s" % status) return self.record_success() return self.record_fail("Error getting status") except Exception as error: return self.record_fail("Error running ztscan: %s" % error) def describe(self) -> str: return "Checking status of zap span %d is OK" % self.span def get_params(self) -> Tuple: return (self.span,) @register class MonitorCommand(Monitor): """Check the output of a command. We can check for a regexp match in the output or give a max value and check the output is lower than this value. """ result_regexp = None monitor_type = "command" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.result_regexp_text = self.get_config_option("result_regexp", default="") self.result_max = self.get_config_option("result_max", required_type="int") if self.result_regexp_text != "": self.result_regexp = re.compile(self.result_regexp_text) if self.result_max is not None: self.monitor_logger.error( "command monitors do not support result_regexp AND" "result_max settings simultaneously" ) self.result_max = None self.show_output = self.get_config_option( "show_output", required_type="bool", default=False ) command = self.get_config_option("command", required=True, allow_empty=False) self.command = shlex.split(command) def run_test(self) -> bool: try: _out = subprocess.check_output(self.command) # nosec if self.result_regexp is not None: out = _out.decode("utf-8") matches = self.result_regexp.search(out) if matches: return self.record_success() return self.record_fail("could not match regexp in out") if self.result_max is not None: outasinteger = int(_out) if outasinteger < self.result_max: return self.record_success( "%s < %s" % (outasinteger, self.result_max) ) return self.record_fail("%s >= %s" % (outasinteger, self.result_max)) msg = "" if self.show_output: msg = escape(_out.decode("utf-8")) return self.record_success(msg) except subprocess.CalledProcessError as exception: if self.show_output: return self.record_fail(exception.output.decode("utf-8")) return self.record_fail(str(exception)) except Exception as exception: return self.record_fail(str(exception)) def describe(self) -> str: """Explains what this instance is checking""" if self.result_regexp is not None: return 'checking command "%s" match a regexp %s' % ( " ".join(self.command), self.result_regexp_text, ) if self.result_max is not None: return 'checking command "%s" returns a value < %d' % ( " ".join(self.command), self.result_max, ) return "checking command '%s' has return status 0" % " ".join(self.command) def get_params(self) -> Tuple: return ( self.command, self.result_regexp_text, self.result_max, self.show_output, ) simplemonitor-1.13.0/simplemonitor/Monitors/monitor.py000066400000000000000000000454341464501162400232660ustar00rootroot00000000000000"""A collection of monitors for the SimpleMonitor application. The Monitor class contains the monitor-independent logic for handling results etc. Subclasses should provide an __init__(), and override at least run_test() to actually perform the test. A successful test should call self.record_success() and a failed one should call self.record_fail(). You should also override the describe() and get_params() functions. """ import copy import datetime import logging import platform import subprocess # nosec import time from typing import Any, List, NoReturn, Optional, Tuple, Union, cast import arrow from ..util import ( MonitorState, UpDownTime, format_datetime, get_config_option, short_hostname, subclass_dict_handler, ) class Monitor: """Simple monitor. This class is abstract.""" monitor_type = "unknown" last_result = "" error_count = 0 _failed_at = None _last_run = 0 success_count = 0 tests_run = 0 last_error_count = 0 last_run_duration = 0 skip_dep = None # type: Optional[str] failures = 0 last_failure = None # type: Optional[arrow.Arrow] uptime_start = None # type: Optional[arrow.Arrow] # this is the time we last received data into this monitor (if we're remote) last_update = None # type: Optional[arrow.Arrow] _first_load = None # type: Optional[arrow.Arrow] unavailable_seconds = 0 # type: int def __init__( self, name: str = "unnamed", config_options: Optional[dict] = None ) -> None: """What's that coming over the hill? Is a monitor?""" if config_options is None: config_options = {} self._config_options = config_options self.name = name self._deps = [] # type: List[str] self.monitor_logger = logging.getLogger("simplemonitor.monitor-" + self.name) self._dependencies = cast( List[str], self.get_config_option("depend", required_type="[str]", default=list()), ) self._urgent = self.get_config_option( "urgent", required_type="bool", default=True ) self._notify = self.get_config_option( "notify", required_type="bool", default=True ) self.group = cast(str, self.get_config_option("group", default="default")) self._tolerance = self.get_config_option( "tolerance", required_type="int", default=0, minimum=0 ) self.remote_alerting = cast( bool, self.get_config_option("remote_alert", required_type="bool", default=False), ) self._recover_command = self.get_config_option("recover_command") self._recovered_command = self.get_config_option("recovered_command") self.recover_info = "" self.recovered_info = "" self.minimum_gap = self.get_config_option( "gap", required_type="int", minimum=0, default=0 ) self.failure_doc = cast( Optional[str], self.get_config_option("failure_doc", default=None) ) self.enabled = cast( bool, self.get_config_option("enabled", required_type="bool", default=True) ) _gps = cast(Optional[str], self.get_config_option("gps")) if _gps: self.gps = [ float(x) for x in _gps.split(",") ] # type: Optional[List[float]] else: self.gps = None self.running_on = short_hostname() self._state = MonitorState.UNKNOWN self._force_run = True # set to ensure we re-run ASAP after a HUP if self._first_load is None: self._first_load = arrow.utcnow() self.ran_this_time = False def get_config_option( self, key: str, *, default: Any = None, required: bool = False, required_type: str = "str", allowed_values: Any = None, allow_empty: bool = True, minimum: Optional[Union[int, float]] = None, maximum: Optional[Union[int, float]] = None, ) -> Any: """Get a config value. Throws the right flavour exception if something is wrong.""" return get_config_option( self._config_options, key, default=default, required=required, required_type=required_type, allowed_values=allowed_values, allow_empty=allow_empty, minimum=minimum, maximum=maximum, ) @property def dependencies(self) -> List[str]: """The Monitors we depend on. If a monitor we depend on fails, we will skip""" return self._dependencies @dependencies.setter def dependencies(self, dependency_list: List[str]) -> None: if not isinstance(dependency_list, list): raise TypeError("dependency_list must be a list") self._dependencies = dependency_list self.reset_dependencies() @property def remaining_dependencies(self) -> List[str]: """The Monitors we still depend on for this loop""" return self._deps def is_remote(self) -> bool: """Check if we're running on this machine, or if we're a remote instance.""" if self.running_on == short_hostname(): return False return True def run_test(self) -> Union[NoReturn, bool]: """Override this method to perform the test.""" raise NotImplementedError def virtual_fail_count(self) -> int: """Return the number of failures we've had past our tolerance.""" vfs = self.error_count - self._tolerance return max(vfs, 0) def test_success(self) -> bool: """Returns false if the monitor has failed. This means that enough tests have failed in a row to exceed the tolerance.""" return not bool(self.virtual_fail_count()) def first_failure(self) -> bool: """Check if this is our first failure (past tolerance).""" if self.error_count == (self._tolerance + 1): return True return False def state(self) -> MonitorState: """Get the monitor state""" return self._state def get_result(self) -> str: """Return the result info from the last test.""" return self.last_result def reset_dependencies(self) -> None: """Reset the monitor's dependency list back to default.""" self._deps = copy.copy(self._dependencies) def dependency_succeeded(self, dependency: str) -> None: """Remove a dependency from the current version of the list.""" try: self._deps.remove(dependency) except ValueError: pass def log_result(self, name: str, logger: Any) -> None: """Save our latest result to the logger. TODO: remove when known safe""" self.monitor_logger.critical("Unexpected call to log_result()") raise NotImplementedError def get_params(self) -> Tuple: """Override this method to return a list of parameters (for logging)""" raise NotImplementedError def set_mon_refs(self, mmm: Any) -> None: """Called with a reference to the list of all monitors. Only used by CompoundMonitor for now.""" pass @property def minimum_gap(self) -> int: """Minimum gap between runs of the monitor.""" return self._minimum_gap @minimum_gap.setter def minimum_gap(self, gap: int) -> None: if isinstance(gap, int): if gap < 0: raise ValueError("gap must be at least 0") self._minimum_gap = int(gap) else: raise TypeError("gap must be an integer") def describe(self) -> str: """Explain what this monitor does. We don't throw NotImplementedError here as it won't show up until something breaks, and we don't want to randomly die then.""" return "(Monitor did not write an auto-biography.)" @staticmethod def is_windows(allow_cygwin: bool = True) -> bool: """Checks if our platform is Windowsy. If allow_cygwin is False, cygwin will be reported as UNIX.""" platforms = ["Microsoft", "Windows"] if allow_cygwin: platforms.append("CYGWIN_NT-6.0") if platform.system() in platforms: return True return False def _add_unavailable_seconds(self) -> None: if self.last_update and self.success_count == 0: unavailable_delta = arrow.utcnow() - self.last_update self.unavailable_seconds += unavailable_delta.seconds def record_fail(self, message: str = "") -> bool: """Update internal state to show that we had a failure.""" self.error_count += 1 self._add_unavailable_seconds() self.last_update = arrow.utcnow() self.last_result = str(message) if self.virtual_fail_count() == 1: self._failed_at = arrow.utcnow() self.last_failure = arrow.utcnow() self.failures += 1 self._state = MonitorState.FAILED self.success_count = 0 self.tests_run += 1 self.uptime_start = None return False def record_success(self, message: str = "") -> bool: """Update internal state to show we had a success.""" if self.error_count > 0: self.last_error_count = self.error_count if self.uptime_start is None: self.uptime_start = arrow.utcnow() self._add_unavailable_seconds() self._state = MonitorState.OK self.error_count = 0 self.last_update = arrow.utcnow() self.success_count += 1 self.tests_run += 1 self.last_result = message return True def record_skip(self, which_dep: Optional[str]) -> bool: """Record that we were skipped. We pretend to have succeeded as we don't want notifications sent.""" if which_dep is not None: # we were skipped because of a dependency self.record_success() self.skip_dep = which_dep self._state = MonitorState.SKIPPED return True def uptime(self) -> Optional[datetime.timedelta]: """Get the monitor uptime""" if self.uptime_start: return arrow.utcnow() - self.uptime_start return None def skipped(self) -> bool: """Check if the monitor was skipped""" if self._state == MonitorState.SKIPPED: return True return False def get_success_count(self) -> int: """Get the number of successful tests.""" if self.tests_run == 0: return 0 return self.success_count def all_better_now(self) -> bool: """Check if we've just recovered.""" if ( self.last_virtual_fail_count() and self.success_count == 1 and self._state != MonitorState.SKIPPED ): return True return False @property def availability(self) -> float: """Calculate the monitor's availability""" if self.tests_run <= 1: return 0.0 if self._first_load is not None: total_seconds = (arrow.utcnow() - self._first_load).total_seconds() availability = 1 - (self.unavailable_seconds / total_seconds) else: availability = 0.0 return availability def first_failure_time(self) -> Optional[arrow.Arrow]: """Get an Arrow object showing when we first failed.""" return self._failed_at @property def notify(self) -> bool: """Should the monitor notify""" return self._notify @notify.setter def notify(self, value: bool) -> None: if isinstance(value, bool): self._notify = value else: raise TypeError("notify must be a bool") @property def urgent(self) -> bool: """Is the monitor urgent""" return self._urgent @urgent.setter def urgent(self, value: Union[bool, int]) -> None: if isinstance(value, bool): self._urgent = value elif isinstance(value, int): if value: self._urgent = True else: self._urgent = False else: raise TypeError("urgent should be a bool, or an int at a push") @property def was_skipped(self) -> bool: """Was the monitor skipped""" return self._state == MonitorState.SKIPPED def should_run(self) -> bool: """Check if we should run our tests. We always run if the minimum gap is 0, or if we're currently failing. Otherwise, we run if the last time we ran was more than minimum_gap seconds ago. """ if not self.enabled: return False now = int(time.time()) if self._force_run: self._force_run = False self._last_run = now return True if self.minimum_gap == 0: self._last_run = now return True if self.error_count > 0: self._last_run = now return True if self._last_run == 0: self._last_run = now return True gap = now - self._last_run if gap >= self.minimum_gap: self._last_run = now return True return False def last_virtual_fail_count(self) -> int: """The last VFC""" value = self.last_error_count - self._tolerance return max(0, value) def attempt_recover(self) -> None: """Attempt to recover, if a command is set""" if self._recover_command is None: self.recover_info = "" return if not self.first_failure(): return try: self.monitor_logger.info("Attempting recovery command") process = subprocess.Popen(self._recover_command.split(" ")) # nosec process.wait() self.recover_info = "Command executed and returned %d" % process.returncode except Exception as error: self.recover_info = "Unable to run command: %s" % error def run_recovered(self) -> None: """Run the post-recover command, if set""" if self._recovered_command is None: self.recovered_info = "" return if self.all_better_now(): self.monitor_logger.info("Attempting recovered command") try: process = subprocess.Popen(self._recovered_command.split(" ")) # nosec process.wait() self.recovered_info = ( "Command executed and returned %d" % process.returncode ) except Exception as error: self.recovered_info = "Unable to run command: %s" % error def post_config_setup(self) -> None: """any post config setup needed""" pass def __getstate__(self) -> dict: """Loggers (the Python kind, not the SimpleMonitor kind) can't be serialized. In order to work around that, we omit them when getting serialized (for being sent over the network). """ serialize_dict = dict(self.__dict__) del serialize_dict["monitor_logger"] return serialize_dict def __setstate__(self, state: dict) -> None: self.__dict__.update(state) self._set_monitor_logger() def _set_monitor_logger(self) -> None: self.monitor_logger = logging.getLogger("simplemonitor.monitor-" + self.name) def to_python_dict(self) -> dict: """Get a dict of the monitor state""" return self.__getstate__() @classmethod def from_python_dict( cls: Any, load_dict: dict ) -> "Monitor": # can't return Monitor type as flake8 gets cross """Set up a monitor from a dict""" monitor = Monitor() monitor.__class__ = cls monitor.__setstate__(load_dict) return monitor def get_downtime(self) -> UpDownTime: """Get monitor downtime""" first_failure_time = self.first_failure_time() if first_failure_time is None: return UpDownTime() downtime = arrow.utcnow() - first_failure_time return UpDownTime.from_timedelta(downtime) def get_wasdowntime(self) -> UpDownTime: """Get the downtime for our last failure""" downtime_started = self._failed_at downtime_ended = self.uptime_start if downtime_started and downtime_ended: return UpDownTime.from_timedelta(downtime_ended - downtime_started) return UpDownTime() def get_uptime(self) -> UpDownTime: """Get monitor uptime""" uptime = self.uptime() if uptime is None: return UpDownTime() return UpDownTime.from_timedelta(uptime) def state_dict(self) -> dict: """Get state information about the Monitor to use for logging or alerting""" ret = { "failed_at": format_datetime(self.first_failure_time()), "name": self.name, "host": self.running_on, "is_remote": self.is_remote(), "downtime": str(self.get_downtime()), "uptime": str(self.get_uptime()), "vfc": self.virtual_fail_count(), "info": self.last_result, "description": self.describe(), "recovery_info": self.recover_info, "recovered_info": self.recovered_info, "first_failure_time": self.first_failure_time(), } return ret def __str__(self) -> str: return self.describe() (register, get_class, all_types) = subclass_dict_handler( "simplemonitor.Monitors.monitor", Monitor, "monitor_type" ) @register class MonitorFail(Monitor): """A monitor which fails a fixed number of times then succeeds once, and repeats. Use for testing alerters etc. The default interval for successes is 5.""" monitor_type = "fail" def __init__(self, name: str, config_options: dict): Monitor.__init__(self, name, config_options) self.interval = self.get_config_option( "interval", required_type="int", minimum=1, default=5 ) def run_test(self) -> bool: """Always fails.""" self.monitor_logger.info( "error_count = %d, interval = %d --> %d", self.error_count, self.interval, self.error_count % self.interval, ) if ( (self.interval == 0) or (self.error_count == 0) or (self.error_count % self.interval != 0) ): return self.record_fail("This monitor always fails.") else: return self.record_success() def describe(self) -> str: return "A monitor which always fails." def get_params(self) -> Tuple: return (self.interval,) @register class MonitorNull(Monitor): """A monitor which always passes.""" monitor_type = "null" def run_test(self) -> bool: return self.record_success() def get_params(self) -> Tuple: return () simplemonitor-1.13.0/simplemonitor/Monitors/network.py000066400000000000000000000444371464501162400232720ustar00rootroot00000000000000""" Network-related monitors for SimpleMonitor """ import datetime import json import re import socket import ssl import subprocess # nosec import sys from typing import List, Optional, Pattern, Tuple, Union, cast import arrow import requests from requests.auth import HTTPBasicAuth from .monitor import Monitor, register try: from icmplib import NameLookupError, SocketPermissionError, ping except ImportError: pass @register class MonitorHTTP(Monitor): """Check an HTTP server is working right.""" url = "" regexp = None regexp_text = "" allowed_codes = [] # type: List[int] allow_redirects = True # type: bool monitor_type = "http" # optional - for HTTPS client authentication only certfile: Optional[str] = None keyfile: Optional[str] = None # optional - headers headers = None def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.url = self.get_config_option("url", required=True) regexp = self.get_config_option("regexp") if regexp is not None: self.regexp = re.compile(regexp) self.regexp_text = regexp self.allowed_codes = self.get_config_option( "allowed_codes", default=[200], required_type="[int]" ) self.allow_redirects = self.get_config_option( "allow_redirects", default=True, required_type="bool", ) # optional - for HTTPS client authentication only # in this case, certfile is required self.certfile = cast(Optional[str], config_options.get("certfile")) self.keyfile = cast(Optional[str], config_options.get("keyfile")) if self.certfile and not self.keyfile: self.keyfile = self.certfile if not self.certfile and self.keyfile: raise ValueError("config option keyfile is set but certfile is not") self.headers = config_options.get("headers") if self.headers: self.headers = json.loads(self.headers) self.verify_hostname = self.get_config_option( "verify_hostname", default=True, required_type="bool" ) self.request_timeout = self.get_config_option( "timeout", default=5, required_type="int" ) self.username = cast(Optional[str], config_options.get("username")) self.password = cast(Optional[str], config_options.get("password")) def run_test(self) -> bool: start_time = arrow.get() end_time = None try: if self.certfile is None and self.username is None: response = requests.get( self.url, timeout=self.request_timeout, verify=self.verify_hostname, headers=self.headers, allow_redirects=self.allow_redirects, ) elif ( self.certfile is None and self.username is not None and self.password is not None ): response = requests.get( self.url, timeout=self.request_timeout, auth=HTTPBasicAuth(self.username, self.password), verify=self.verify_hostname, headers=self.headers, allow_redirects=self.allow_redirects, ) else: assert self.certfile is not None and self.keyfile is not None response = requests.get( self.url, timeout=self.request_timeout, cert=(self.certfile, self.keyfile), verify=self.verify_hostname, headers=self.headers, allow_redirects=self.allow_redirects, ) end_time = arrow.get() load_time = end_time - start_time if response.status_code not in self.allowed_codes: return self.record_fail( "Got status '{0} {1}' instead of {2}".format( response.status_code, response.reason, self.allowed_codes ) ) if self.regexp is None: return self.record_success( "%s in %0.2fs" % ( response.status_code, (load_time.seconds + (load_time.microseconds / 1000000.2)), ) ) matches = self.regexp.search(response.text) if matches: return self.record_success( "%s in %0.2fs" % ( response.status_code, (load_time.seconds + (load_time.microseconds / 1000000.2)), ) ) return self.record_fail( "Got '{0} {1}' but couldn't match /{2}/ in page.".format( response.status_code, response.reason, self.regexp_text ) ) except requests.exceptions.SSLError: return self.record_fail("SSL error during connection") except requests.exceptions.RequestException as exception: return self.record_fail( "Requests exception while opening URL: {0}".format(exception) ) def describe(self) -> str: """Explains what we do.""" codes = [str(x) for x in self.allowed_codes] message = "Checking {} returns HTTP/{} within {}s".format( self.url, "or".join(codes), self.request_timeout ) if self.regexp is not None: message = message + " and that /{}/ matches the page".format( self.regexp_text ) return message def get_params(self) -> Tuple: return (self.url, self.regexp_text, self.allowed_codes) @register class MonitorTCP(Monitor): """TCP port monitor""" host = "" port = 0 monitor_type = "tcp" def __init__(self, name: str, config_options: dict) -> None: """Constructor""" super().__init__(name, config_options) self.host = self.get_config_option("host", required=True) self.port = cast( int, self.get_config_option( "port", required=True, required_type="int", minimum=0 ), ) def run_test(self) -> bool: """Check the port is open on the remote host""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.settimeout(5.0) sock.connect((self.host, self.port)) except OSError as exception: return self.record_fail(str(exception)) sock.close() return self.record_success() def describe(self) -> str: """Explains what this instance is checking""" return "checking for open tcp socket on %s:%d" % (self.host, self.port) def get_params(self) -> Tuple: return (self.host, self.port) @register class MonitorHost(Monitor): """Ping a host to make sure it's up""" host = "" ping_command = "" ping_regexp = "" monitor_type = "host" time_regexp = "" r = "" # type: Union[str, Pattern[str]] r2 = "" # type: Union[str, Pattern[str]] def __init__(self, name: str, config_options: dict) -> None: """ Note: We use -w/-t on Windows/POSIX to limit the amount of time we wait to 5 seconds. This is to stop ping holding things up too much. A machine that can't ping back in <5s is a machine in trouble anyway, so should probably count as a failure. """ super().__init__(name, config_options) ping_ttl = self.get_config_option( "ping_ttl", required_type="int", minimum=0, default=5 ) ping_ms = str(ping_ttl * 1000) ping_ttl = str(ping_ttl) self.count = cast( int, self.get_config_option("count", required_type="int", default=1, minimum=1), ) platform = sys.platform if platform in ["win32", "cygwin"]: self.ping_command = f"ping -n {self.count} -w " + ping_ms + " %s" self.ping_regexp = r"Reply from [0-9a-f:.]+:.+time[=<]\d+ms" self.time_regexp = r"Average = (?P\d+)ms" elif platform.startswith("freebsd") or platform.startswith("darwin"): self.ping_command = f"ping -c{self.count} -t" + ping_ttl + " %s" self.ping_regexp = "bytes from" self.time_regexp = r"min/avg/max/stddev = [\d.]+/(?P[\d.]+)/" elif platform.startswith("linux"): self.ping_command = f"ping -c{self.count} -W" + ping_ttl + " %s" self.ping_regexp = "bytes from" self.time_regexp = r"min/avg/max/stddev = [\d.]+/(?P[\d.]+)/" else: RuntimeError("Don't know how to run ping on this platform, help!") self.ping_regexp = self.get_config_option( "ping_regexp", required=False, default=self.ping_regexp ) self.time_regexp = self.get_config_option( "time_regexp", required=False, default=self.ping_regexp ) self.host = self.get_config_option("host", required=True) def run_test(self) -> bool: success = False pingtime = 0.0 try: cmd = (self.ping_command % self.host).split(" ") output = subprocess.check_output(cmd) # nosec for line in str(output).split("\n"): matches = re.search(self.ping_regexp, line) if matches: success = True else: matches = re.search(self.time_regexp, line) if matches: pingtime = float(matches.group("ms")) except subprocess.CalledProcessError as exception: return self.record_fail(str(exception)) if success: if pingtime > 0: return self.record_success("%sms" % pingtime) return self.record_success() return self.record_fail() def describe(self) -> str: """Explains what this instance is checking""" return "checking host %s is pingable" % self.host def get_params(self) -> Tuple: return (self.host,) @register class MonitorDNS(Monitor): """Monitor DNS server.""" monitor_type = "dns" path = "" command = "dig" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.path = self.get_config_option("record", required=True) self.desired_val = self.get_config_option("desired_val") self.server = self.get_config_option("server") self.params = [self.command] self.port = self.get_config_option("port", required_type="int") if self.server: self.params.append("@%s" % self.server) self.rectype = self.get_config_option("record_type") if self.rectype: self.params.append("-t") self.params.append(config_options["record_type"]) self.params.append(self.path) self.params.append("+short") if self.port: self.params.extend(["-p", str(self.port)]) def run_test(self) -> bool: try: result = subprocess.check_output(self.params).decode("utf-8") # nosec result = result.strip() if result is None or result == "": if self.desired_val != "nxdomain": return self.record_fail("failed to resolve %s" % self.path) return self.record_success("successfully did not resolve") if self.desired_val and set(result.split("\n")) != set( self.desired_val.split("\n") ): return self.record_fail( "resolved DNS record is unexpected: %s != %s" % (self.desired_val, result) ) return self.record_success() except subprocess.CalledProcessError as exception: return self.record_fail( "Command '%s' exited non-zero (%d)" % (" ".join(self.params), exception.returncode) ) def describe(self) -> str: if self.desired_val: end_part = "resolves to %s" % self.desired_val else: end_part = "is resolvable" if self.rectype: mid_part = "%s record %s" % (self.rectype, self.path) else: mid_part = "record %s" % self.path if self.server: very_end_part = " at %s" % self.server else: very_end_part = "" try: port_part = f" on port {self.port}" if self.port else "" except AttributeError: port_part = "" return "Checking that DNS %s %s%s%s" % ( mid_part, end_part, very_end_part, port_part, ) def get_params(self) -> Tuple: return (self.path,) @register class MonitorPing(Monitor): """Ping a host to make sure it's up, using native Python""" monitor_type = "ping" def __init__(self, name: str, config_options: dict) -> None: if config_options is None: config_options = {} super().__init__(name=name, config_options=config_options) self.host = cast(str, self.get_config_option("host", required=True)) self.timeout = cast( int, self.get_config_option("timeout", required_type="int", default=5) ) self.count = cast( int, self.get_config_option("count", required_type="int", default=1, minimum=1), ) def run_test(self) -> bool: if "icmplib" not in sys.modules: return self.record_fail("Missing required icmplib module") try: result = ping(self.host, count=self.count, timeout=self.timeout) if result.is_alive: return self.record_success( "RTT for {}: {:0.3f}ms".format(result.address, result.avg_rtt) ) return self.record_fail(f"Host {result.address} is not alive") except NameLookupError: return self.record_fail(f"Failed to resolve {self.host}") except SocketPermissionError: return self.record_fail( "ping monitor requires root to work; " "try the 'host' monitor if this is not an option for you" ) def get_params(self) -> Tuple: return (self.host, self.timeout, self.count) def describe(self) -> str: return "Checking {} pings within {} seconds ({} attempt(s))".format( self.host, self.timeout, self.count ) @register class MonitorTLSCert(Monitor): """Check the cert on a TLS connection is not due to expire.""" monitor_type = "tls_expiry" def __init__(self, name: str, config_options: Optional[dict]) -> None: if config_options is None: config_options = {} self.minimum_gap = 43200 # 12 hours super().__init__(name=name, config_options=config_options) self.host = cast(str, self.get_config_option("host", required=True)) self.port = cast( int, self.get_config_option("port", required_type="int", default=443) ) self.min_days = cast( int, self.get_config_option("min_days", required_type="int", default=7) ) if self.min_days < 0: raise ValueError("min_days must be 0 or greater") self.sni = cast(Optional[str], self.get_config_option("sni", required=False)) def run_test(self) -> bool: # Note: at time of writing, ssl does not support TLS1.3 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) ssl_context.verify_mode = ssl.CERT_REQUIRED ssl_context.check_hostname = bool(self.sni) ssl_context.load_default_certs() with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: with ssl_context.wrap_socket( sock, server_hostname=self.sni if self.sni else None ) as ssl_sock: try: ssl_sock.connect((self.host, self.port)) except socket.gaierror as error: self.monitor_logger.exception("Failed to connect socket") return self.record_fail("Failed to connect: {}".format(error)) except ssl.CertificateError as error: self.monitor_logger.exception( "SSL certification validation error: %s", error.verify_message ) return self.record_fail( "SSL validation error: {}".format(error.verify_message) ) except ssl.SSLError as error: self.monitor_logger.exception("SSL Error: %s", error.reason) return self.record_fail("SSL Error: {}".format(error.reason)) cert = ssl_sock.getpeercert() if not cert: return self.record_fail("Did not receive certificate") not_after = str(cert["notAfter"]) expiry = datetime.datetime.strptime(not_after, r"%b %d %H:%M:%S %Y %Z") delta = expiry - datetime.datetime.utcnow() days_left = delta.days if days_left < self.min_days: if days_left < 0: return self.record_fail( "Certificate at {}:{} expired {} days ago".format( self.host, self.port, abs(days_left) ) ) return self.record_fail( "Certificate at {}:{} expires in {} days".format( self.host, self.port, days_left ) ) return self.record_success( "Certificate at {}:{} has {} days left to expiry".format( self.host, self.port, days_left ) ) def get_params(self) -> Tuple: return (self.host, self.port, self.min_days) def describe(self) -> str: return "Checking TLS cert at {}:{} {}has at least {} days until expiry".format( self.host, self.port, "(sni: " + self.sni + ") " if self.sni else "", self.min_days, ) simplemonitor-1.13.0/simplemonitor/Monitors/ring.py000066400000000000000000000072611464501162400225320ustar00rootroot00000000000000""" Ring doorbell battery monitoring for SimpleMonitor """ import json from pathlib import Path from typing import Optional, cast import ring_doorbell from oauthlib.oauth2.rfc6749.errors import MissingTokenError from ..Monitors.monitor import Monitor, register from ..version import VERSION RING_USER_AGENT = "SimpleMonitor/{}".format(VERSION) @register class MonitorRingDoorbell(Monitor): """Monitor the battery life on a Ring doorbell.""" monitor_type = "ring_doorbell" def __init__(self, name: str, config_options: dict) -> None: if "gap" not in config_options: config_options["gap"] = 21600 # 6 hours super().__init__(name, config_options) self.device_name = cast(str, self.get_config_option("device_name")) self.device_type = cast( str, self.get_config_option("device_type", default="doorbell") ) self.minimum_battery = cast( int, self.get_config_option("minimum_battery", required_type="int", default=25), ) self.ring_username = cast( str, self.get_config_option("username", required=True) ) self.ring_password = cast( str, self.get_config_option("password", required=True) ) self.cache_file = Path( self.get_config_option("cache_file", default=".ring_token.cache") ) if self.cache_file.is_file(): self.monitor_logger.info("Using token cache file for Ring") self._ring_auth = ring_doorbell.Auth( RING_USER_AGENT, json.loads(self.cache_file.read_text()), self._token_updated, ) else: self._ring_auth = ring_doorbell.Auth( RING_USER_AGENT, token_updater=self._token_updated ) try: self.monitor_logger.info("Logging in to Ring") self._ring_auth.fetch_token(self.ring_username, self.ring_password) except MissingTokenError: self.monitor_logger.critical("MFA logins are not supported") self._ring_auth = None self.ring = None # type: Optional[ring_doorbell.Ring] def _token_updated(self, token: str) -> None: self.cache_file.write_text(json.dumps(token)) def run_test(self) -> bool: if self.ring is None: self.ring = ring_doorbell.Ring(self._ring_auth) self.ring.update_data() devices = self.ring.devices() # doorbots are doorbells owned by this account # authorized_doorbots are ones shared with this account # the device of interest could be in either depending on how the API # user we're configured with relates to it if self.device_type == "doorbell": doorbells = devices["authorized_doorbots"] doorbells.extend(devices["doorbots"]) elif self.device_type == "camera": doorbells = devices.get("authorized_stickup_cams", []) doorbells.extend(devices["stickup_cams"]) else: return self.record_fail(f"Unknown device type {self.device_type}") for doorbell in doorbells: if doorbell.name == self.device_name: battery = int(doorbell.battery_life) if battery < self.minimum_battery: return self.record_fail( f"Battery is at {battery}% (limit: {self.minimum_battery}%)" ) return self.record_success( f"Battery is at {battery}% (limit: {self.minimum_battery}%)" ) return self.record_fail( f"Could not find {self.device_type} named {self.device_name}" ) simplemonitor-1.13.0/simplemonitor/Monitors/service.py000066400000000000000000000436301464501162400232330ustar00rootroot00000000000000""" Service monitoring for SimpleMonitor """ import fnmatch import os import platform import re import subprocess import time from typing import Any, List, Optional, Tuple, cast import psutil from .monitor import Monitor, register try: import pydbus except ImportError: pydbus = None @register class MonitorSvc(Monitor): """Monitor a service handled by daemontools.""" monitor_type = "svc" path = "" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.path = cast(str, self.get_config_option("path", required=True)) self.params = ("svok %s" % self.path).split(" ") def run_test(self) -> bool: if self.path == "": return self.record_fail("Path is not configured") try: result = subprocess.call(self.params) if result is None: result = 0 if result > 0: return self.record_fail("svok returned %d" % int(result)) return self.record_success() except Exception as error: return self.record_fail("Exception while executing svok: %s" % error) def describe(self) -> str: return ( "Checking that the supervise-managed service in %s is running." % self.path ) def get_params(self) -> Tuple: return (self.path,) @register class MonitorService(Monitor): """Monitor a Windows service""" service_name = "" want_state = "RUNNING" host = "." monitor_type = "service" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) if psutil is None: self.monitor_logger.critical("psutil is not installed.") self.monitor_logger.critical("Try: pip install -r requirements.txt") self.service_name = cast(str, self.get_config_option("service", required=True)) self.want_state = cast( str, self.get_config_option( "state", default="RUNNING", allowed_values=[ "RUNNING", "STOPPED", "PAUSED", "START_PENDING", "PAUSE_PENDING", "CONTINUE_PENDING", "STOP_PENDING", ], ), ) self.host = cast(str, self.get_config_option("host", default=".")) def run_test(self) -> bool: """Check the service is in the desired state""" if psutil is None: return self.record_fail("psutil is not installed") try: service = psutil.win_service_get(self.service_name) except psutil.NoSuchProcess: return self.record_fail( "service {} does not exist".format(self.service_name) ) except AttributeError: return self.record_fail("not supported on this platform") except Exception: self.monitor_logger.exception("Failed to get service") return self.record_fail("Unable to get service") _state = service.status() if _state: state = _state.upper() else: state = "NONE" if state != self.want_state: return self.record_fail( "Service state is {} (wanted {})".format(state, self.want_state) ) return self.record_success() def describe(self) -> str: """Explains what this instance is checking""" return "checking for service called %s in state %s" % ( self.service_name, self.want_state, ) def get_params(self) -> Tuple: return (self.host, self.service_name, self.want_state) @register class MonitorRC(Monitor): """Monitor a service handled by an rc.d script. This monitor checks the return code of /usr/local/etc/rc.d/ and reports failure if it's non-zero by default. """ monitor_type = "rc" def __init__(self, name: str, config_options: dict) -> None: """Initialise the class. Change script path to /etc/rc.d/ to monitor base system services. If the script path ends with /, the service name is appended.""" super().__init__(name, config_options) self.service_name = cast(str, self.get_config_option("service", required=True)) self.script_path = cast( str, self.get_config_option("path", default="/usr/local/etc/rc.d/") ) self.want_return_code = cast( str, self.get_config_option("return_code", required_type="int", default=0) ) if self.script_path.endswith("/"): self.script_path = self.script_path + self.service_name if not os.path.isfile(self.script_path): if os.path.isfile(self.script_path + ".sh"): self.script_path = self.script_path + ".sh" else: raise RuntimeError("Script %s(.sh) does not exist" % self.script_path) def run_test(self) -> bool: """Check the service is in the desired state.""" if platform.system() in ["Microsoft", "CYGWIN_NT-6.0"]: return self.record_fail("Cannot run this monitor on a non-UNIX host.") try: returncode = subprocess.check_call([self.script_path, "status"]) if returncode == self.want_return_code: return self.record_success() except subprocess.CalledProcessError as error: if error.returncode == self.want_return_code: return self.record_success() returncode = -1 except Exception as error: return self.record_fail("Exception while executing script: %s" % error) return self.record_fail( "Return code: %d (wanted %d)" % (returncode, int(self.want_return_code)) ) def get_params(self) -> Tuple: return (self.service_name, self.want_return_code) def describe(self) -> str: """Explains what this instance is checking.""" return "Checks service %s is running" % self.script_path @register class MonitorUnixService(Monitor): """Monitor a service handled by a generic "service" command. If "service X status" exits 0 for the service being up, and non-zero otherwise, this is for you. """ monitor_type = "unix_service" def __init__( self, name: str = "unnamed", config_options: Optional[dict] = None ) -> None: super().__init__(name=name, config_options=config_options) self.service_name = cast(str, self.get_config_option("service", required=True)) self.want_state = cast( str, self.get_config_option( "state", allowed_values=["running", "stopped"], default="running" ), ) if self.want_state == "running": self._want_return_code = 0 else: self._want_return_code = 1 def run_test(self) -> bool: try: result = subprocess.run( ["service", self.service_name, "status"], check=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) # nosec returncode = result.returncode except subprocess.SubprocessError as exception: return self.record_fail( "Failed to run 'service {} status: {}".format( self.service_name, exception ) ) if returncode == self._want_return_code: return self.record_success() return self.record_fail( "Got exit code {}, wanted {}".format(returncode, self._want_return_code) ) def get_params(self) -> Tuple: return (self.service_name, self.want_state) def describe(self) -> str: return "Checking service {} is {}".format(self.service_name, self.want_state) @register class MonitorSystemdUnit(Monitor): """Monitor a systemd unit. This monitor checks the state of the unit as given by /org/freedesktop/systemd1/ListUnits and reports failure if it is not one of the expected states. """ monitor_type = "systemd-unit" # A cached shared by all instances of MonitorSystemdUnit, so a single # call is done for all monitors at once. _listunit_cache = [] # type: List[Any] _listunit_cache_expiry = 0 CACHE_LIFETIME = 1 # in seconds def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) if not pydbus: self.monitor_logger.critical( "pydbus package is not available, cannot use MonitorSystemdUnit." ) return self.unit_name = cast(str, self.get_config_option("name", required=True)) self.want_load_states = cast( List[str], self.get_config_option( "load_states", required_type="[str]", default=["loaded"] ), ) self.want_active_states = cast( List[str], self.get_config_option( "active_states", required_type="[str]", default=["active", "reloading"] ), ) self.want_sub_states = cast( List[str], self.get_config_option("sub_states", required_type="[str]", default=[]), ) @classmethod def _list_units(cls) -> List[Any]: if pydbus is None: raise RuntimeError("pydbus module not installed") if cls._listunit_cache_expiry < time.time(): bus = pydbus.SystemBus() systemd = bus.get(".systemd1") cls._listunit_cache_expiry = int(time.time()) + cls.CACHE_LIFETIME cls._listunit_cache = list(systemd.ListUnits()) return cls._listunit_cache def run_test(self) -> bool: """Check the service is in the desired state.""" nb_matches = 0 for unit in self._list_units(): ( name, desc, load_state, active_state, sub_state, follower, unit_path, job_id, job_type, job_path, ) = unit if fnmatch.fnmatch(name, self.unit_name): result = self._check_unit(name, load_state, active_state, sub_state) nb_matches += 1 # TODO: is this right? return result return self.record_fail("No unit %s" % self.unit_name) def _check_unit( self, name: str, load_state: str, active_state: str, sub_state: str ) -> bool: if self.want_load_states and load_state not in self.want_load_states: return self.record_fail( "Unit {0} has load state: {1} (wanted {2})".format( name, load_state, self.want_load_states ) ) if self.want_active_states and active_state not in self.want_active_states: return self.record_fail( "Unit {0} has active state: {1} (wanted {2})".format( name, active_state, self.want_active_states ) ) if self.want_sub_states and sub_state not in self.want_sub_states: return self.record_fail( "Unit {0} has sub state: {1} (wanted {2})".format( name, sub_state, self.want_sub_states ) ) # TODO: added since there's no other apparent path to success return self.record_success("Implicit success for unit {0}".format(name)) def get_params(self) -> Tuple: return ( self.unit_name, self.want_load_states, self.want_active_states, self.want_sub_states, ) def describe(self) -> str: return "Checks unit %s is running" % self.name @register class MonitorProcess(Monitor): """Check for a running process.""" monitor_type = "process" def __init__( self, name: str = "unnamed", config_options: Optional[dict] = None ) -> None: if config_options is None: config_options = {} super().__init__(name=name, config_options=config_options) if psutil is None: self.monitor_logger.critical("psutil is not installed.") self.monitor_logger.critical("Try: pip install -r requirements.txt") self.process_name = cast( str, self.get_config_option("process_name", required=True) ) self.max_count = cast( int, self.get_config_option("max_count", required_type="int", default=-1) ) self.min_count = cast( int, self.get_config_option("min_count", required_type="int", default=1) ) self.username = cast( Optional[str], self.get_config_option("username", required_type="str") ) @staticmethod def _find_process_by_name( name: str, username: Optional[str] = None ) -> List[psutil.Process]: processes = [] for process in psutil.process_iter(["name", "exe", "cmdline", "username"]): if ( name == process.info["name"] or ( process.info["exe"] and os.path.basename(process.info["exe"]) == name ) or (process.info["cmdline"] and process.info["cmdline"][0] == name) ): if username is None or username == process.info["username"]: processes.append(process) return processes def run_test(self) -> bool: if psutil is None: return self.record_fail("psutil is not installed") processes = self._find_process_by_name(self.process_name, self.username) count = len(processes) if count == 1: message = "1 matching process running" else: message = "{} matching processes running".format(count) if count < self.min_count: return self.record_fail(message) if self.max_count > -1 and count > self.max_count: return self.record_fail(message) return self.record_success(message) def get_params(self) -> Tuple: return (self.process_name, self.min_count, self.max_count, self.username) def describe(self) -> str: desc = "Checking for at least {} and at most {} processes matching {}".format( self.min_count, "infinity" if self.max_count == -1 else self.max_count, self.process_name, ) if self.username: desc = desc + " owned by {}".format(self.username) return desc @register class MonitorEximQueue(Monitor): """Make sure an exim queue isn't too big.""" monitor_type = "eximqueue" max_length = 10 r = re.compile(r"(?P\d+) matches out of (?P\d+) messages") path = "/usr/local/sbin" def __init__(self, name: str, config_options: dict) -> None: super().__init__(name, config_options) self.max_length = self.get_config_option( "max_length", required_type="int", minimum=1 ) path = self.get_config_option("path", default="/usr/local/sbin") self.path = os.path.join(path, "exiqgrep") def run_test(self) -> bool: try: _output = subprocess.check_output([self.path, "-xc"]) output = _output.decode("utf-8") for line in output.splitlines(): matches = self.r.match(line) if matches: count = int(matches.group("count")) # total = int(matches.group("total")) if count > self.max_length: if count == 1: return self.record_fail("%d message queued" % count) return self.record_fail("%d messages queued" % count) if count == 1: return self.record_success("%d message queued" % count) return self.record_success("%d messages queued" % count) return self.record_fail("Error getting queue size") except Exception as error: return self.record_fail("Error running exiqgrep: %s" % error) def describe(self) -> str: return "Checking the exim queue length is < %d" % self.max_length def get_params(self) -> Tuple: return (self.max_length,) @register class MonitorWindowsDHCPScope(Monitor): """Checks a Windows DHCP scope to make sure it has sufficient free IPs in the pool.""" # netsh dhcp server \\SERVER scope SCOPE show clients # "No of Clients(version N): N in the Scope monitor_type = "dhcpscope" max_used = 0 scope = "" server = "" r = re.compile(r"No of Clients\(version \d+\): (?P\d+) in the Scope") def __init__(self, name: str, config_options: dict) -> None: if not self.is_windows(True): raise RuntimeError("DHCPScope monitor requires a Windows platform.") super().__init__(name, config_options) self.max_used = self.get_config_option( "max_used", required_type="int", minimum=1 ) self.scope = self.get_config_option("scope", required=True) def run_test(self) -> bool: try: output = str( subprocess.check_output( ["netsh", "dhcp", "server", "scope", self.scope, "show", "clients"] ) ) matches = self.r.search(output) if matches: clients = int(matches.group("clients")) if clients > self.max_used: return self.record_fail("%d clients in scope" % clients) return self.record_success("%d clients in scope" % clients) return self.record_fail("Error getting client count: no match") except Exception as error: return self.record_fail("Error getting client count: {0}".format(error)) def describe(self) -> str: return "Checking the DHCP scope has fewer than %d leases" % self.max_used simplemonitor-1.13.0/simplemonitor/Monitors/unifi.py000066400000000000000000000222671464501162400227100ustar00rootroot00000000000000""" UniFi monitoring for SimpleMonitor """ import re from typing import Dict, NoReturn, Union, cast from paramiko.client import RejectPolicy, SSHClient from paramiko.ssh_exception import SSHException from .monitor import Monitor, register @register class MonitorUnifiFailover(Monitor): """Monitor USG WAN Failover""" monitor_type = "unifi_failover" def __init__(self, name: str, config_options: dict) -> None: if "gap" not in config_options: config_options["gap"] = 300 # 5 mins super().__init__(name, config_options) self._router_address = cast( str, self.get_config_option("router_address", required=True) ) self._username = cast( str, self.get_config_option("router_username", required=True) ) self._password = self.get_config_option("router_password", required=False) self._ssh_key = self.get_config_option("ssh_key", required=False) if self._ssh_key is None and self._password is None: raise ValueError("must specify only one of router_password or ssh_key") self._check_interface = cast( str, self.get_config_option("check_interface", default="eth2") ) def run_test(self) -> Union[NoReturn, bool]: try: with SSHClient() as client: client.set_missing_host_key_policy(RejectPolicy) client.load_system_host_keys() client.connect( hostname=self._router_address, username=self._username, password=self._password, key_filename=self._ssh_key, ) _, stdout, _ = client.exec_command( # nosec "sudo /usr/sbin/ubnt-hal wlbGetStatus" ) data = {} # type: Dict[str, Dict[str, str]] data_block = {} # type: Dict[str, str] interface = "" for _line in stdout.readlines(): line = _line.strip() matches = re.match(r"interface +: (\w+)", line) if matches: if interface != "" and len(data_block) > 0: data[interface] = data_block data_block = {} interface = matches.group(1) data_block = {} continue matches = re.match(r"carrier +: (\w+)", line) if matches: data_block["carrier"] = matches.group(1) continue matches = re.match(r"status +: (\w+)", line) if matches: data_block["status"] = matches.group(1) continue matches = re.match(r"gateway +: (\w+)", line) if matches: data_block["gateway"] = matches.group(1) if interface != "" and len(data_block) > 0: data[interface] = data_block except SSHException as error: self.monitor_logger.exception("Failed to ssh to USG") return self.record_fail("Failed to ssh to USG: {}".format(error)) if self._check_interface not in data: self.monitor_logger.debug("processed data was %s", data) return self.record_fail( "Could not get status for interface {}".format(self._check_interface) ) if data[self._check_interface]["carrier"] != "up": return self.record_fail( "Interface {} carrier is in status {} (wanted 'up')".format( self._check_interface, data[self._check_interface]["carrier"] ) ) if data[self._check_interface]["status"] != "failover": return self.record_fail( "Interface {} is in status {} (wanted 'failover')".format( self._check_interface, data[self._check_interface]["status"] ) ) if data[self._check_interface]["gateway"] == "unknown": return self.record_fail( "Interface {} has gateway {}".format( self._check_interface, data[self._check_interface]["gateway"] ) ) return self.record_success( "Interface {} is {} with status {}".format( self._check_interface, data[self._check_interface]["carrier"], data[self._check_interface]["status"], ) ) def describe(self) -> str: return "Checking USG at {} has interface {} up and not failed over".format( self._router_address, self._check_interface ) @register class MonitorUnifiFailoverWatchdog(Monitor): """Monitor UniFi WAN watchdog""" monitor_type = "unifi_watchdog" def __init__(self, name: str, config_options: dict) -> None: if "gap" not in config_options: config_options["gap"] = 300 # 5 mins super().__init__(name, config_options) self._router_address = cast( str, self.get_config_option("router_address", required=True) ) self._username = cast( str, self.get_config_option("router_username", required=True) ) self._password = self.get_config_option("router_password", required=False) self._ssh_key = self.get_config_option("ssh_key", required=False) if self._ssh_key is None and self._password is None: raise ValueError("must specify only one of router_password or ssh_key") self._primary_interface = cast( str, self.get_config_option("primary_interface", default="pppoe0") ) self._secondary_interface = cast( str, self.get_config_option("secondary_interface", default="eth2") ) def run_test(self) -> Union[NoReturn, bool]: try: with SSHClient() as client: client.set_missing_host_key_policy(RejectPolicy) client.load_system_host_keys() client.connect( hostname=self._router_address, username=self._username, password=self._password, key_filename=self._ssh_key, ) _, stdout, _ = client.exec_command( # nosec "/usr/sbin/ubnt-hal wlbGetWdStatus" ) data = {} # type: Dict[str, Dict[str, str]] data_block = {} # type: Dict[str, str] interface = "" for _line in stdout.readlines(): line = _line.strip() matches = re.match( r"([a-z]+[0-9])", line ) # two spaces and an if name if matches: if interface != "" and len(data_block) > 0: data[interface] = data_block data_block = {} interface = matches.group(1) data_block = {} continue matches = re.match(r"status: (\w+)", line) if matches: data_block["status"] = matches.group(1) continue matches = re.match(r"ping gateway: ([^ ]+) - (\w+)", line) if matches: data_block["gateway"] = matches.group(1) data_block["ping_status"] = matches.group(2) continue if interface != "" and len(data_block) > 0: data[interface] = data_block except SSHException as error: self.monitor_logger.exception("Failed to ssh to USG") return self.record_fail("Failed to ssh to USG: {}".format(error)) for interface in [self._primary_interface, self._secondary_interface]: if interface not in data: self.monitor_logger.debug("processed data was %s", data) return self.record_fail( "Could not get status for interface {}".format(interface) ) if data[interface]["status"] != "Running": return self.record_fail( "Interface {} in status {} (wanted 'Running')".format( interface, data[interface]["status"] ) ) if data[interface]["ping_status"] != "REACHABLE": return self.record_fail( "Interface {} ping ({}) is {} (wanted 'REACHABLE')".format( interface, data[interface]["gateway"], data[interface]["ping_status"], ) ) return self.record_success( "Interfaces {} and {} both running and pinging".format( self._primary_interface, self._secondary_interface ) ) def describe(self) -> str: return "Checking USG at {} has interface {} and {} running and pinging".format( self._router_address, self._primary_interface, self._secondary_interface ) simplemonitor-1.13.0/simplemonitor/__init__.py000066400000000000000000000000541464501162400215110ustar00rootroot00000000000000from .version import VERSION as __version__ simplemonitor-1.13.0/simplemonitor/html/000077500000000000000000000000001464501162400203455ustar00rootroot00000000000000simplemonitor-1.13.0/simplemonitor/html/dist/000077500000000000000000000000001464501162400213105ustar00rootroot00000000000000simplemonitor-1.13.0/simplemonitor/html/dist/2b3e1faf89f94a483539.png000066400000000000000000000026721464501162400246630ustar00rootroot00000000000000PNG  IHDR)IDATxWcYڅik޶L c5dmxfU^=<'2vnX0 E hv*  #Uj*iG|F0Z?i(,'+*Сy3 .'5:8n@)ȵ׫tv8~Ò?Nśl:h z1 EڵvfE&7M!>y3<)e:dGߡb)*J.Zwhѵ>6Ŵ(z4ЎMm=m-{B]#=\>1(QGGlO^%VD]wp+E6lM]MlWr}]cIJV,DFA+HJ};>z>Op K6{GeU Gv3.笥X߀3 խTgFxUt6v.cwӽ5tq{u=5l:xϓ..JQn489$tx)f3O}牳JxArӠcBoST%~8gنaAYY:ڟnP${3EVm6Ϙ !qR:8`.R,|9I7qb/X?xHݫӺV-Õ΍2w.-}a#8?!MF1*|܅һx7-;%3*;y5b.J/%if+Uk8ܸpj$4隂}v#hˢ^:*[G;)뀻iHJ6ҝZ;]_g'4E[ ajyeuq3K'?SkήJaŬxt]ǹ G}GDiJg]`;iQ>"h"Y}yN7_5>0aIENDB`simplemonitor-1.13.0/simplemonitor/html/dist/416d91365b44e4b4f477.png000066400000000000000000000012701464501162400245050ustar00rootroot00000000000000PNG  IHDRCEIDATxT3x$=ݡy}bW'"L㯲>MzO. /8/B9Cq䮣Ɨ 02Ӿޖƭpч9?p >ᝬ>J١v{hL/8n);(pauTaYߤe36\وM_2'ƿ[O4uP'-J<=n0&Ԡu0 v@36Ȁ #~ZNLt±%`xzj LFK*36)IENDB`simplemonitor-1.13.0/simplemonitor/html/dist/6733b337e5bc285232c2.png000066400000000000000000000043041464501162400244720ustar00rootroot00000000000000PNG  IHDR)tEXtSoftwareAdobe ImageReadyqe<fiTXtXML:com.adobe.xmp IDATxڬW[h\U1$f:$Amɇ JcOATC6 hmֈ` mmWG+m"X:3I,ئiW>̝L&s9k?gAs2҅o18^~Vy }h 69>;_HPgG |>_QrWLH.h=\!;:QMQSW# mKUL\I5(W1Re=2]$-+؉A #X?%ozY/IJPbϊ@{bӈ~ ܺ>Y%勡bH^'FUFy '~БOEL5baY!YȫTI|V )MW\:)AWOzm,߮]yFi>ơ,H%I+Ҏ ZZ(Xqs7WޞP#Cֶ&Xu5ܻuܠ^$*nn> O鋷:QTLpY-u@ʱRk]])fk}-k*[y[.\J5Yo"c&6Ϩe1u+޳JJJ2 2~ (WFNX9+r}eA&oŻ"E1KY} wj_5Cm}V? +T6IENDB`simplemonitor-1.13.0/simplemonitor/html/dist/8f2c4d11474275fbc161.png000066400000000000000000000023531464501162400245550ustar00rootroot00000000000000PNG  IHDR44oq`IDATxbO'HJJV:X4vW?mx bM;?厙ˡr%guPp1oI0fws1B.\kxvSQGwRwQǦޒBydHui!y(m\oPmDz64!k0Y *Op͜P+9{șTtV.ݞ6!wڮdY]yg.\fBQ}HԖ mR1JFZ3bX~_gYk >ܾ]>ck(ߍnݶ}y‡!EgS =>fr*K4R墚veYCL˖f ReM,k&#nV.G[ޤ92DSx{z),@)I$@bFΠ4OH%֕ m%XKyэ"-@Xyvt#ϻwTLIaa[ҹ@n - M{&[j) O ݦԡvXYЦvIkCm9!sgyܙ"vlS/rt)\ m!a BH %vۘtIќk_SV};0p>2{`EjG< 3"! գ4bHA=BE.bA"nDZ ҠpqS.7.Cjam<07+zL*^2j$=#stcYYe-IcD3X eg0RX; sD}Y[z߃9eo8P9b(a"-v_IbrKLPХCT LENsK=2}F:!숲w8*fB knl-tIw~a NIwLA&JF9-znϰ愙2Ňesh9arZ>$'LJKPDDZ6$&!0 mH$̔sqCIENDB`simplemonitor-1.13.0/simplemonitor/html/dist/a47ad8a0da980112e2cd.png000066400000000000000000000346511464501162400247700ustar00rootroot00000000000000PNG  IHDR)zTXtRaw profile type exifxڭgr#*f f90Y|Eusӊ"Y9&??˩'J//Is~{yy _/Eg_>''Er<}V_h}x>(̅b}t񳂨>߅yb{xNF@ףs ?0>Xegx!<5v鍓SOy?[\j&?wy|KLB1EtzX7cI?Jvlr{Y{-^-߸5xN?yՋs {8Q+Y2߼[V?*)fj+f_!Tc#A$>YdHnyjhA3JaDXb%7J)S?55jhSιۓ{%Tr)ܨkh[iG00ko13\0L32lϱ(V^eV_c70ˮ@ KXfơNd ˖{ё9kZ84mTkQϡJwkZ!Zr+;/x&YMڷO,^zP@u{,\NT2li6:h*`_YG-ڢAGpC=3NV}>Pd1 ,b 4Cֲ#elިt^g4Xz } /H->ó ^?8Sl:Y!S]AmXVmT.8R< ;m辏݆ 9KfίMfUۏVmt_]5YwnvȉVoRbsqڎ LFzj2# OT]ra{sܨ.7jhPs(l e-hhW8ٵ&zMX6Hs0Xqhv>AOWdv+o UB-'AԀ}P\ӓ7 EG0Gu!I/+ճƦ=BIRhl ׬ tQ'ܵ]K#]`!ӜcĆg@ 2+PRA9GR]Ir^4Q'8p\/@grF[EvZ*?gx]2NY}yCpO8+aB(ܸ48D(spJAfkY+  \@:DdWT Jߢn-"wX#"#GRt%Y}h4q e ?y/J[d]| 㳰ˡ)M䅟y/EU)jNP4ښI/!51QRpQ#@~@-( ~:13D8 Y? ` \@-@Laf<L-P uFM:$|hAU F;'U0gB P24łoA | |!%+{8PQK}A^Oh"uHB;m ]mI< dV $ نb;$ -V2ZJ$􂜀,8Ng!֩Hm"LRӀH٭)R0;u'9۱YB9쑅" n*HCaP@l0e2S`|#R`QF_$r^z&p+^Xp X˪"͊|q|d%/If'YxJ7W HN_V&ڧFe~(eh(s*Q"hzx|Gce|3C<:mwUJ4)]+NJ( xlO~fa@tCh49!i'EA$2XhfH( 9>q7t7I}80Ew=n? m)" }f6(Sb<]B񰓭Gn" Ҥ{[{ɭ@WQHáf -MB^ O1x5'PĞ*ǩ!*tl[$DDB4m/[ b(R)GdQ_8# 0_C`  y`ihA36 CqA1gX#%bq{XۏҚȯ Mk:"E$ PLa1JKЮ5փdxεM< ۩F-Ja=Rl nMa h`VPW#3ȦfiȆ[LXm}Q]0鮞4 rP#@Q0)DS6:f]X 0u Bb?5ݿtZfUi Z+~Fp;Y#%jŖ!Ъ4¿. HX{ԻBusg35VȔ(hdƊcꓨ}udER X>%Ga2hM$h| U)b*H{rijvgRZ#gD\ND.d6MJκ agiMڐ*B#l]9\(J /j{:~ą0;E*Z%p 3f ^G{ Dӊt"NW)A'" i9`y&ѭ`c*#~-&Qn0&l 5Vي\+Twp[bnB:"z{#2N2^f*u,298AOf{R: G@vœZB# /M$-DzUr8e(;lS]YEyH% VPt(xlECD!Qhi6s2 #~_p^d=>)!fC7eܾm|l7K_BƠuSyAI<30\:td!i!s=E3n0a>㤝41L58x2D!sQ.)e쵀D|jPrOsPk-=|#av~|xL3 $Q24Yn{PJM3>\u}85[N>%-8Uh+bB AF.!S7  5z8KA'=Ҁ/$5Ȋػyd 9YE~ ]||,JFraS. >2˲i2pfj f* AN`C1'uԎ&z}M= ^%-OL n;@-Ra}"t#pZҘN2Fl,OPӎe\MEq^p>cpBiQMy^!Rόp-&-j[4fyRK /'>JJxd i`E3$ \E.lA #}5R``.*OMť^T`e5:5">\]gB|3x$Lri&vґp L6 b-Kp'pAc#x,\ʣv)8! 5yi[F G<=k&_wL _m4Иň?=к37T)t!,xm4ndVQ AwX&@e$3LZ~<:nu4暶xӴJ\ Tד@ajދL)~nM\UcO\ OBxB2ky!'+Lsl %sguK̺0rV}i39-*'c #-RDt<܎Mԗֲtwq6Wy|$׭Sv"XlV 55 EnT:UpjS"kIOPB %jl6fkhFmk5 yJ4K'ce&~a`M޸2 !5z#- UCb<}gBUrG)ЄS?Ӷ5^`&"_g=Pޯp8}nJEJ07A$V_>S76+aco 邐hPJ=ŅK (}?[ezTXtRaw profile type iptcx=LA0 fnYol lN*(]ㄣ/[!60AI= ,o iCCPICC profilex}=HPOjE*"vqPĂTJ[UMGbYWWAqssRtK -bxsx>@jUT<&fs+b>tH <랺<˻Q&|", xxz9YIRω #e8xfȤb6f%C%"(FBeg\e{r봆"HB*6P()&Rt:$drmc?YpB1Ŷ?.Шm7N3p:0IzEm⺥{0KH~ZB7[{՝[@ft#E^xwWi;reiTXtXML:com.adobe.xmp P[2gAMA abKGD u7 pHYs  tIME0!8IDATX - j j j \j \j \j \j\\\ G?!!\fjj!!\\\ff#&#& \g#%\\#%\\\ \j ;jCkj;kjkjjk \j \j \j kkk  Z,gIENDB`simplemonitor-1.13.0/simplemonitor/html/dist/fc32c7cbd6407867571b.png000066400000000000000000000014351464501162400246460ustar00rootroot00000000000000PNG  IHDR))`IDATX[0㔴KYn].B KX$j;tj#'iT93KsR1␶6'[QГ l@F^hg;¹AOhWY0hSuKϨa~ C<{]N@A05H:za8Ր}pObxlOs.6Șoӊ5bpO\M)` _Z\{var t={169:(t,e,r)=>{"use strict";r.r(e),r.d(e,{Alert:()=>ve,Button:()=>ye,Carousel:()=>He,Collapse:()=>Ve,Dropdown:()=>fr,Modal:()=>Br,Offcanvas:()=>Vr,Popover:()=>xo,ScrollSpy:()=>Co,Tab:()=>No,Toast:()=>Ho,Tooltip:()=>go});var o={};r.r(o),r.d(o,{afterMain:()=>_,afterRead:()=>w,afterWrite:()=>T,applyStyles:()=>O,arrow:()=>G,auto:()=>s,basePlacements:()=>d,beforeMain:()=>y,beforeRead:()=>x,beforeWrite:()=>E,bottom:()=>i,clippingParents:()=>p,computeStyles:()=>et,createPopper:()=>Nt,createPopperBase:()=>Lt,createPopperLite:()=>jt,detectOverflow:()=>bt,end:()=>m,eventListeners:()=>ot,flip:()=>xt,hide:()=>yt,left:()=>l,main:()=>k,modifierPhases:()=>A,offset:()=>kt,placements:()=>b,popper:()=>g,popperGenerator:()=>zt,popperOffsets:()=>_t,preventOverflow:()=>Et,read:()=>v,reference:()=>u,right:()=>a,start:()=>c,top:()=>n,variationPlacements:()=>h,viewport:()=>f,write:()=>C});var n="top",i="bottom",a="right",l="left",s="auto",d=[n,i,a,l],c="start",m="end",p="clippingParents",f="viewport",g="popper",u="reference",h=d.reduce((function(t,e){return t.concat([e+"-"+c,e+"-"+m])}),[]),b=[].concat(d,[s]).reduce((function(t,e){return t.concat([e,e+"-"+c,e+"-"+m])}),[]),x="beforeRead",v="read",w="afterRead",y="beforeMain",k="main",_="afterMain",E="beforeWrite",C="write",T="afterWrite",A=[x,v,w,y,k,_,E,C,T];function S(t){return t?(t.nodeName||"").toLowerCase():null}function z(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function L(t){return t instanceof z(t).Element||t instanceof Element}function N(t){return t instanceof z(t).HTMLElement||t instanceof HTMLElement}function j(t){return"undefined"!=typeof ShadowRoot&&(t instanceof z(t).ShadowRoot||t instanceof ShadowRoot)}const O={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var r=e.styles[t]||{},o=e.attributes[t]||{},n=e.elements[t];N(n)&&S(n)&&(Object.assign(n.style,r),Object.keys(o).forEach((function(t){var e=o[t];!1===e?n.removeAttribute(t):n.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,r={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,r.popper),e.styles=r,e.elements.arrow&&Object.assign(e.elements.arrow.style,r.arrow),function(){Object.keys(e.elements).forEach((function(t){var o=e.elements[t],n=e.attributes[t]||{},i=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:r[t]).reduce((function(t,e){return t[e]="",t}),{});N(o)&&S(o)&&(Object.assign(o.style,i),Object.keys(n).forEach((function(t){o.removeAttribute(t)})))}))}},requires:["computeStyles"]};function D(t){return t.split("-")[0]}var I=Math.round;function M(t,e){void 0===e&&(e=!1);var r=t.getBoundingClientRect(),o=1,n=1;return N(t)&&e&&(o=r.width/t.offsetWidth||1,n=r.height/t.offsetHeight||1),{width:I(r.width/o),height:I(r.height/n),top:I(r.top/n),right:I(r.right/o),bottom:I(r.bottom/n),left:I(r.left/o),x:I(r.left/o),y:I(r.top/n)}}function P(t){var e=M(t),r=t.offsetWidth,o=t.offsetHeight;return Math.abs(e.width-r)<=1&&(r=e.width),Math.abs(e.height-o)<=1&&(o=e.height),{x:t.offsetLeft,y:t.offsetTop,width:r,height:o}}function H(t,e){var r=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(r&&j(r)){var o=e;do{if(o&&t.isSameNode(o))return!0;o=o.parentNode||o.host}while(o)}return!1}function R(t){return z(t).getComputedStyle(t)}function B(t){return["table","td","th"].indexOf(S(t))>=0}function q(t){return((L(t)?t.ownerDocument:t.document)||window.document).documentElement}function $(t){return"html"===S(t)?t:t.assignedSlot||t.parentNode||(j(t)?t.host:null)||q(t)}function W(t){return N(t)&&"fixed"!==R(t).position?t.offsetParent:null}function F(t){for(var e=z(t),r=W(t);r&&B(r)&&"static"===R(r).position;)r=W(r);return r&&("html"===S(r)||"body"===S(r)&&"static"===R(r).position)?e:r||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&N(t)&&"fixed"===R(t).position)return null;for(var r=$(t);N(r)&&["html","body"].indexOf(S(r))<0;){var o=R(r);if("none"!==o.transform||"none"!==o.perspective||"paint"===o.contain||-1!==["transform","perspective"].indexOf(o.willChange)||e&&"filter"===o.willChange||e&&o.filter&&"none"!==o.filter)return r;r=r.parentNode}return null}(t)||e}function U(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var X=Math.max,V=Math.min,Y=Math.round;function K(t,e,r){return X(t,V(e,r))}function Q(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Z(t,e){return e.reduce((function(e,r){return e[r]=t,e}),{})}const G={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,r=t.state,o=t.name,s=t.options,c=r.elements.arrow,m=r.modifiersData.popperOffsets,p=D(r.placement),f=U(p),g=[l,a].indexOf(p)>=0?"height":"width";if(c&&m){var u=function(t,e){return Q("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Z(t,d))}(s.padding,r),h=P(c),b="y"===f?n:l,x="y"===f?i:a,v=r.rects.reference[g]+r.rects.reference[f]-m[f]-r.rects.popper[g],w=m[f]-r.rects.reference[f],y=F(c),k=y?"y"===f?y.clientHeight||0:y.clientWidth||0:0,_=v/2-w/2,E=u[b],C=k-h[g]-u[x],T=k/2-h[g]/2+_,A=K(E,T,C),S=f;r.modifiersData[o]=((e={})[S]=A,e.centerOffset=A-T,e)}},effect:function(t){var e=t.state,r=t.options.element,o=void 0===r?"[data-popper-arrow]":r;null!=o&&("string"!=typeof o||(o=e.elements.popper.querySelector(o)))&&H(e.elements.popper,o)&&(e.elements.arrow=o)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};var J={top:"auto",right:"auto",bottom:"auto",left:"auto"};function tt(t){var e,r=t.popper,o=t.popperRect,s=t.placement,d=t.offsets,c=t.position,m=t.gpuAcceleration,p=t.adaptive,f=t.roundOffsets,g=!0===f?function(t){var e=t.x,r=t.y,o=window.devicePixelRatio||1;return{x:Y(Y(e*o)/o)||0,y:Y(Y(r*o)/o)||0}}(d):"function"==typeof f?f(d):d,u=g.x,h=void 0===u?0:u,b=g.y,x=void 0===b?0:b,v=d.hasOwnProperty("x"),w=d.hasOwnProperty("y"),y=l,k=n,_=window;if(p){var E=F(r),C="clientHeight",T="clientWidth";E===z(r)&&"static"!==R(E=q(r)).position&&(C="scrollHeight",T="scrollWidth"),E=E,s===n&&(k=i,x-=E[C]-o.height,x*=m?1:-1),s===l&&(y=a,h-=E[T]-o.width,h*=m?1:-1)}var A,S=Object.assign({position:c},p&&J);return m?Object.assign({},S,((A={})[k]=w?"0":"",A[y]=v?"0":"",A.transform=(_.devicePixelRatio||1)<2?"translate("+h+"px, "+x+"px)":"translate3d("+h+"px, "+x+"px, 0)",A)):Object.assign({},S,((e={})[k]=w?x+"px":"",e[y]=v?h+"px":"",e.transform="",e))}const et={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,r=t.options,o=r.gpuAcceleration,n=void 0===o||o,i=r.adaptive,a=void 0===i||i,l=r.roundOffsets,s=void 0===l||l,d={placement:D(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:n};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,tt(Object.assign({},d,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:a,roundOffsets:s})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,tt(Object.assign({},d,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:s})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var rt={passive:!0};const ot={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,r=t.instance,o=t.options,n=o.scroll,i=void 0===n||n,a=o.resize,l=void 0===a||a,s=z(e.elements.popper),d=[].concat(e.scrollParents.reference,e.scrollParents.popper);return i&&d.forEach((function(t){t.addEventListener("scroll",r.update,rt)})),l&&s.addEventListener("resize",r.update,rt),function(){i&&d.forEach((function(t){t.removeEventListener("scroll",r.update,rt)})),l&&s.removeEventListener("resize",r.update,rt)}},data:{}};var nt={left:"right",right:"left",bottom:"top",top:"bottom"};function it(t){return t.replace(/left|right|bottom|top/g,(function(t){return nt[t]}))}var at={start:"end",end:"start"};function lt(t){return t.replace(/start|end/g,(function(t){return at[t]}))}function st(t){var e=z(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function dt(t){return M(q(t)).left+st(t).scrollLeft}function ct(t){var e=R(t),r=e.overflow,o=e.overflowX,n=e.overflowY;return/auto|scroll|overlay|hidden/.test(r+n+o)}function mt(t){return["html","body","#document"].indexOf(S(t))>=0?t.ownerDocument.body:N(t)&&ct(t)?t:mt($(t))}function pt(t,e){var r;void 0===e&&(e=[]);var o=mt(t),n=o===(null==(r=t.ownerDocument)?void 0:r.body),i=z(o),a=n?[i].concat(i.visualViewport||[],ct(o)?o:[]):o,l=e.concat(a);return n?l:l.concat(pt($(a)))}function ft(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function gt(t,e){return e===f?ft(function(t){var e=z(t),r=q(t),o=e.visualViewport,n=r.clientWidth,i=r.clientHeight,a=0,l=0;return o&&(n=o.width,i=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(a=o.offsetLeft,l=o.offsetTop)),{width:n,height:i,x:a+dt(t),y:l}}(t)):N(e)?function(t){var e=M(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):ft(function(t){var e,r=q(t),o=st(t),n=null==(e=t.ownerDocument)?void 0:e.body,i=X(r.scrollWidth,r.clientWidth,n?n.scrollWidth:0,n?n.clientWidth:0),a=X(r.scrollHeight,r.clientHeight,n?n.scrollHeight:0,n?n.clientHeight:0),l=-o.scrollLeft+dt(t),s=-o.scrollTop;return"rtl"===R(n||r).direction&&(l+=X(r.clientWidth,n?n.clientWidth:0)-i),{width:i,height:a,x:l,y:s}}(q(t)))}function ut(t){return t.split("-")[1]}function ht(t){var e,r=t.reference,o=t.element,s=t.placement,d=s?D(s):null,p=s?ut(s):null,f=r.x+r.width/2-o.width/2,g=r.y+r.height/2-o.height/2;switch(d){case n:e={x:f,y:r.y-o.height};break;case i:e={x:f,y:r.y+r.height};break;case a:e={x:r.x+r.width,y:g};break;case l:e={x:r.x-o.width,y:g};break;default:e={x:r.x,y:r.y}}var u=d?U(d):null;if(null!=u){var h="y"===u?"height":"width";switch(p){case c:e[u]=e[u]-(r[h]/2-o[h]/2);break;case m:e[u]=e[u]+(r[h]/2-o[h]/2)}}return e}function bt(t,e){void 0===e&&(e={});var r=e,o=r.placement,l=void 0===o?t.placement:o,s=r.boundary,c=void 0===s?p:s,m=r.rootBoundary,h=void 0===m?f:m,b=r.elementContext,x=void 0===b?g:b,v=r.altBoundary,w=void 0!==v&&v,y=r.padding,k=void 0===y?0:y,_=Q("number"!=typeof k?k:Z(k,d)),E=x===g?u:g,C=t.elements.reference,T=t.rects.popper,A=t.elements[w?E:x],z=function(t,e,r){var o="clippingParents"===e?function(t){var e=pt($(t)),r=["absolute","fixed"].indexOf(R(t).position)>=0&&N(t)?F(t):t;return L(r)?e.filter((function(t){return L(t)&&H(t,r)&&"body"!==S(t)})):[]}(t):[].concat(e),n=[].concat(o,[r]),i=n[0],a=n.reduce((function(e,r){var o=gt(t,r);return e.top=X(o.top,e.top),e.right=V(o.right,e.right),e.bottom=V(o.bottom,e.bottom),e.left=X(o.left,e.left),e}),gt(t,i));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(L(A)?A:A.contextElement||q(t.elements.popper),c,h),j=M(C),O=ht({reference:j,element:T,strategy:"absolute",placement:l}),D=ft(Object.assign({},T,O)),I=x===g?D:j,P={top:z.top-I.top+_.top,bottom:I.bottom-z.bottom+_.bottom,left:z.left-I.left+_.left,right:I.right-z.right+_.right},B=t.modifiersData.offset;if(x===g&&B){var W=B[l];Object.keys(P).forEach((function(t){var e=[a,i].indexOf(t)>=0?1:-1,r=[n,i].indexOf(t)>=0?"y":"x";P[t]+=W[r]*e}))}return P}const xt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,r=t.options,o=t.name;if(!e.modifiersData[o]._skip){for(var m=r.mainAxis,p=void 0===m||m,f=r.altAxis,g=void 0===f||f,u=r.fallbackPlacements,x=r.padding,v=r.boundary,w=r.rootBoundary,y=r.altBoundary,k=r.flipVariations,_=void 0===k||k,E=r.allowedAutoPlacements,C=e.options.placement,T=D(C),A=u||(T!==C&&_?function(t){if(D(t)===s)return[];var e=it(t);return[lt(t),e,lt(e)]}(C):[it(C)]),S=[C].concat(A).reduce((function(t,r){return t.concat(D(r)===s?function(t,e){void 0===e&&(e={});var r=e,o=r.placement,n=r.boundary,i=r.rootBoundary,a=r.padding,l=r.flipVariations,s=r.allowedAutoPlacements,c=void 0===s?b:s,m=ut(o),p=m?l?h:h.filter((function(t){return ut(t)===m})):d,f=p.filter((function(t){return c.indexOf(t)>=0}));0===f.length&&(f=p);var g=f.reduce((function(e,r){return e[r]=bt(t,{placement:r,boundary:n,rootBoundary:i,padding:a})[D(r)],e}),{});return Object.keys(g).sort((function(t,e){return g[t]-g[e]}))}(e,{placement:r,boundary:v,rootBoundary:w,padding:x,flipVariations:_,allowedAutoPlacements:E}):r)}),[]),z=e.rects.reference,L=e.rects.popper,N=new Map,j=!0,O=S[0],I=0;I=0,B=R?"width":"height",q=bt(e,{placement:M,boundary:v,rootBoundary:w,altBoundary:y,padding:x}),$=R?H?a:l:H?i:n;z[B]>L[B]&&($=it($));var W=it($),F=[];if(p&&F.push(q[P]<=0),g&&F.push(q[$]<=0,q[W]<=0),F.every((function(t){return t}))){O=M,j=!1;break}N.set(M,F)}if(j)for(var U=function(t){var e=S.find((function(e){var r=N.get(e);if(r)return r.slice(0,t).every((function(t){return t}))}));if(e)return O=e,"break"},X=_?3:1;X>0&&"break"!==U(X);X--);e.placement!==O&&(e.modifiersData[o]._skip=!0,e.placement=O,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function vt(t,e,r){return void 0===r&&(r={x:0,y:0}),{top:t.top-e.height-r.y,right:t.right-e.width+r.x,bottom:t.bottom-e.height+r.y,left:t.left-e.width-r.x}}function wt(t){return[n,a,i,l].some((function(e){return t[e]>=0}))}const yt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,r=t.name,o=e.rects.reference,n=e.rects.popper,i=e.modifiersData.preventOverflow,a=bt(e,{elementContext:"reference"}),l=bt(e,{altBoundary:!0}),s=vt(a,o),d=vt(l,n,i),c=wt(s),m=wt(d);e.modifiersData[r]={referenceClippingOffsets:s,popperEscapeOffsets:d,isReferenceHidden:c,hasPopperEscaped:m},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":c,"data-popper-escaped":m})}},kt={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,r=t.options,o=t.name,i=r.offset,s=void 0===i?[0,0]:i,d=b.reduce((function(t,r){return t[r]=function(t,e,r){var o=D(t),i=[l,n].indexOf(o)>=0?-1:1,s="function"==typeof r?r(Object.assign({},e,{placement:t})):r,d=s[0],c=s[1];return d=d||0,c=(c||0)*i,[l,a].indexOf(o)>=0?{x:c,y:d}:{x:d,y:c}}(r,e.rects,s),t}),{}),c=d[e.placement],m=c.x,p=c.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=m,e.modifiersData.popperOffsets.y+=p),e.modifiersData[o]=d}},_t={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,r=t.name;e.modifiersData[r]=ht({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},Et={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,r=t.options,o=t.name,s=r.mainAxis,d=void 0===s||s,m=r.altAxis,p=void 0!==m&&m,f=r.boundary,g=r.rootBoundary,u=r.altBoundary,h=r.padding,b=r.tether,x=void 0===b||b,v=r.tetherOffset,w=void 0===v?0:v,y=bt(e,{boundary:f,rootBoundary:g,padding:h,altBoundary:u}),k=D(e.placement),_=ut(e.placement),E=!_,C=U(k),T="x"===C?"y":"x",A=e.modifiersData.popperOffsets,S=e.rects.reference,z=e.rects.popper,L="function"==typeof w?w(Object.assign({},e.rects,{placement:e.placement})):w,N={x:0,y:0};if(A){if(d||p){var j="y"===C?n:l,O="y"===C?i:a,I="y"===C?"height":"width",M=A[C],H=A[C]+y[j],R=A[C]-y[O],B=x?-z[I]/2:0,q=_===c?S[I]:z[I],$=_===c?-z[I]:-S[I],W=e.elements.arrow,Y=x&&W?P(W):{width:0,height:0},Q=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},Z=Q[j],G=Q[O],J=K(0,S[I],Y[I]),tt=E?S[I]/2-B-J-Z-L:q-J-Z-L,et=E?-S[I]/2+B+J+G+L:$+J+G+L,rt=e.elements.arrow&&F(e.elements.arrow),ot=rt?"y"===C?rt.clientTop||0:rt.clientLeft||0:0,nt=e.modifiersData.offset?e.modifiersData.offset[e.placement][C]:0,it=A[C]+tt-nt-ot,at=A[C]+et-nt;if(d){var lt=K(x?V(H,it):H,M,x?X(R,at):R);A[C]=lt,N[C]=lt-M}if(p){var st="x"===C?n:l,dt="x"===C?i:a,ct=A[T],mt=ct+y[st],pt=ct-y[dt],ft=K(x?V(mt,it):mt,ct,x?X(pt,at):pt);A[T]=ft,N[T]=ft-ct}}e.modifiersData[o]=N}},requiresIfExists:["offset"]};function Ct(t,e,r){void 0===r&&(r=!1);var o,n,i=N(e),a=N(e)&&function(t){var e=t.getBoundingClientRect(),r=e.width/t.offsetWidth||1,o=e.height/t.offsetHeight||1;return 1!==r||1!==o}(e),l=q(e),s=M(t,a),d={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(i||!i&&!r)&&(("body"!==S(e)||ct(l))&&(d=(o=e)!==z(o)&&N(o)?{scrollLeft:(n=o).scrollLeft,scrollTop:n.scrollTop}:st(o)),N(e)?((c=M(e,!0)).x+=e.clientLeft,c.y+=e.clientTop):l&&(c.x=dt(l))),{x:s.left+d.scrollLeft-c.x,y:s.top+d.scrollTop-c.y,width:s.width,height:s.height}}function Tt(t){var e=new Map,r=new Set,o=[];function n(t){r.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!r.has(t)){var o=e.get(t);o&&n(o)}})),o.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){r.has(t.name)||n(t)})),o}var At={placement:"bottom",modifiers:[],strategy:"absolute"};function St(){for(var t=arguments.length,e=new Array(t),r=0;r{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let r=t.getAttribute("href");if(!r||!r.includes("#")&&!r.startsWith("."))return null;r.includes("#")&&!r.startsWith("#")&&(r=`#${r.split("#")[1]}`),e=r&&"#"!==r?r.trim():null}return e},It=t=>{const e=Dt(t);return e&&document.querySelector(e)?e:null},Mt=t=>{const e=Dt(t);return e?document.querySelector(e):null},Pt=t=>{t.dispatchEvent(new Event(Ot))},Ht=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),Rt=t=>Ht(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,Bt=(t,e,r)=>{Object.keys(r).forEach((o=>{const n=r[o],i=e[o],a=i&&Ht(i)?"element":null==(l=i)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(n).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${o}" provided type "${a}" but expected type "${n}".`)}))},qt=t=>!(!Ht(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),$t=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),Wt=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?Wt(t.parentNode):null},Ft=()=>{},Ut=t=>{t.offsetHeight},Xt=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},Vt=[],Yt=()=>"rtl"===document.documentElement.dir,Kt=t=>{var e;e=()=>{const e=Xt();if(e){const r=t.NAME,o=e.fn[r];e.fn[r]=t.jQueryInterface,e.fn[r].Constructor=t,e.fn[r].noConflict=()=>(e.fn[r]=o,t.jQueryInterface)}},"loading"===document.readyState?(Vt.length||document.addEventListener("DOMContentLoaded",(()=>{Vt.forEach((t=>t()))})),Vt.push(e)):e()},Qt=t=>{"function"==typeof t&&t()},Zt=(t,e,r=!0)=>{if(!r)return void Qt(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:r}=window.getComputedStyle(t);const o=Number.parseFloat(e),n=Number.parseFloat(r);return o||n?(e=e.split(",")[0],r=r.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(r))):0})(e)+5;let n=!1;const i=({target:r})=>{r===e&&(n=!0,e.removeEventListener(Ot,i),Qt(t))};e.addEventListener(Ot,i),setTimeout((()=>{n||Pt(e)}),o)},Gt=(t,e,r,o)=>{let n=t.indexOf(e);if(-1===n)return t[!r&&o?t.length-1:0];const i=t.length;return n+=r?1:-1,o&&(n=(n+i)%i),t[Math.max(0,Math.min(n,i-1))]},Jt=/[^.]*(?=\..*)\.|.*/,te=/\..*/,ee=/::\d+$/,re={};let oe=1;const ne={mouseenter:"mouseover",mouseleave:"mouseout"},ie=/^(mouseenter|mouseleave)/i,ae=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function le(t,e){return e&&`${e}::${oe++}`||t.uidEvent||oe++}function se(t){const e=le(t);return t.uidEvent=e,re[e]=re[e]||{},re[e]}function de(t,e,r=null){const o=Object.keys(t);for(let n=0,i=o.length;nfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};o?o=t(o):r=t(r)}const[i,a,l]=ce(e,r,o),s=se(t),d=s[l]||(s[l]={}),c=de(d,a,i?r:null);if(c)return void(c.oneOff=c.oneOff&&n);const m=le(a,e.replace(Jt,"")),p=i?function(t,e,r){return function o(n){const i=t.querySelectorAll(e);for(let{target:a}=n;a&&a!==this;a=a.parentNode)for(let l=i.length;l--;)if(i[l]===a)return n.delegateTarget=a,o.oneOff&&ge.off(t,n.type,e,r),r.apply(a,[n]);return null}}(t,r,o):function(t,e){return function r(o){return o.delegateTarget=t,r.oneOff&&ge.off(t,o.type,e),e.apply(t,[o])}}(t,r);p.delegationSelector=i?r:null,p.originalHandler=a,p.oneOff=n,p.uidEvent=m,d[m]=p,t.addEventListener(l,p,i)}function pe(t,e,r,o,n){const i=de(e[r],o,n);i&&(t.removeEventListener(r,i,Boolean(n)),delete e[r][i.uidEvent])}function fe(t){return t=t.replace(te,""),ne[t]||t}const ge={on(t,e,r,o){me(t,e,r,o,!1)},one(t,e,r,o){me(t,e,r,o,!0)},off(t,e,r,o){if("string"!=typeof e||!t)return;const[n,i,a]=ce(e,r,o),l=a!==e,s=se(t),d=e.startsWith(".");if(void 0!==i){if(!s||!s[a])return;return void pe(t,s,a,i,n?r:null)}d&&Object.keys(s).forEach((r=>{!function(t,e,r,o){const n=e[r]||{};Object.keys(n).forEach((i=>{if(i.includes(o)){const o=n[i];pe(t,e,r,o.originalHandler,o.delegationSelector)}}))}(t,s,r,e.slice(1))}));const c=s[a]||{};Object.keys(c).forEach((r=>{const o=r.replace(ee,"");if(!l||e.includes(o)){const e=c[r];pe(t,s,a,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,r){if("string"!=typeof e||!t)return null;const o=Xt(),n=fe(e),i=e!==n,a=ae.has(n);let l,s=!0,d=!0,c=!1,m=null;return i&&o&&(l=o.Event(e,r),o(t).trigger(l),s=!l.isPropagationStopped(),d=!l.isImmediatePropagationStopped(),c=l.isDefaultPrevented()),a?(m=document.createEvent("HTMLEvents"),m.initEvent(n,s,!0)):m=new CustomEvent(e,{bubbles:s,cancelable:!0}),void 0!==r&&Object.keys(r).forEach((t=>{Object.defineProperty(m,t,{get:()=>r[t]})})),c&&m.preventDefault(),d&&t.dispatchEvent(m),m.defaultPrevented&&void 0!==l&&l.preventDefault(),m}},ue=new Map;var he={set(t,e,r){ue.has(t)||ue.set(t,new Map);const o=ue.get(t);o.has(e)||0===o.size?o.set(e,r):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(o.keys())[0]}.`)},get:(t,e)=>ue.has(t)&&ue.get(t).get(e)||null,remove(t,e){if(!ue.has(t))return;const r=ue.get(t);r.delete(e),0===r.size&&ue.delete(t)}};class be{constructor(t){(t=Rt(t))&&(this._element=t,he.set(this._element,this.constructor.DATA_KEY,this))}dispose(){he.remove(this._element,this.constructor.DATA_KEY),ge.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,r=!0){Zt(t,e,r)}static getInstance(t){return he.get(Rt(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.0"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const xe=(t,e="hide")=>{const r=`click.dismiss${t.EVENT_KEY}`,o=t.NAME;ge.on(document,r,`[data-bs-dismiss="${o}"]`,(function(r){if(["A","AREA"].includes(this.tagName)&&r.preventDefault(),$t(this))return;const n=Mt(this)||this.closest(`.${o}`);t.getOrCreateInstance(n)[e]()}))};class ve extends be{static get NAME(){return"alert"}close(){if(ge.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),ge.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=ve.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}xe(ve,"close"),Kt(ve);const we='[data-bs-toggle="button"]';class ye extends be{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=ye.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function ke(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function _e(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}ge.on(document,"click.bs.button.data-api",we,(t=>{t.preventDefault();const e=t.target.closest(we);ye.getOrCreateInstance(e).toggle()})),Kt(ye);const Ee={setDataAttribute(t,e,r){t.setAttribute(`data-bs-${_e(e)}`,r)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${_e(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((r=>{let o=r.replace(/^bs/,"");o=o.charAt(0).toLowerCase()+o.slice(1,o.length),e[o]=ke(t.dataset[r])})),e},getDataAttribute:(t,e)=>ke(t.getAttribute(`data-bs-${_e(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},Ce={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const r=[];let o=t.parentNode;for(;o&&o.nodeType===Node.ELEMENT_NODE&&3!==o.nodeType;)o.matches(e)&&r.push(o),o=o.parentNode;return r},prev(t,e){let r=t.previousElementSibling;for(;r;){if(r.matches(e))return[r];r=r.previousElementSibling}return[]},next(t,e){let r=t.nextElementSibling;for(;r;){if(r.matches(e))return[r];r=r.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!$t(t)&&qt(t)))}},Te="carousel",Ae={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Se={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},ze="next",Le="prev",Ne="left",je="right",Oe={ArrowLeft:je,ArrowRight:Ne},De="slid.bs.carousel",Ie="active",Me=".active.carousel-item",Pe="touch";class He extends be{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=Ce.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return Ae}static get NAME(){return Te}next(){this._slide(ze)}nextWhenVisible(){!document.hidden&&qt(this._element)&&this.next()}prev(){this._slide(Le)}pause(t){t||(this._isPaused=!0),Ce.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(Pt(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=Ce.findOne(Me,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void ge.one(this._element,De,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const r=t>e?ze:Le;this._slide(r,this._items[t])}_getConfig(t){return t={...Ae,...Ee.getDataAttributes(this._element),..."object"==typeof t?t:{}},Bt(Te,t,Se),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?je:Ne)}_addEventListeners(){this._config.keyboard&&ge.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(ge.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),ge.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>{!this._pointerEvent||"pen"!==t.pointerType&&t.pointerType!==Pe?this._pointerEvent||(this.touchStartX=t.touches[0].clientX):this.touchStartX=t.clientX},e=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},r=t=>{!this._pointerEvent||"pen"!==t.pointerType&&t.pointerType!==Pe||(this.touchDeltaX=t.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};Ce.find(".carousel-item img",this._element).forEach((t=>{ge.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(ge.on(this._element,"pointerdown.bs.carousel",(e=>t(e))),ge.on(this._element,"pointerup.bs.carousel",(t=>r(t))),this._element.classList.add("pointer-event")):(ge.on(this._element,"touchstart.bs.carousel",(e=>t(e))),ge.on(this._element,"touchmove.bs.carousel",(t=>e(t))),ge.on(this._element,"touchend.bs.carousel",(t=>r(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Oe[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?Ce.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const r=t===ze;return Gt(this._items,e,r,this._config.wrap)}_triggerSlideEvent(t,e){const r=this._getItemIndex(t),o=this._getItemIndex(Ce.findOne(Me,this._element));return ge.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:o,to:r})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=Ce.findOne(".active",this._indicatorsElement);e.classList.remove(Ie),e.removeAttribute("aria-current");const r=Ce.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{ge.trigger(this._element,De,{relatedTarget:i,direction:m,from:n,to:a})};if(this._element.classList.contains("slide")){i.classList.add(c),Ut(i),o.classList.add(d),i.classList.add(d);const t=()=>{i.classList.remove(d,c),i.classList.add(Ie),o.classList.remove(Ie,c,d),this._isSliding=!1,setTimeout(p,0)};this._queueCallback(t,o,!0)}else o.classList.remove(Ie),i.classList.add(Ie),this._isSliding=!1,p();l&&this.cycle()}_directionToOrder(t){return[je,Ne].includes(t)?Yt()?t===Ne?Le:ze:t===Ne?ze:Le:t}_orderToDirection(t){return[ze,Le].includes(t)?Yt()?t===Le?Ne:je:t===Le?je:Ne:t}static carouselInterface(t,e){const r=He.getOrCreateInstance(t,e);let{_config:o}=r;"object"==typeof e&&(o={...o,...e});const n="string"==typeof e?e:o.slide;if("number"==typeof e)r.to(e);else if("string"==typeof n){if(void 0===r[n])throw new TypeError(`No method named "${n}"`);r[n]()}else o.interval&&o.ride&&(r.pause(),r.cycle())}static jQueryInterface(t){return this.each((function(){He.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=Mt(this);if(!e||!e.classList.contains("carousel"))return;const r={...Ee.getDataAttributes(e),...Ee.getDataAttributes(this)},o=this.getAttribute("data-bs-slide-to");o&&(r.interval=!1),He.carouselInterface(e,r),o&&He.getInstance(e).to(o),t.preventDefault()}}ge.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",He.dataApiClickHandler),ge.on(window,"load.bs.carousel.data-api",(()=>{const t=Ce.find('[data-bs-ride="carousel"]');for(let e=0,r=t.length;et===this._element));null!==o&&n.length&&(this._selector=o,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Be}static get NAME(){return Re}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=Ce.find(".collapse .collapse",this._config.parent);e=Ce.find(".show, .collapsing",this._config.parent).filter((e=>!t.includes(e)))}const r=Ce.findOne(this._selector);if(e.length){const o=e.find((t=>r!==t));if(t=o?Ve.getInstance(o):null,t&&t._isTransitioning)return}if(ge.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{r!==e&&Ve.getOrCreateInstance(e,{toggle:!1}).hide(),t||he.set(e,"bs.collapse",null)}));const o=this._getDimension();this._element.classList.remove(We),this._element.classList.add(Fe),this._element.style[o]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const n=`scroll${o[0].toUpperCase()+o.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Fe),this._element.classList.add(We,$e),this._element.style[o]="",ge.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[o]=`${this._element[n]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(ge.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,Ut(this._element),this._element.classList.add(Fe),this._element.classList.remove(We,$e);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(Fe),this._element.classList.add(We),ge.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains($e)}_getConfig(t){return(t={...Be,...Ee.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=Rt(t.parent),Bt(Re,t,qe),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=Ce.find(".collapse .collapse",this._config.parent);Ce.find(Xe,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=Mt(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(Ue):t.classList.add(Ue),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const r=Ve.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===r[t])throw new TypeError(`No method named "${t}"`);r[t]()}}))}}ge.on(document,"click.bs.collapse.data-api",Xe,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=It(this);Ce.find(e).forEach((t=>{Ve.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),Kt(Ve);const Ye="dropdown",Ke="Escape",Qe="Space",Ze="ArrowUp",Ge="ArrowDown",Je=new RegExp("ArrowUp|ArrowDown|Escape"),tr="click.bs.dropdown.data-api",er="keydown.bs.dropdown.data-api",rr="show",or='[data-bs-toggle="dropdown"]',nr=".dropdown-menu",ir=Yt()?"top-end":"top-start",ar=Yt()?"top-start":"top-end",lr=Yt()?"bottom-end":"bottom-start",sr=Yt()?"bottom-start":"bottom-end",dr=Yt()?"left-start":"right-start",cr=Yt()?"right-start":"left-start",mr={offset:[0,2],boundary:"clippingParents",reference:"toggle",display:"dynamic",popperConfig:null,autoClose:!0},pr={offset:"(array|string|function)",boundary:"(string|element)",reference:"(string|element|object)",display:"string",popperConfig:"(null|object|function)",autoClose:"(boolean|string)"};class fr extends be{constructor(t,e){super(t),this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar()}static get Default(){return mr}static get DefaultType(){return pr}static get NAME(){return Ye}toggle(){return this._isShown()?this.hide():this.show()}show(){if($t(this._element)||this._isShown(this._menu))return;const t={relatedTarget:this._element};if(ge.trigger(this._element,"show.bs.dropdown",t).defaultPrevented)return;const e=fr.getParentFromElement(this._element);this._inNavbar?Ee.setDataAttribute(this._menu,"popper","none"):this._createPopper(e),"ontouchstart"in document.documentElement&&!e.closest(".navbar-nav")&&[].concat(...document.body.children).forEach((t=>ge.on(t,"mouseover",Ft))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(rr),this._element.classList.add(rr),ge.trigger(this._element,"shown.bs.dropdown",t)}hide(){if($t(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){ge.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>ge.off(t,"mouseover",Ft))),this._popper&&this._popper.destroy(),this._menu.classList.remove(rr),this._element.classList.remove(rr),this._element.setAttribute("aria-expanded","false"),Ee.removeDataAttribute(this._menu,"popper"),ge.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...Ee.getDataAttributes(this._element),...t},Bt(Ye,t,this.constructor.DefaultType),"object"==typeof t.reference&&!Ht(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ye.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===o)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:Ht(this._config.reference)?e=Rt(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const r=this._getPopperConfig(),n=r.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=Nt(e,this._menu,r),n&&Ee.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(rr)}_getMenuElement(){return Ce.next(this._element,nr)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return dr;if(t.classList.contains("dropstart"))return cr;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ar:ir:e?sr:lr}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const r=Ce.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(qt);r.length&&Gt(r,e,t===Ge,!r.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=fr.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=Ce.find(or);for(let r=0,o=e.length;re+t)),this._setElementAttributes(gr,"paddingRight",(e=>e+t)),this._setElementAttributes(ur,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,r){const o=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+o)return;this._saveInitialAttribute(t,e);const n=window.getComputedStyle(t)[e];t.style[e]=`${r(Number.parseFloat(n))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(gr,"paddingRight"),this._resetElementAttributes(ur,"marginRight")}_saveInitialAttribute(t,e){const r=t.style[e];r&&Ee.setDataAttribute(t,e,r)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const r=Ee.getDataAttribute(t,e);void 0===r?t.style.removeProperty(e):(Ee.removeDataAttribute(t,e),t.style[e]=r)}))}_applyManipulationCallback(t,e){Ht(t)?e(t):Ce.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const br={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},xr={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},vr="show",wr="mousedown.bs.backdrop";class yr{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&Ut(this._getElement()),this._getElement().classList.add(vr),this._emulateAnimation((()=>{Qt(t)}))):Qt(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(vr),this._emulateAnimation((()=>{this.dispose(),Qt(t)}))):Qt(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...br,..."object"==typeof t?t:{}}).rootElement=Rt(t.rootElement),Bt("backdrop",t,xr),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),ge.on(this._getElement(),wr,(()=>{Qt(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(ge.off(this._element,wr),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){Zt(t,this._getElement(),this._config.isAnimated)}}const kr={trapElement:null,autofocus:!0},_r={trapElement:"element",autofocus:"boolean"},Er=".bs.focustrap",Cr="backward";class Tr{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),ge.off(document,Er),ge.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),ge.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,ge.off(document,Er))}_handleFocusin(t){const{target:e}=t,{trapElement:r}=this._config;if(e===document||e===r||r.contains(e))return;const o=Ce.focusableChildren(r);0===o.length?r.focus():this._lastTabNavDirection===Cr?o[o.length-1].focus():o[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Cr:"forward")}_getConfig(t){return t={...kr,..."object"==typeof t?t:{}},Bt("focustrap",t,_r),t}}const Ar="modal",Sr="Escape",zr={backdrop:!0,keyboard:!0,focus:!0},Lr={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Nr="hidden.bs.modal",jr="show.bs.modal",Or="resize.bs.modal",Dr="click.dismiss.bs.modal",Ir="keydown.dismiss.bs.modal",Mr="mousedown.dismiss.bs.modal",Pr="modal-open",Hr="show",Rr="modal-static";class Br extends be{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=Ce.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new hr}static get Default(){return zr}static get NAME(){return Ar}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||ge.trigger(this._element,jr,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pr),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),ge.on(this._dialog,Mr,(()=>{ge.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(ge.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(Hr),ge.off(this._element,Dr),ge.off(this._dialog,Mr),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>ge.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new yr({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Tr({trapElement:this._element})}_getConfig(t){return t={...zr,...Ee.getDataAttributes(this._element),..."object"==typeof t?t:{}},Bt(Ar,t,Lr),t}_showElement(t){const e=this._isAnimated(),r=Ce.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,r&&(r.scrollTop=0),e&&Ut(this._element),this._element.classList.add(Hr),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,ge.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?ge.on(this._element,Ir,(t=>{this._config.keyboard&&t.key===Sr?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Sr||this._triggerBackdropTransition()})):ge.off(this._element,Ir)}_setResizeEvent(){this._isShown?ge.on(window,Or,(()=>this._adjustDialog())):ge.off(window,Or)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pr),this._resetAdjustments(),this._scrollBar.reset(),ge.trigger(this._element,Nr)}))}_showBackdrop(t){ge.on(this._element,Dr,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(ge.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:r}=this._element,o=e>document.documentElement.clientHeight;!o&&"hidden"===r.overflowY||t.contains(Rr)||(o||(r.overflowY="hidden"),t.add(Rr),this._queueCallback((()=>{t.remove(Rr),o||this._queueCallback((()=>{r.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),r=e>0;(!r&&t&&!Yt()||r&&!t&&Yt())&&(this._element.style.paddingLeft=`${e}px`),(r&&!t&&!Yt()||!r&&t&&Yt())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const r=Br.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===r[t])throw new TypeError(`No method named "${t}"`);r[t](e)}}))}}ge.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=Mt(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),ge.one(e,jr,(t=>{t.defaultPrevented||ge.one(e,Nr,(()=>{qt(this)&&this.focus()}))})),Br.getOrCreateInstance(e).toggle(this)})),xe(Br),Kt(Br);const qr="offcanvas",$r={backdrop:!0,keyboard:!0,scroll:!1},Wr={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},Fr="show",Ur=".offcanvas.show",Xr="hidden.bs.offcanvas";class Vr extends be{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return qr}static get Default(){return $r}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||ge.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new hr).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Fr),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),ge.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(ge.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove(Fr),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new hr).reset(),ge.trigger(this._element,Xr)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...$r,...Ee.getDataAttributes(this._element),..."object"==typeof t?t:{}},Bt(qr,t,Wr),t}_initializeBackDrop(){return new yr({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Tr({trapElement:this._element})}_addEventListeners(){ge.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Vr.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}ge.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=Mt(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),$t(this))return;ge.one(e,Xr,(()=>{qt(this)&&this.focus()}));const r=Ce.findOne(Ur);r&&r!==e&&Vr.getInstance(r).hide(),Vr.getOrCreateInstance(e).toggle(this)})),ge.on(window,"load.bs.offcanvas.data-api",(()=>Ce.find(Ur).forEach((t=>Vr.getOrCreateInstance(t).show())))),xe(Vr),Kt(Vr);const Yr=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Kr=/^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i,Qr=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Zr=(t,e)=>{const r=t.nodeName.toLowerCase();if(e.includes(r))return!Yr.has(r)||Boolean(Kr.test(t.nodeValue)||Qr.test(t.nodeValue));const o=e.filter((t=>t instanceof RegExp));for(let t=0,e=o.length;t{Zr(t,l)||r.removeAttribute(t.nodeName)}))}return o.body.innerHTML}const Jr="tooltip",to=new Set(["sanitize","allowList","sanitizeFn"]),eo={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},ro={AUTO:"auto",TOP:"top",RIGHT:Yt()?"left":"right",BOTTOM:"bottom",LEFT:Yt()?"right":"left"},oo={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},no={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},io="fade",ao="show",lo="show",so="out",co=".modal",mo="hide.bs.modal",po="hover",fo="focus";class go extends be{constructor(t,e){if(void 0===o)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return oo}static get NAME(){return Jr}static get Event(){return no}static get DefaultType(){return eo}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(ao))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),ge.off(this._element.closest(co),mo,this._hideModalHandler),this.tip&&this.tip.remove(),this._popper&&this._popper.destroy(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=ge.trigger(this._element,this.constructor.Event.SHOW),e=Wt(this._element),r=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!r)return;const o=this.getTipElement(),n=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);o.setAttribute("id",n),this._element.setAttribute("aria-describedby",n),this._config.animation&&o.classList.add(io);const i="function"==typeof this._config.placement?this._config.placement.call(this,o,this._element):this._config.placement,a=this._getAttachment(i);this._addAttachmentClass(a);const{container:l}=this._config;he.set(o,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(l.append(o),ge.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=Nt(this._element,o,this._getPopperConfig(a)),o.classList.add(ao);const s=this._resolvePossibleFunction(this._config.customClass);s&&o.classList.add(...s.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{ge.on(t,"mouseover",Ft)}));const d=this.tip.classList.contains(io);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,ge.trigger(this._element,this.constructor.Event.SHOWN),t===so&&this._leave(null,this)}),this.tip,d)}hide(){if(!this._popper)return;const t=this.getTipElement();if(ge.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(ao),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>ge.off(t,"mouseover",Ft))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(io);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==lo&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),ge.trigger(this._element,this.constructor.Event.HIDDEN),this._popper&&(this._popper.destroy(),this._popper=null))}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(io,ao),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".tooltip-inner")}_sanitizeAndSetContent(t,e,r){const o=Ce.findOne(r,t);e||!o?this.setElementContent(o,e):o.remove()}setElementContent(t,e){if(null!==t)return Ht(e)?(e=Rt(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Gr(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return ro[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)ge.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===po?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,r=t===po?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;ge.on(this._element,e,this._config.selector,(t=>this._enter(t))),ge.on(this._element,r,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},ge.on(this._element.closest(co),mo,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?fo:po]=!0),e.getTipElement().classList.contains(ao)||e._hoverState===lo?e._hoverState=lo:(clearTimeout(e._timeout),e._hoverState=lo,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===lo&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?fo:po]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=so,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===so&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=Ee.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{to.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:Rt(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),Bt(Jr,t,this.constructor.DefaultType),t.sanitize&&(t.template=Gr(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),r=t.getAttribute("class").match(e);null!==r&&r.length>0&&r.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}static jQueryInterface(t){return this.each((function(){const e=go.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Kt(go);const uo={...go.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},ho={...go.DefaultType,content:"(string|element|function)"},bo={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class xo extends go{static get Default(){return uo}static get NAME(){return"popover"}static get Event(){return bo}static get DefaultType(){return ho}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=xo.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Kt(xo);const vo="scrollspy",wo={offset:10,method:"auto",target:""},yo={offset:"number",method:"string",target:"(string|element)"},ko="active",_o=".nav-link, .list-group-item, .dropdown-item",Eo="position";class Co extends be{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,ge.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return wo}static get NAME(){return vo}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":Eo,e="auto"===this._config.method?t:this._config.method,r=e===Eo?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),Ce.find(_o,this._config.target).map((t=>{const o=It(t),n=o?Ce.findOne(o):null;if(n){const t=n.getBoundingClientRect();if(t.width||t.height)return[Ee[e](n).top+r,o]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){ge.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...wo,...Ee.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=Rt(t.target)||document.documentElement,Bt(vo,t,yo),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),r=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=r){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),r=Ce.findOne(e.join(","),this._config.target);r.classList.add(ko),r.classList.contains("dropdown-item")?Ce.findOne(".dropdown-toggle",r.closest(".dropdown")).classList.add(ko):Ce.parents(r,".nav, .list-group").forEach((t=>{Ce.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(ko))),Ce.prev(t,".nav-item").forEach((t=>{Ce.children(t,".nav-link").forEach((t=>t.classList.add(ko)))}))})),ge.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){Ce.find(_o,this._config.target).filter((t=>t.classList.contains(ko))).forEach((t=>t.classList.remove(ko)))}static jQueryInterface(t){return this.each((function(){const e=Co.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}ge.on(window,"load.bs.scrollspy.data-api",(()=>{Ce.find('[data-bs-spy="scroll"]').forEach((t=>new Co(t)))})),Kt(Co);const To="active",Ao="fade",So="show",zo=".active",Lo=":scope > li > .active";class No extends be{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(To))return;let t;const e=Mt(this._element),r=this._element.closest(".nav, .list-group");if(r){const e="UL"===r.nodeName||"OL"===r.nodeName?Lo:zo;t=Ce.find(e,r),t=t[t.length-1]}const o=t?ge.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(ge.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==o&&o.defaultPrevented)return;this._activate(this._element,r);const n=()=>{ge.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),ge.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,n):n()}_activate(t,e,r){const o=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?Ce.children(e,zo):Ce.find(Lo,e))[0],n=r&&o&&o.classList.contains(Ao),i=()=>this._transitionComplete(t,o,r);o&&n?(o.classList.remove(So),this._queueCallback(i,t,!0)):i()}_transitionComplete(t,e,r){if(e){e.classList.remove(To);const t=Ce.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(To),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(To),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),Ut(t),t.classList.contains(Ao)&&t.classList.add(So);let o=t.parentNode;if(o&&"LI"===o.nodeName&&(o=o.parentNode),o&&o.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&Ce.find(".dropdown-toggle",e).forEach((t=>t.classList.add(To))),t.setAttribute("aria-expanded",!0)}r&&r()}static jQueryInterface(t){return this.each((function(){const e=No.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}ge.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),$t(this)||No.getOrCreateInstance(this).show()})),Kt(No);const jo="toast",Oo="hide",Do="show",Io="showing",Mo={animation:"boolean",autohide:"boolean",delay:"number"},Po={animation:!0,autohide:!0,delay:5e3};class Ho extends be{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Mo}static get Default(){return Po}static get NAME(){return jo}show(){ge.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Oo),Ut(this._element),this._element.classList.add(Do),this._element.classList.add(Io),this._queueCallback((()=>{this._element.classList.remove(Io),ge.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Do)&&(ge.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(Io),this._queueCallback((()=>{this._element.classList.add(Oo),this._element.classList.remove(Io),this._element.classList.remove(Do),ge.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Do)&&this._element.classList.remove(Do),super.dispose()}_getConfig(t){return t={...Po,...Ee.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},Bt(jo,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const r=t.relatedTarget;this._element===r||this._element.contains(r)||this._maybeScheduleHide()}_setListeners(){ge.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),ge.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),ge.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),ge.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Ho.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}xe(Ho),Kt(Ho)},666:(t,e,r)=>{"use strict";r.d(e,{Z:()=>B});var o=r(645),n=r.n(o),i=r(667),a=r.n(i),l=new URL(r(214),r.b),s=new URL(r(349),r.b),d=new URL(r(204),r.b),c=new URL(r(931),r.b),m=new URL(r(486),r.b),p=new URL(r(609),r.b),f=new URL(r(469),r.b),g=new URL(r(819),r.b),u=new URL(r(144),r.b),h=new URL(r(217),r.b),b=new URL(r(956),r.b),x=new URL(r(740),r.b),v=new URL(r(460),r.b),w=new URL(r(175),r.b),y=new URL(r(647),r.b),k=new URL(r(692),r.b),_=n()((function(t){return t[1]})),E=a()(l),C=a()(s),T=a()(d),A=a()(c),S=a()(m),z=a()(p),L=a()(f),N=a()(g),j=a()(u),O=a()(h),D=a()(b),I=a()(x),M=a()(v),P=a()(w),H=a()(y),R=a()(k);_.push([t.id,'@charset "UTF-8";/*!\n * Bootstrap v5.1.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url('+E+');background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url('+C+")}.form-check-input:checked[type=radio]{background-image:url("+T+")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("+A+")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("+S+");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("+z+")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("+L+")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("+N+');background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url('+E+"),url("+N+");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("+j+');background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url('+E+"),url("+j+');background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url('+O+")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("+D+")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("+I+');transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url('+M+');background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{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);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url('+P+') center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url('+H+")}.carousel-control-next-icon{background-image:url("+R+')}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}',""]);const B=_},426:(t,e,r)=>{"use strict";r.d(e,{Z:()=>i});var o=r(645),n=r.n(o)()((function(t){return t[1]}));n.push([t.id,".table {\r\n font-size: 12px !important;\r\n}\r\n",""]);const i=n},645:t=>{"use strict";t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var r=t(e);return e[2]?"@media ".concat(e[2]," {").concat(r,"}"):r})).join("")},e.i=function(t,r,o){"string"==typeof t&&(t=[[null,t,""]]);var n={};if(o)for(var i=0;i{"use strict";t.exports=function(t,e){return e||(e={}),t?(t=String(t.__esModule?t.default:t),/^['"].*['"]$/.test(t)&&(t=t.slice(1,-1)),e.hash&&(t+=e.hash),/["'() \t\n]|(%20)/.test(t)||e.needQuotes?'"'.concat(t.replace(/"/g,'\\"').replace(/\n/g,"\\n"),'"'):t):t}},70:function(t,e){var r;!function(e,r){"use strict";"object"==typeof t.exports?t.exports=e.document?r(e,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return r(t)}:r(e)}("undefined"!=typeof window?window:this,(function(o,n){"use strict";var i=[],a=Object.getPrototypeOf,l=i.slice,s=i.flat?function(t){return i.flat.call(t)}:function(t){return i.concat.apply([],t)},d=i.push,c=i.indexOf,m={},p=m.toString,f=m.hasOwnProperty,g=f.toString,u=g.call(Object),h={},b=function(t){return"function"==typeof t&&"number"!=typeof t.nodeType&&"function"!=typeof t.item},x=function(t){return null!=t&&t===t.window},v=o.document,w={type:!0,src:!0,nonce:!0,noModule:!0};function y(t,e,r){var o,n,i=(r=r||v).createElement("script");if(i.text=t,e)for(o in w)(n=e[o]||e.getAttribute&&e.getAttribute(o))&&i.setAttribute(o,n);r.head.appendChild(i).parentNode.removeChild(i)}function k(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?m[p.call(t)]||"object":typeof t}var _="3.6.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",E=function(t,e){return new E.fn.init(t,e)};function C(t){var e=!!t&&"length"in t&&t.length,r=k(t);return!b(t)&&!x(t)&&("array"===r||0===e||"number"==typeof e&&e>0&&e-1 in t)}E.fn=E.prototype={jquery:_,constructor:E,length:0,toArray:function(){return l.call(this)},get:function(t){return null==t?l.call(this):t<0?this[t+this.length]:this[t]},pushStack:function(t){var e=E.merge(this.constructor(),t);return e.prevObject=this,e},each:function(t){return E.each(this,t)},map:function(t){return this.pushStack(E.map(this,(function(e,r){return t.call(e,r,e)})))},slice:function(){return this.pushStack(l.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(E.grep(this,(function(t,e){return(e+1)%2})))},odd:function(){return this.pushStack(E.grep(this,(function(t,e){return e%2})))},eq:function(t){var e=this.length,r=+t+(t<0?e:0);return this.pushStack(r>=0&&r+~]|[\\x20\\t\\r\\n\\f])[\\x20\\t\\r\\n\\f]*"),U=new RegExp(P+"|>"),X=new RegExp(B),V=new RegExp("^"+H+"$"),Y={ID:new RegExp("^#("+H+")"),CLASS:new RegExp("^\\.("+H+")"),TAG:new RegExp("^("+H+"|[*])"),ATTR:new RegExp("^"+R),PSEUDO:new RegExp("^"+B),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\([\\x20\\t\\r\\n\\f]*(even|odd|(([+-]|)(\\d*)n|)[\\x20\\t\\r\\n\\f]*(?:([+-]|)[\\x20\\t\\r\\n\\f]*(\\d+)|))[\\x20\\t\\r\\n\\f]*\\)|)","i"),bool:new RegExp("^(?:"+M+")$","i"),needsContext:new RegExp("^[\\x20\\t\\r\\n\\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\([\\x20\\t\\r\\n\\f]*((?:-\\d)?\\d*)[\\x20\\t\\r\\n\\f]*\\)|)(?=[^-]|$)","i")},K=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,G=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,tt=/[+~]/,et=new RegExp("\\\\[\\da-fA-F]{1,6}[\\x20\\t\\r\\n\\f]?|\\\\([^\\r\\n\\f])","g"),rt=function(t,e){var r="0x"+t.slice(1)-65536;return e||(r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320))},ot=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,nt=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t},it=function(){p()},at=wt((function(t){return!0===t.disabled&&"fieldset"===t.nodeName.toLowerCase()}),{dir:"parentNode",next:"legend"});try{O.apply(L=D.call(y.childNodes),y.childNodes),L[y.childNodes.length].nodeType}catch(t){O={apply:L.length?function(t,e){j.apply(t,D.call(e))}:function(t,e){for(var r=t.length,o=0;t[r++]=e[o++];);t.length=r-1}}}function lt(t,e,o,n){var i,l,d,c,m,g,b,x=e&&e.ownerDocument,y=e?e.nodeType:9;if(o=o||[],"string"!=typeof t||!t||1!==y&&9!==y&&11!==y)return o;if(!n&&(p(e),e=e||f,u)){if(11!==y&&(m=J.exec(t)))if(i=m[1]){if(9===y){if(!(d=e.getElementById(i)))return o;if(d.id===i)return o.push(d),o}else if(x&&(d=x.getElementById(i))&&v(e,d)&&d.id===i)return o.push(d),o}else{if(m[2])return O.apply(o,e.getElementsByTagName(t)),o;if((i=m[3])&&r.getElementsByClassName&&e.getElementsByClassName)return O.apply(o,e.getElementsByClassName(i)),o}if(r.qsa&&!A[t+" "]&&(!h||!h.test(t))&&(1!==y||"object"!==e.nodeName.toLowerCase())){if(b=t,x=e,1===y&&(U.test(t)||F.test(t))){for((x=tt.test(t)&&bt(e.parentNode)||e)===e&&r.scope||((c=e.getAttribute("id"))?c=c.replace(ot,nt):e.setAttribute("id",c=w)),l=(g=a(t)).length;l--;)g[l]=(c?"#"+c:":scope")+" "+vt(g[l]);b=g.join(",")}try{return O.apply(o,x.querySelectorAll(b)),o}catch(e){A(t,!0)}finally{c===w&&e.removeAttribute("id")}}}return s(t.replace($,"$1"),e,o,n)}function st(){var t=[];return function e(r,n){return t.push(r+" ")>o.cacheLength&&delete e[t.shift()],e[r+" "]=n}}function dt(t){return t[w]=!0,t}function ct(t){var e=f.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function mt(t,e){for(var r=t.split("|"),n=r.length;n--;)o.attrHandle[r[n]]=e}function pt(t,e){var r=e&&t,o=r&&1===t.nodeType&&1===e.nodeType&&t.sourceIndex-e.sourceIndex;if(o)return o;if(r)for(;r=r.nextSibling;)if(r===e)return-1;return t?1:-1}function ft(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function gt(t){return function(e){var r=e.nodeName.toLowerCase();return("input"===r||"button"===r)&&e.type===t}}function ut(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&at(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ht(t){return dt((function(e){return e=+e,dt((function(r,o){for(var n,i=t([],r.length,e),a=i.length;a--;)r[n=i[a]]&&(r[n]=!(o[n]=r[n]))}))}))}function bt(t){return t&&void 0!==t.getElementsByTagName&&t}for(e in r=lt.support={},i=lt.isXML=function(t){var e=t&&t.namespaceURI,r=t&&(t.ownerDocument||t).documentElement;return!K.test(e||r&&r.nodeName||"HTML")},p=lt.setDocument=function(t){var e,n,a=t?t.ownerDocument||t:y;return a!=f&&9===a.nodeType&&a.documentElement?(g=(f=a).documentElement,u=!i(f),y!=f&&(n=f.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",it,!1):n.attachEvent&&n.attachEvent("onunload",it)),r.scope=ct((function(t){return g.appendChild(t).appendChild(f.createElement("div")),void 0!==t.querySelectorAll&&!t.querySelectorAll(":scope fieldset div").length})),r.attributes=ct((function(t){return t.className="i",!t.getAttribute("className")})),r.getElementsByTagName=ct((function(t){return t.appendChild(f.createComment("")),!t.getElementsByTagName("*").length})),r.getElementsByClassName=G.test(f.getElementsByClassName),r.getById=ct((function(t){return g.appendChild(t).id=w,!f.getElementsByName||!f.getElementsByName(w).length})),r.getById?(o.filter.ID=function(t){var e=t.replace(et,rt);return function(t){return t.getAttribute("id")===e}},o.find.ID=function(t,e){if(void 0!==e.getElementById&&u){var r=e.getElementById(t);return r?[r]:[]}}):(o.filter.ID=function(t){var e=t.replace(et,rt);return function(t){var r=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return r&&r.value===e}},o.find.ID=function(t,e){if(void 0!==e.getElementById&&u){var r,o,n,i=e.getElementById(t);if(i){if((r=i.getAttributeNode("id"))&&r.value===t)return[i];for(n=e.getElementsByName(t),o=0;i=n[o++];)if((r=i.getAttributeNode("id"))&&r.value===t)return[i]}return[]}}),o.find.TAG=r.getElementsByTagName?function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):r.qsa?e.querySelectorAll(t):void 0}:function(t,e){var r,o=[],n=0,i=e.getElementsByTagName(t);if("*"===t){for(;r=i[n++];)1===r.nodeType&&o.push(r);return o}return i},o.find.CLASS=r.getElementsByClassName&&function(t,e){if(void 0!==e.getElementsByClassName&&u)return e.getElementsByClassName(t)},b=[],h=[],(r.qsa=G.test(f.querySelectorAll))&&(ct((function(t){var e;g.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&h.push("[*^$]=[\\x20\\t\\r\\n\\f]*(?:''|\"\")"),t.querySelectorAll("[selected]").length||h.push("\\[[\\x20\\t\\r\\n\\f]*(?:value|"+M+")"),t.querySelectorAll("[id~="+w+"-]").length||h.push("~="),(e=f.createElement("input")).setAttribute("name",""),t.appendChild(e),t.querySelectorAll("[name='']").length||h.push("\\[[\\x20\\t\\r\\n\\f]*name[\\x20\\t\\r\\n\\f]*=[\\x20\\t\\r\\n\\f]*(?:''|\"\")"),t.querySelectorAll(":checked").length||h.push(":checked"),t.querySelectorAll("a#"+w+"+*").length||h.push(".#.+[+~]"),t.querySelectorAll("\\\f"),h.push("[\\r\\n\\f]")})),ct((function(t){t.innerHTML="";var e=f.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&h.push("name[\\x20\\t\\r\\n\\f]*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&h.push(":enabled",":disabled"),g.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&h.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),h.push(",.*:")}))),(r.matchesSelector=G.test(x=g.matches||g.webkitMatchesSelector||g.mozMatchesSelector||g.oMatchesSelector||g.msMatchesSelector))&&ct((function(t){r.disconnectedMatch=x.call(t,"*"),x.call(t,"[s!='']:x"),b.push("!=",B)})),h=h.length&&new RegExp(h.join("|")),b=b.length&&new RegExp(b.join("|")),e=G.test(g.compareDocumentPosition),v=e||G.test(g.contains)?function(t,e){var r=9===t.nodeType?t.documentElement:t,o=e&&e.parentNode;return t===o||!(!o||1!==o.nodeType||!(r.contains?r.contains(o):t.compareDocumentPosition&&16&t.compareDocumentPosition(o)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},S=e?function(t,e){if(t===e)return m=!0,0;var o=!t.compareDocumentPosition-!e.compareDocumentPosition;return o||(1&(o=(t.ownerDocument||t)==(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!r.sortDetached&&e.compareDocumentPosition(t)===o?t==f||t.ownerDocument==y&&v(y,t)?-1:e==f||e.ownerDocument==y&&v(y,e)?1:c?I(c,t)-I(c,e):0:4&o?-1:1)}:function(t,e){if(t===e)return m=!0,0;var r,o=0,n=t.parentNode,i=e.parentNode,a=[t],l=[e];if(!n||!i)return t==f?-1:e==f?1:n?-1:i?1:c?I(c,t)-I(c,e):0;if(n===i)return pt(t,e);for(r=t;r=r.parentNode;)a.unshift(r);for(r=e;r=r.parentNode;)l.unshift(r);for(;a[o]===l[o];)o++;return o?pt(a[o],l[o]):a[o]==y?-1:l[o]==y?1:0},f):f},lt.matches=function(t,e){return lt(t,null,null,e)},lt.matchesSelector=function(t,e){if(p(t),r.matchesSelector&&u&&!A[e+" "]&&(!b||!b.test(e))&&(!h||!h.test(e)))try{var o=x.call(t,e);if(o||r.disconnectedMatch||t.document&&11!==t.document.nodeType)return o}catch(t){A(e,!0)}return lt(e,f,null,[t]).length>0},lt.contains=function(t,e){return(t.ownerDocument||t)!=f&&p(t),v(t,e)},lt.attr=function(t,e){(t.ownerDocument||t)!=f&&p(t);var n=o.attrHandle[e.toLowerCase()],i=n&&z.call(o.attrHandle,e.toLowerCase())?n(t,e,!u):void 0;return void 0!==i?i:r.attributes||!u?t.getAttribute(e):(i=t.getAttributeNode(e))&&i.specified?i.value:null},lt.escape=function(t){return(t+"").replace(ot,nt)},lt.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},lt.uniqueSort=function(t){var e,o=[],n=0,i=0;if(m=!r.detectDuplicates,c=!r.sortStable&&t.slice(0),t.sort(S),m){for(;e=t[i++];)e===t[i]&&(n=o.push(i));for(;n--;)t.splice(o[n],1)}return c=null,t},n=lt.getText=function(t){var e,r="",o=0,i=t.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)r+=n(t)}else if(3===i||4===i)return t.nodeValue}else for(;e=t[o++];)r+=n(e);return r},(o=lt.selectors={cacheLength:50,createPseudo:dt,match:Y,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(et,rt),t[3]=(t[3]||t[4]||t[5]||"").replace(et,rt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||lt.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&<.error(t[0]),t},PSEUDO:function(t){var e,r=!t[6]&&t[2];return Y.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":r&&X.test(r)&&(e=a(r,!0))&&(e=r.indexOf(")",r.length-e)-r.length)&&(t[0]=t[0].slice(0,e),t[2]=r.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(et,rt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=E[t+" "];return e||(e=new RegExp("(^|[\\x20\\t\\r\\n\\f])"+t+"("+P+"|$)"))&&E(t,(function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")}))},ATTR:function(t,e,r){return function(o){var n=lt.attr(o,t);return null==n?"!="===e:!e||(n+="","="===e?n===r:"!="===e?n!==r:"^="===e?r&&0===n.indexOf(r):"*="===e?r&&n.indexOf(r)>-1:"$="===e?r&&n.slice(-r.length)===r:"~="===e?(" "+n.replace(q," ")+" ").indexOf(r)>-1:"|="===e&&(n===r||n.slice(0,r.length+1)===r+"-"))}},CHILD:function(t,e,r,o,n){var i="nth"!==t.slice(0,3),a="last"!==t.slice(-4),l="of-type"===e;return 1===o&&0===n?function(t){return!!t.parentNode}:function(e,r,s){var d,c,m,p,f,g,u=i!==a?"nextSibling":"previousSibling",h=e.parentNode,b=l&&e.nodeName.toLowerCase(),x=!s&&!l,v=!1;if(h){if(i){for(;u;){for(p=e;p=p[u];)if(l?p.nodeName.toLowerCase()===b:1===p.nodeType)return!1;g=u="only"===t&&!g&&"nextSibling"}return!0}if(g=[a?h.firstChild:h.lastChild],a&&x){for(v=(f=(d=(c=(m=(p=h)[w]||(p[w]={}))[p.uniqueID]||(m[p.uniqueID]={}))[t]||[])[0]===k&&d[1])&&d[2],p=f&&h.childNodes[f];p=++f&&p&&p[u]||(v=f=0)||g.pop();)if(1===p.nodeType&&++v&&p===e){c[t]=[k,f,v];break}}else if(x&&(v=f=(d=(c=(m=(p=e)[w]||(p[w]={}))[p.uniqueID]||(m[p.uniqueID]={}))[t]||[])[0]===k&&d[1]),!1===v)for(;(p=++f&&p&&p[u]||(v=f=0)||g.pop())&&((l?p.nodeName.toLowerCase()!==b:1!==p.nodeType)||!++v||(x&&((c=(m=p[w]||(p[w]={}))[p.uniqueID]||(m[p.uniqueID]={}))[t]=[k,v]),p!==e)););return(v-=n)===o||v%o==0&&v/o>=0}}},PSEUDO:function(t,e){var r,n=o.pseudos[t]||o.setFilters[t.toLowerCase()]||lt.error("unsupported pseudo: "+t);return n[w]?n(e):n.length>1?(r=[t,t,"",e],o.setFilters.hasOwnProperty(t.toLowerCase())?dt((function(t,r){for(var o,i=n(t,e),a=i.length;a--;)t[o=I(t,i[a])]=!(r[o]=i[a])})):function(t){return n(t,0,r)}):n}},pseudos:{not:dt((function(t){var e=[],r=[],o=l(t.replace($,"$1"));return o[w]?dt((function(t,e,r,n){for(var i,a=o(t,null,n,[]),l=t.length;l--;)(i=a[l])&&(t[l]=!(e[l]=i))})):function(t,n,i){return e[0]=t,o(e,null,i,r),e[0]=null,!r.pop()}})),has:dt((function(t){return function(e){return lt(t,e).length>0}})),contains:dt((function(t){return t=t.replace(et,rt),function(e){return(e.textContent||n(e)).indexOf(t)>-1}})),lang:dt((function(t){return V.test(t||"")||lt.error("unsupported lang: "+t),t=t.replace(et,rt).toLowerCase(),function(e){var r;do{if(r=u?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(r=r.toLowerCase())===t||0===r.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}})),target:function(e){var r=t.location&&t.location.hash;return r&&r.slice(1)===e.id},root:function(t){return t===g},focus:function(t){return t===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:ut(!1),disabled:ut(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!o.pseudos.empty(t)},header:function(t){return Z.test(t.nodeName)},input:function(t){return Q.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:ht((function(){return[0]})),last:ht((function(t,e){return[e-1]})),eq:ht((function(t,e,r){return[r<0?r+e:r]})),even:ht((function(t,e){for(var r=0;re?e:r;--o>=0;)t.push(o);return t})),gt:ht((function(t,e,r){for(var o=r<0?r+e:r;++o1?function(e,r,o){for(var n=t.length;n--;)if(!t[n](e,r,o))return!1;return!0}:t[0]}function kt(t,e,r,o,n){for(var i,a=[],l=0,s=t.length,d=null!=e;l-1&&(i[d]=!(a[d]=m))}}else b=kt(b===a?b.splice(g,b.length):b),n?n(null,a,b,s):O.apply(a,b)}))}function Et(t){for(var e,r,n,i=t.length,a=o.relative[t[0].type],l=a||o.relative[" "],s=a?1:0,c=wt((function(t){return t===e}),l,!0),m=wt((function(t){return I(e,t)>-1}),l,!0),p=[function(t,r,o){var n=!a&&(o||r!==d)||((e=r).nodeType?c(t,r,o):m(t,r,o));return e=null,n}];s1&&yt(p),s>1&&vt(t.slice(0,s-1).concat({value:" "===t[s-2].type?"*":""})).replace($,"$1"),r,s0,n=t.length>0,i=function(i,a,l,s,c){var m,g,h,b=0,x="0",v=i&&[],w=[],y=d,_=i||n&&o.find.TAG("*",c),E=k+=null==y?1:Math.random()||.1,C=_.length;for(c&&(d=a==f||a||c);x!==C&&null!=(m=_[x]);x++){if(n&&m){for(g=0,a||m.ownerDocument==f||(p(m),l=!u);h=t[g++];)if(h(m,a||f,l)){s.push(m);break}c&&(k=E)}r&&((m=!h&&m)&&b--,i&&v.push(m))}if(b+=x,r&&x!==b){for(g=0;h=e[g++];)h(v,w,a,l);if(i){if(b>0)for(;x--;)v[x]||w[x]||(w[x]=N.call(s));w=kt(w)}O.apply(s,w),c&&!i&&w.length>0&&b+e.length>1&<.uniqueSort(s)}return c&&(k=E,d=y),v};return r?dt(i):i}(i,n))).selector=t}return l},s=lt.select=function(t,e,r,n){var i,s,d,c,m,p="function"==typeof t&&t,f=!n&&a(t=p.selector||t);if(r=r||[],1===f.length){if((s=f[0]=f[0].slice(0)).length>2&&"ID"===(d=s[0]).type&&9===e.nodeType&&u&&o.relative[s[1].type]){if(!(e=(o.find.ID(d.matches[0].replace(et,rt),e)||[])[0]))return r;p&&(e=e.parentNode),t=t.slice(s.shift().value.length)}for(i=Y.needsContext.test(t)?0:s.length;i--&&(d=s[i],!o.relative[c=d.type]);)if((m=o.find[c])&&(n=m(d.matches[0].replace(et,rt),tt.test(s[0].type)&&bt(e.parentNode)||e))){if(s.splice(i,1),!(t=n.length&&vt(s)))return O.apply(r,n),r;break}}return(p||l(t,f))(n,e,!u,r,!e||tt.test(t)&&bt(e.parentNode)||e),r},r.sortStable=w.split("").sort(S).join("")===w,r.detectDuplicates=!!m,p(),r.sortDetached=ct((function(t){return 1&t.compareDocumentPosition(f.createElement("fieldset"))})),ct((function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")}))||mt("type|href|height|width",(function(t,e,r){if(!r)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)})),r.attributes&&ct((function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")}))||mt("value",(function(t,e,r){if(!r&&"input"===t.nodeName.toLowerCase())return t.defaultValue})),ct((function(t){return null==t.getAttribute("disabled")}))||mt(M,(function(t,e,r){var o;if(!r)return!0===t[e]?e.toLowerCase():(o=t.getAttributeNode(e))&&o.specified?o.value:null})),lt}(o);E.find=T,E.expr=T.selectors,E.expr[":"]=E.expr.pseudos,E.uniqueSort=E.unique=T.uniqueSort,E.text=T.getText,E.isXMLDoc=T.isXML,E.contains=T.contains,E.escapeSelector=T.escape;var A=function(t,e,r){for(var o=[],n=void 0!==r;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(n&&E(t).is(r))break;o.push(t)}return o},S=function(t,e){for(var r=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&r.push(t);return r},z=E.expr.match.needsContext;function L(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}var N=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(t,e,r){return b(e)?E.grep(t,(function(t,o){return!!e.call(t,o,t)!==r})):e.nodeType?E.grep(t,(function(t){return t===e!==r})):"string"!=typeof e?E.grep(t,(function(t){return c.call(e,t)>-1!==r})):E.filter(e,t,r)}E.filter=function(t,e,r){var o=e[0];return r&&(t=":not("+t+")"),1===e.length&&1===o.nodeType?E.find.matchesSelector(o,t)?[o]:[]:E.find.matches(t,E.grep(e,(function(t){return 1===t.nodeType})))},E.fn.extend({find:function(t){var e,r,o=this.length,n=this;if("string"!=typeof t)return this.pushStack(E(t).filter((function(){for(e=0;e1?E.uniqueSort(r):r},filter:function(t){return this.pushStack(j(this,t||[],!1))},not:function(t){return this.pushStack(j(this,t||[],!0))},is:function(t){return!!j(this,"string"==typeof t&&z.test(t)?E(t):t||[],!1).length}});var O,D=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(E.fn.init=function(t,e,r){var o,n;if(!t)return this;if(r=r||O,"string"==typeof t){if(!(o="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:D.exec(t))||!o[1]&&e)return!e||e.jquery?(e||r).find(t):this.constructor(e).find(t);if(o[1]){if(e=e instanceof E?e[0]:e,E.merge(this,E.parseHTML(o[1],e&&e.nodeType?e.ownerDocument||e:v,!0)),N.test(o[1])&&E.isPlainObject(e))for(o in e)b(this[o])?this[o](e[o]):this.attr(o,e[o]);return this}return(n=v.getElementById(o[2]))&&(this[0]=n,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):b(t)?void 0!==r.ready?r.ready(t):t(E):E.makeArray(t,this)}).prototype=E.fn,O=E(v);var I=/^(?:parents|prev(?:Until|All))/,M={children:!0,contents:!0,next:!0,prev:!0};function P(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}E.fn.extend({has:function(t){var e=E(t,this),r=e.length;return this.filter((function(){for(var t=0;t-1:1===r.nodeType&&E.find.matchesSelector(r,t))){i.push(r);break}return this.pushStack(i.length>1?E.uniqueSort(i):i)},index:function(t){return t?"string"==typeof t?c.call(E(t),this[0]):c.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(E.uniqueSort(E.merge(this.get(),E(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),E.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return A(t,"parentNode")},parentsUntil:function(t,e,r){return A(t,"parentNode",r)},next:function(t){return P(t,"nextSibling")},prev:function(t){return P(t,"previousSibling")},nextAll:function(t){return A(t,"nextSibling")},prevAll:function(t){return A(t,"previousSibling")},nextUntil:function(t,e,r){return A(t,"nextSibling",r)},prevUntil:function(t,e,r){return A(t,"previousSibling",r)},siblings:function(t){return S((t.parentNode||{}).firstChild,t)},children:function(t){return S(t.firstChild)},contents:function(t){return null!=t.contentDocument&&a(t.contentDocument)?t.contentDocument:(L(t,"template")&&(t=t.content||t),E.merge([],t.childNodes))}},(function(t,e){E.fn[t]=function(r,o){var n=E.map(this,e,r);return"Until"!==t.slice(-5)&&(o=r),o&&"string"==typeof o&&(n=E.filter(o,n)),this.length>1&&(M[t]||E.uniqueSort(n),I.test(t)&&n.reverse()),this.pushStack(n)}}));var H=/[^\x20\t\r\n\f]+/g;function R(t){return t}function B(t){throw t}function q(t,e,r,o){var n;try{t&&b(n=t.promise)?n.call(t).done(e).fail(r):t&&b(n=t.then)?n.call(t,e,r):e.apply(void 0,[t].slice(o))}catch(t){r.apply(void 0,[t])}}E.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return E.each(t.match(H)||[],(function(t,r){e[r]=!0})),e}(t):E.extend({},t);var e,r,o,n,i=[],a=[],l=-1,s=function(){for(n=n||t.once,o=e=!0;a.length;l=-1)for(r=a.shift();++l-1;)i.splice(r,1),r<=l&&l--})),this},has:function(t){return t?E.inArray(t,i)>-1:i.length>0},empty:function(){return i&&(i=[]),this},disable:function(){return n=a=[],i=r="",this},disabled:function(){return!i},lock:function(){return n=a=[],r||e||(i=r=""),this},locked:function(){return!!n},fireWith:function(t,r){return n||(r=[t,(r=r||[]).slice?r.slice():r],a.push(r),e||s()),this},fire:function(){return d.fireWith(this,arguments),this},fired:function(){return!!o}};return d},E.extend({Deferred:function(t){var e=[["notify","progress",E.Callbacks("memory"),E.Callbacks("memory"),2],["resolve","done",E.Callbacks("once memory"),E.Callbacks("once memory"),0,"resolved"],["reject","fail",E.Callbacks("once memory"),E.Callbacks("once memory"),1,"rejected"]],r="pending",n={state:function(){return r},always:function(){return i.done(arguments).fail(arguments),this},catch:function(t){return n.then(null,t)},pipe:function(){var t=arguments;return E.Deferred((function(r){E.each(e,(function(e,o){var n=b(t[o[4]])&&t[o[4]];i[o[1]]((function(){var t=n&&n.apply(this,arguments);t&&b(t.promise)?t.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[o[0]+"With"](this,n?[t]:arguments)}))})),t=null})).promise()},then:function(t,r,n){var i=0;function a(t,e,r,n){return function(){var l=this,s=arguments,d=function(){var o,d;if(!(t=i&&(r!==B&&(l=void 0,s=[o]),e.rejectWith(l,s))}};t?c():(E.Deferred.getStackHook&&(c.stackTrace=E.Deferred.getStackHook()),o.setTimeout(c))}}return E.Deferred((function(o){e[0][3].add(a(0,o,b(n)?n:R,o.notifyWith)),e[1][3].add(a(0,o,b(t)?t:R)),e[2][3].add(a(0,o,b(r)?r:B))})).promise()},promise:function(t){return null!=t?E.extend(t,n):n}},i={};return E.each(e,(function(t,o){var a=o[2],l=o[5];n[o[1]]=a.add,l&&a.add((function(){r=l}),e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),a.add(o[3].fire),i[o[0]]=function(){return i[o[0]+"With"](this===i?void 0:this,arguments),this},i[o[0]+"With"]=a.fireWith})),n.promise(i),t&&t.call(i,i),i},when:function(t){var e=arguments.length,r=e,o=Array(r),n=l.call(arguments),i=E.Deferred(),a=function(t){return function(r){o[t]=this,n[t]=arguments.length>1?l.call(arguments):r,--e||i.resolveWith(o,n)}};if(e<=1&&(q(t,i.done(a(r)).resolve,i.reject,!e),"pending"===i.state()||b(n[r]&&n[r].then)))return i.then();for(;r--;)q(n[r],a(r),i.reject);return i.promise()}});var $=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;E.Deferred.exceptionHook=function(t,e){o.console&&o.console.warn&&t&&$.test(t.name)&&o.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},E.readyException=function(t){o.setTimeout((function(){throw t}))};var W=E.Deferred();function F(){v.removeEventListener("DOMContentLoaded",F),o.removeEventListener("load",F),E.ready()}E.fn.ready=function(t){return W.then(t).catch((function(t){E.readyException(t)})),this},E.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--E.readyWait:E.isReady)||(E.isReady=!0,!0!==t&&--E.readyWait>0||W.resolveWith(v,[E]))}}),E.ready.then=W.then,"complete"===v.readyState||"loading"!==v.readyState&&!v.documentElement.doScroll?o.setTimeout(E.ready):(v.addEventListener("DOMContentLoaded",F),o.addEventListener("load",F));var U=function(t,e,r,o,n,i,a){var l=0,s=t.length,d=null==r;if("object"===k(r))for(l in n=!0,r)U(t,e,l,r[l],!0,i,a);else if(void 0!==o&&(n=!0,b(o)||(a=!0),d&&(a?(e.call(t,o),e=null):(d=e,e=function(t,e,r){return d.call(E(t),r)})),e))for(;l1,null,!0)},removeData:function(t){return this.each((function(){J.remove(this,t)}))}}),E.extend({queue:function(t,e,r){var o;if(t)return e=(e||"fx")+"queue",o=G.get(t,e),r&&(!o||Array.isArray(r)?o=G.access(t,e,E.makeArray(r)):o.push(r)),o||[]},dequeue:function(t,e){e=e||"fx";var r=E.queue(t,e),o=r.length,n=r.shift(),i=E._queueHooks(t,e);"inprogress"===n&&(n=r.shift(),o--),n&&("fx"===e&&r.unshift("inprogress"),delete i.stop,n.call(t,(function(){E.dequeue(t,e)}),i)),!o&&i&&i.empty.fire()},_queueHooks:function(t,e){var r=e+"queueHooks";return G.get(t,r)||G.access(t,r,{empty:E.Callbacks("once memory").add((function(){G.remove(t,[e+"queue",r])}))})}}),E.fn.extend({queue:function(t,e){var r=2;return"string"!=typeof t&&(e=t,t="fx",r--),arguments.length\x20\t\r\n\f]*)/i,bt=/^$|^module$|\/(?:java|ecma)script/i;ft=v.createDocumentFragment().appendChild(v.createElement("div")),(gt=v.createElement("input")).setAttribute("type","radio"),gt.setAttribute("checked","checked"),gt.setAttribute("name","t"),ft.appendChild(gt),h.checkClone=ft.cloneNode(!0).cloneNode(!0).lastChild.checked,ft.innerHTML="",h.noCloneChecked=!!ft.cloneNode(!0).lastChild.defaultValue,ft.innerHTML="",h.option=!!ft.lastChild;var xt={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function vt(t,e){var r;return r=void 0!==t.getElementsByTagName?t.getElementsByTagName(e||"*"):void 0!==t.querySelectorAll?t.querySelectorAll(e||"*"):[],void 0===e||e&&L(t,e)?E.merge([t],r):r}function wt(t,e){for(var r=0,o=t.length;r",""]);var yt=/<|&#?\w+;/;function kt(t,e,r,o,n){for(var i,a,l,s,d,c,m=e.createDocumentFragment(),p=[],f=0,g=t.length;f-1)n&&n.push(i);else if(d=lt(i),a=vt(m.appendChild(i),"script"),d&&wt(a),r)for(c=0;i=a[c++];)bt.test(i.type||"")&&r.push(i);return m}var _t=/^([^.]*)(?:\.(.+)|)/;function Et(){return!0}function Ct(){return!1}function Tt(t,e){return t===function(){try{return v.activeElement}catch(t){}}()==("focus"===e)}function At(t,e,r,o,n,i){var a,l;if("object"==typeof e){for(l in"string"!=typeof r&&(o=o||r,r=void 0),e)At(t,l,r,o,e[l],i);return t}if(null==o&&null==n?(n=r,o=r=void 0):null==n&&("string"==typeof r?(n=o,o=void 0):(n=o,o=r,r=void 0)),!1===n)n=Ct;else if(!n)return t;return 1===i&&(a=n,(n=function(t){return E().off(t),a.apply(this,arguments)}).guid=a.guid||(a.guid=E.guid++)),t.each((function(){E.event.add(this,e,n,o,r)}))}function St(t,e,r){r?(G.set(t,e,!1),E.event.add(t,e,{namespace:!1,handler:function(t){var o,n,i=G.get(this,e);if(1&t.isTrigger&&this[e]){if(i.length)(E.event.special[e]||{}).delegateType&&t.stopPropagation();else if(i=l.call(arguments),G.set(this,e,i),o=r(this,e),this[e](),i!==(n=G.get(this,e))||o?G.set(this,e,!1):n={},i!==n)return t.stopImmediatePropagation(),t.preventDefault(),n&&n.value}else i.length&&(G.set(this,e,{value:E.event.trigger(E.extend(i[0],E.Event.prototype),i.slice(1),this)}),t.stopImmediatePropagation())}})):void 0===G.get(t,e)&&E.event.add(t,e,Et)}E.event={global:{},add:function(t,e,r,o,n){var i,a,l,s,d,c,m,p,f,g,u,h=G.get(t);if(Q(t))for(r.handler&&(r=(i=r).handler,n=i.selector),n&&E.find.matchesSelector(at,n),r.guid||(r.guid=E.guid++),(s=h.events)||(s=h.events=Object.create(null)),(a=h.handle)||(a=h.handle=function(e){return void 0!==E&&E.event.triggered!==e.type?E.event.dispatch.apply(t,arguments):void 0}),d=(e=(e||"").match(H)||[""]).length;d--;)f=u=(l=_t.exec(e[d])||[])[1],g=(l[2]||"").split(".").sort(),f&&(m=E.event.special[f]||{},f=(n?m.delegateType:m.bindType)||f,m=E.event.special[f]||{},c=E.extend({type:f,origType:u,data:o,handler:r,guid:r.guid,selector:n,needsContext:n&&E.expr.match.needsContext.test(n),namespace:g.join(".")},i),(p=s[f])||((p=s[f]=[]).delegateCount=0,m.setup&&!1!==m.setup.call(t,o,g,a)||t.addEventListener&&t.addEventListener(f,a)),m.add&&(m.add.call(t,c),c.handler.guid||(c.handler.guid=r.guid)),n?p.splice(p.delegateCount++,0,c):p.push(c),E.event.global[f]=!0)},remove:function(t,e,r,o,n){var i,a,l,s,d,c,m,p,f,g,u,h=G.hasData(t)&&G.get(t);if(h&&(s=h.events)){for(d=(e=(e||"").match(H)||[""]).length;d--;)if(f=u=(l=_t.exec(e[d])||[])[1],g=(l[2]||"").split(".").sort(),f){for(m=E.event.special[f]||{},p=s[f=(o?m.delegateType:m.bindType)||f]||[],l=l[2]&&new RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=i=p.length;i--;)c=p[i],!n&&u!==c.origType||r&&r.guid!==c.guid||l&&!l.test(c.namespace)||o&&o!==c.selector&&("**"!==o||!c.selector)||(p.splice(i,1),c.selector&&p.delegateCount--,m.remove&&m.remove.call(t,c));a&&!p.length&&(m.teardown&&!1!==m.teardown.call(t,g,h.handle)||E.removeEvent(t,f,h.handle),delete s[f])}else for(f in s)E.event.remove(t,f+e[d],r,o,!0);E.isEmptyObject(s)&&G.remove(t,"handle events")}},dispatch:function(t){var e,r,o,n,i,a,l=new Array(arguments.length),s=E.event.fix(t),d=(G.get(this,"events")||Object.create(null))[s.type]||[],c=E.event.special[s.type]||{};for(l[0]=s,e=1;e=1))for(;d!==this;d=d.parentNode||this)if(1===d.nodeType&&("click"!==t.type||!0!==d.disabled)){for(i=[],a={},r=0;r-1:E.find(n,this,null,[d]).length),a[n]&&i.push(o);i.length&&l.push({elem:d,handlers:i})}return d=this,s\s*$/g;function jt(t,e){return L(t,"table")&&L(11!==e.nodeType?e:e.firstChild,"tr")&&E(t).children("tbody")[0]||t}function Ot(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function Dt(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function It(t,e){var r,o,n,i,a,l;if(1===e.nodeType){if(G.hasData(t)&&(l=G.get(t).events))for(n in G.remove(e,"handle events"),l)for(r=0,o=l[n].length;r1&&"string"==typeof g&&!h.checkClone&&Lt.test(g))return t.each((function(n){var i=t.eq(n);u&&(e[0]=g.call(this,n,i.html())),Pt(i,e,r,o)}));if(p&&(i=(n=kt(e,t[0].ownerDocument,!1,t,o)).firstChild,1===n.childNodes.length&&(n=i),i||o)){for(l=(a=E.map(vt(n,"script"),Ot)).length;m0&&wt(a,!s&&vt(t,"script")),l},cleanData:function(t){for(var e,r,o,n=E.event.special,i=0;void 0!==(r=t[i]);i++)if(Q(r)){if(e=r[G.expando]){if(e.events)for(o in e.events)n[o]?E.event.remove(r,o):E.removeEvent(r,o,e.handle);r[G.expando]=void 0}r[J.expando]&&(r[J.expando]=void 0)}}}),E.fn.extend({detach:function(t){return Ht(this,t,!0)},remove:function(t){return Ht(this,t)},text:function(t){return U(this,(function(t){return void 0===t?E.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)}))}),null,t,arguments.length)},append:function(){return Pt(this,arguments,(function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||jt(this,t).appendChild(t)}))},prepend:function(){return Pt(this,arguments,(function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=jt(this,t);e.insertBefore(t,e.firstChild)}}))},before:function(){return Pt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this)}))},after:function(){return Pt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)}))},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(E.cleanData(vt(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map((function(){return E.clone(this,t,e)}))},html:function(t){return U(this,(function(t){var e=this[0]||{},r=0,o=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!zt.test(t)&&!xt[(ht.exec(t)||["",""])[1].toLowerCase()]){t=E.htmlPrefilter(t);try{for(;r=0&&(s+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-i-s-l-.5))||0),s}function ee(t,e,r){var o=Bt(t),n=(!h.boxSizingReliable()||r)&&"border-box"===E.css(t,"boxSizing",!1,o),i=n,a=Wt(t,e,o),l="offset"+e[0].toUpperCase()+e.slice(1);if(Rt.test(a)){if(!r)return a;a="auto"}return(!h.boxSizingReliable()&&n||!h.reliableTrDimensions()&&L(t,"tr")||"auto"===a||!parseFloat(a)&&"inline"===E.css(t,"display",!1,o))&&t.getClientRects().length&&(n="border-box"===E.css(t,"boxSizing",!1,o),(i=l in t)&&(a=t[l])),(a=parseFloat(a)||0)+te(t,e,r||(n?"border":"content"),i,o,a)+"px"}E.extend({cssHooks:{opacity:{get:function(t,e){if(e){var r=Wt(t,"opacity");return""===r?"1":r}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(t,e,r,o){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var n,i,a,l=K(e),s=Qt.test(e),d=t.style;if(s||(e=Yt(l)),a=E.cssHooks[e]||E.cssHooks[l],void 0===r)return a&&"get"in a&&void 0!==(n=a.get(t,!1,o))?n:d[e];"string"==(i=typeof r)&&(n=nt.exec(r))&&n[1]&&(r=function(t,e,r,o){var n,i,a=20,l=function(){return E.css(t,e,"")},s=l(),d=r&&r[3]||(E.cssNumber[e]?"":"px"),c=t.nodeType&&(E.cssNumber[e]||"px"!==d&&+s)&&nt.exec(E.css(t,e));if(c&&c[3]!==d){for(s/=2,d=d||c[3],c=+s||1;a--;)E.style(t,e,c+d),(1-i)*(1-(i=l()/s||.5))<=0&&(a=0),c/=i;c*=2,E.style(t,e,c+d),r=r||[]}return r&&(c=+c||+s||0,n=r[1]?c+(r[1]+1)*r[2]:+r[2]),n}(t,e,n),i="number"),null!=r&&r==r&&("number"!==i||s||(r+=n&&n[3]||(E.cssNumber[l]?"":"px")),h.clearCloneStyle||""!==r||0!==e.indexOf("background")||(d[e]="inherit"),a&&"set"in a&&void 0===(r=a.set(t,r,o))||(s?d.setProperty(e,r):d[e]=r))}},css:function(t,e,r,o){var n,i,a,l=K(e);return Qt.test(e)||(e=Yt(l)),(a=E.cssHooks[e]||E.cssHooks[l])&&"get"in a&&(n=a.get(t,!0,r)),void 0===n&&(n=Wt(t,e,o)),"normal"===n&&e in Gt&&(n=Gt[e]),""===r||r?(i=parseFloat(n),!0===r||isFinite(i)?i||0:n):n}}),E.each(["height","width"],(function(t,e){E.cssHooks[e]={get:function(t,r,o){if(r)return!Kt.test(E.css(t,"display"))||t.getClientRects().length&&t.getBoundingClientRect().width?ee(t,e,o):qt(t,Zt,(function(){return ee(t,e,o)}))},set:function(t,r,o){var n,i=Bt(t),a=!h.scrollboxSize()&&"absolute"===i.position,l=(a||o)&&"border-box"===E.css(t,"boxSizing",!1,i),s=o?te(t,e,o,l,i):0;return l&&a&&(s-=Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-parseFloat(i[e])-te(t,e,"border",!1,i)-.5)),s&&(n=nt.exec(r))&&"px"!==(n[3]||"px")&&(t.style[e]=r,r=E.css(t,e)),Jt(0,r,s)}}})),E.cssHooks.marginLeft=Ft(h.reliableMarginLeft,(function(t,e){if(e)return(parseFloat(Wt(t,"marginLeft"))||t.getBoundingClientRect().left-qt(t,{marginLeft:0},(function(){return t.getBoundingClientRect().left})))+"px"})),E.each({margin:"",padding:"",border:"Width"},(function(t,e){E.cssHooks[t+e]={expand:function(r){for(var o=0,n={},i="string"==typeof r?r.split(" "):[r];o<4;o++)n[t+it[o]+e]=i[o]||i[o-2]||i[0];return n}},"margin"!==t&&(E.cssHooks[t+e].set=Jt)})),E.fn.extend({css:function(t,e){return U(this,(function(t,e,r){var o,n,i={},a=0;if(Array.isArray(e)){for(o=Bt(t),n=e.length;a1)}}),E.fn.delay=function(t,e){return t=E.fx&&E.fx.speeds[t]||t,e=e||"fx",this.queue(e,(function(e,r){var n=o.setTimeout(e,t);r.stop=function(){o.clearTimeout(n)}}))},function(){var t=v.createElement("input"),e=v.createElement("select").appendChild(v.createElement("option"));t.type="checkbox",h.checkOn=""!==t.value,h.optSelected=e.selected,(t=v.createElement("input")).value="t",t.type="radio",h.radioValue="t"===t.value}();var re,oe=E.expr.attrHandle;E.fn.extend({attr:function(t,e){return U(this,E.attr,t,e,arguments.length>1)},removeAttr:function(t){return this.each((function(){E.removeAttr(this,t)}))}}),E.extend({attr:function(t,e,r){var o,n,i=t.nodeType;if(3!==i&&8!==i&&2!==i)return void 0===t.getAttribute?E.prop(t,e,r):(1===i&&E.isXMLDoc(t)||(n=E.attrHooks[e.toLowerCase()]||(E.expr.match.bool.test(e)?re:void 0)),void 0!==r?null===r?void E.removeAttr(t,e):n&&"set"in n&&void 0!==(o=n.set(t,r,e))?o:(t.setAttribute(e,r+""),r):n&&"get"in n&&null!==(o=n.get(t,e))?o:null==(o=E.find.attr(t,e))?void 0:o)},attrHooks:{type:{set:function(t,e){if(!h.radioValue&&"radio"===e&&L(t,"input")){var r=t.value;return t.setAttribute("type",e),r&&(t.value=r),e}}}},removeAttr:function(t,e){var r,o=0,n=e&&e.match(H);if(n&&1===t.nodeType)for(;r=n[o++];)t.removeAttribute(r)}}),re={set:function(t,e,r){return!1===e?E.removeAttr(t,r):t.setAttribute(r,r),r}},E.each(E.expr.match.bool.source.match(/\w+/g),(function(t,e){var r=oe[e]||E.find.attr;oe[e]=function(t,e,o){var n,i,a=e.toLowerCase();return o||(i=oe[a],oe[a]=n,n=null!=r(t,e,o)?a:null,oe[a]=i),n}}));var ne=/^(?:input|select|textarea|button)$/i,ie=/^(?:a|area)$/i;function ae(t){return(t.match(H)||[]).join(" ")}function le(t){return t.getAttribute&&t.getAttribute("class")||""}function se(t){return Array.isArray(t)?t:"string"==typeof t&&t.match(H)||[]}E.fn.extend({prop:function(t,e){return U(this,E.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each((function(){delete this[E.propFix[t]||t]}))}}),E.extend({prop:function(t,e,r){var o,n,i=t.nodeType;if(3!==i&&8!==i&&2!==i)return 1===i&&E.isXMLDoc(t)||(e=E.propFix[e]||e,n=E.propHooks[e]),void 0!==r?n&&"set"in n&&void 0!==(o=n.set(t,r,e))?o:t[e]=r:n&&"get"in n&&null!==(o=n.get(t,e))?o:t[e]},propHooks:{tabIndex:{get:function(t){var e=E.find.attr(t,"tabindex");return e?parseInt(e,10):ne.test(t.nodeName)||ie.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),h.optSelected||(E.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),E.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],(function(){E.propFix[this.toLowerCase()]=this})),E.fn.extend({addClass:function(t){var e,r,o,n,i,a,l,s=0;if(b(t))return this.each((function(e){E(this).addClass(t.call(this,e,le(this)))}));if((e=se(t)).length)for(;r=this[s++];)if(n=le(r),o=1===r.nodeType&&" "+ae(n)+" "){for(a=0;i=e[a++];)o.indexOf(" "+i+" ")<0&&(o+=i+" ");n!==(l=ae(o))&&r.setAttribute("class",l)}return this},removeClass:function(t){var e,r,o,n,i,a,l,s=0;if(b(t))return this.each((function(e){E(this).removeClass(t.call(this,e,le(this)))}));if(!arguments.length)return this.attr("class","");if((e=se(t)).length)for(;r=this[s++];)if(n=le(r),o=1===r.nodeType&&" "+ae(n)+" "){for(a=0;i=e[a++];)for(;o.indexOf(" "+i+" ")>-1;)o=o.replace(" "+i+" "," ");n!==(l=ae(o))&&r.setAttribute("class",l)}return this},toggleClass:function(t,e){var r=typeof t,o="string"===r||Array.isArray(t);return"boolean"==typeof e&&o?e?this.addClass(t):this.removeClass(t):b(t)?this.each((function(r){E(this).toggleClass(t.call(this,r,le(this),e),e)})):this.each((function(){var e,n,i,a;if(o)for(n=0,i=E(this),a=se(t);e=a[n++];)i.hasClass(e)?i.removeClass(e):i.addClass(e);else void 0!==t&&"boolean"!==r||((e=le(this))&&G.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":G.get(this,"__className__")||""))}))},hasClass:function(t){var e,r,o=0;for(e=" "+t+" ";r=this[o++];)if(1===r.nodeType&&(" "+ae(le(r))+" ").indexOf(e)>-1)return!0;return!1}});var de=/\r/g;E.fn.extend({val:function(t){var e,r,o,n=this[0];return arguments.length?(o=b(t),this.each((function(r){var n;1===this.nodeType&&(null==(n=o?t.call(this,r,E(this).val()):t)?n="":"number"==typeof n?n+="":Array.isArray(n)&&(n=E.map(n,(function(t){return null==t?"":t+""}))),(e=E.valHooks[this.type]||E.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,n,"value")||(this.value=n))}))):n?(e=E.valHooks[n.type]||E.valHooks[n.nodeName.toLowerCase()])&&"get"in e&&void 0!==(r=e.get(n,"value"))?r:"string"==typeof(r=n.value)?r.replace(de,""):null==r?"":r:void 0}}),E.extend({valHooks:{option:{get:function(t){var e=E.find.attr(t,"value");return null!=e?e:ae(E.text(t))}},select:{get:function(t){var e,r,o,n=t.options,i=t.selectedIndex,a="select-one"===t.type,l=a?null:[],s=a?i+1:n.length;for(o=i<0?s:a?i:0;o-1)&&(r=!0);return r||(t.selectedIndex=-1),i}}}}),E.each(["radio","checkbox"],(function(){E.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=E.inArray(E(t).val(),e)>-1}},h.checkOn||(E.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})})),h.focusin="onfocusin"in o;var ce=/^(?:focusinfocus|focusoutblur)$/,me=function(t){t.stopPropagation()};E.extend(E.event,{trigger:function(t,e,r,n){var i,a,l,s,d,c,m,p,g=[r||v],u=f.call(t,"type")?t.type:t,h=f.call(t,"namespace")?t.namespace.split("."):[];if(a=p=l=r=r||v,3!==r.nodeType&&8!==r.nodeType&&!ce.test(u+E.event.triggered)&&(u.indexOf(".")>-1&&(h=u.split("."),u=h.shift(),h.sort()),d=u.indexOf(":")<0&&"on"+u,(t=t[E.expando]?t:new E.Event(u,"object"==typeof t&&t)).isTrigger=n?2:3,t.namespace=h.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),e=null==e?[t]:E.makeArray(e,[t]),m=E.event.special[u]||{},n||!m.trigger||!1!==m.trigger.apply(r,e))){if(!n&&!m.noBubble&&!x(r)){for(s=m.delegateType||u,ce.test(s+u)||(a=a.parentNode);a;a=a.parentNode)g.push(a),l=a;l===(r.ownerDocument||v)&&g.push(l.defaultView||l.parentWindow||o)}for(i=0;(a=g[i++])&&!t.isPropagationStopped();)p=a,t.type=i>1?s:m.bindType||u,(c=(G.get(a,"events")||Object.create(null))[t.type]&&G.get(a,"handle"))&&c.apply(a,e),(c=d&&a[d])&&c.apply&&Q(a)&&(t.result=c.apply(a,e),!1===t.result&&t.preventDefault());return t.type=u,n||t.isDefaultPrevented()||m._default&&!1!==m._default.apply(g.pop(),e)||!Q(r)||d&&b(r[u])&&!x(r)&&((l=r[d])&&(r[d]=null),E.event.triggered=u,t.isPropagationStopped()&&p.addEventListener(u,me),r[u](),t.isPropagationStopped()&&p.removeEventListener(u,me),E.event.triggered=void 0,l&&(r[d]=l)),t.result}},simulate:function(t,e,r){var o=E.extend(new E.Event,r,{type:t,isSimulated:!0});E.event.trigger(o,null,e)}}),E.fn.extend({trigger:function(t,e){return this.each((function(){E.event.trigger(t,e,this)}))},triggerHandler:function(t,e){var r=this[0];if(r)return E.event.trigger(t,e,r,!0)}}),h.focusin||E.each({focus:"focusin",blur:"focusout"},(function(t,e){var r=function(t){E.event.simulate(e,t.target,E.event.fix(t))};E.event.special[e]={setup:function(){var o=this.ownerDocument||this.document||this,n=G.access(o,e);n||o.addEventListener(t,r,!0),G.access(o,e,(n||0)+1)},teardown:function(){var o=this.ownerDocument||this.document||this,n=G.access(o,e)-1;n?G.access(o,e,n):(o.removeEventListener(t,r,!0),G.remove(o,e))}}})),E.parseXML=function(t){var e,r;if(!t||"string"!=typeof t)return null;try{e=(new o.DOMParser).parseFromString(t,"text/xml")}catch(t){}return r=e&&e.getElementsByTagName("parsererror")[0],e&&!r||E.error("Invalid XML: "+(r?E.map(r.childNodes,(function(t){return t.textContent})).join("\n"):t)),e};var pe,fe=/\[\]$/,ge=/\r?\n/g,ue=/^(?:submit|button|image|reset|file)$/i,he=/^(?:input|select|textarea|keygen)/i;function be(t,e,r,o){var n;if(Array.isArray(e))E.each(e,(function(e,n){r||fe.test(t)?o(t,n):be(t+"["+("object"==typeof n&&null!=n?e:"")+"]",n,r,o)}));else if(r||"object"!==k(e))o(t,e);else for(n in e)be(t+"["+n+"]",e[n],r,o)}E.param=function(t,e){var r,o=[],n=function(t,e){var r=b(e)?e():e;o[o.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==r?"":r)};if(null==t)return"";if(Array.isArray(t)||t.jquery&&!E.isPlainObject(t))E.each(t,(function(){n(this.name,this.value)}));else for(r in t)be(r,t[r],e,n);return o.join("&")},E.fn.extend({serialize:function(){return E.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var t=E.prop(this,"elements");return t?E.makeArray(t):this})).filter((function(){var t=this.type;return this.name&&!E(this).is(":disabled")&&he.test(this.nodeName)&&!ue.test(t)&&(this.checked||!ut.test(t))})).map((function(t,e){var r=E(this).val();return null==r?null:Array.isArray(r)?E.map(r,(function(t){return{name:e.name,value:t.replace(ge,"\r\n")}})):{name:e.name,value:r.replace(ge,"\r\n")}})).get()}}),E.fn.extend({wrapAll:function(t){var e;return this[0]&&(b(t)&&(t=t.call(this[0])),e=E(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map((function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t})).append(this)),this},wrapInner:function(t){return b(t)?this.each((function(e){E(this).wrapInner(t.call(this,e))})):this.each((function(){var e=E(this),r=e.contents();r.length?r.wrapAll(t):e.append(t)}))},wrap:function(t){var e=b(t);return this.each((function(r){E(this).wrapAll(e?t.call(this,r):t)}))},unwrap:function(t){return this.parent(t).not("body").each((function(){E(this).replaceWith(this.childNodes)})),this}}),E.expr.pseudos.hidden=function(t){return!E.expr.pseudos.visible(t)},E.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},h.createHTMLDocument=((pe=v.implementation.createHTMLDocument("").body).innerHTML="
",2===pe.childNodes.length),E.parseHTML=function(t,e,r){return"string"!=typeof t?[]:("boolean"==typeof e&&(r=e,e=!1),e||(h.createHTMLDocument?((o=(e=v.implementation.createHTMLDocument("")).createElement("base")).href=v.location.href,e.head.appendChild(o)):e=v),i=!r&&[],(n=N.exec(t))?[e.createElement(n[1])]:(n=kt([t],e,i),i&&i.length&&E(i).remove(),E.merge([],n.childNodes)));var o,n,i},E.offset={setOffset:function(t,e,r){var o,n,i,a,l,s,d=E.css(t,"position"),c=E(t),m={};"static"===d&&(t.style.position="relative"),l=c.offset(),i=E.css(t,"top"),s=E.css(t,"left"),("absolute"===d||"fixed"===d)&&(i+s).indexOf("auto")>-1?(a=(o=c.position()).top,n=o.left):(a=parseFloat(i)||0,n=parseFloat(s)||0),b(e)&&(e=e.call(t,r,E.extend({},l))),null!=e.top&&(m.top=e.top-l.top+a),null!=e.left&&(m.left=e.left-l.left+n),"using"in e?e.using.call(t,m):c.css(m)}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each((function(e){E.offset.setOffset(this,t,e)}));var e,r,o=this[0];return o?o.getClientRects().length?(e=o.getBoundingClientRect(),r=o.ownerDocument.defaultView,{top:e.top+r.pageYOffset,left:e.left+r.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var t,e,r,o=this[0],n={top:0,left:0};if("fixed"===E.css(o,"position"))e=o.getBoundingClientRect();else{for(e=this.offset(),r=o.ownerDocument,t=o.offsetParent||r.documentElement;t&&(t===r.body||t===r.documentElement)&&"static"===E.css(t,"position");)t=t.parentNode;t&&t!==o&&1===t.nodeType&&((n=E(t).offset()).top+=E.css(t,"borderTopWidth",!0),n.left+=E.css(t,"borderLeftWidth",!0))}return{top:e.top-n.top-E.css(o,"marginTop",!0),left:e.left-n.left-E.css(o,"marginLeft",!0)}}},offsetParent:function(){return this.map((function(){for(var t=this.offsetParent;t&&"static"===E.css(t,"position");)t=t.offsetParent;return t||at}))}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},(function(t,e){var r="pageYOffset"===e;E.fn[t]=function(o){return U(this,(function(t,o,n){var i;if(x(t)?i=t:9===t.nodeType&&(i=t.defaultView),void 0===n)return i?i[e]:t[o];i?i.scrollTo(r?i.pageXOffset:n,r?n:i.pageYOffset):t[o]=n}),t,o,arguments.length)}})),E.each(["top","left"],(function(t,e){E.cssHooks[e]=Ft(h.pixelPosition,(function(t,r){if(r)return r=Wt(t,e),Rt.test(r)?E(t).position()[e]+"px":r}))})),E.each({Height:"height",Width:"width"},(function(t,e){E.each({padding:"inner"+t,content:e,"":"outer"+t},(function(r,o){E.fn[o]=function(n,i){var a=arguments.length&&(r||"boolean"!=typeof n),l=r||(!0===n||!0===i?"margin":"border");return U(this,(function(e,r,n){var i;return x(e)?0===o.indexOf("outer")?e["inner"+t]:e.document.documentElement["client"+t]:9===e.nodeType?(i=e.documentElement,Math.max(e.body["scroll"+t],i["scroll"+t],e.body["offset"+t],i["offset"+t],i["client"+t])):void 0===n?E.css(e,r,l):E.style(e,r,n,l)}),e,a?n:void 0,a)}}))})),E.fn.extend({bind:function(t,e,r){return this.on(t,null,e,r)},unbind:function(t,e){return this.off(t,null,e)},delegate:function(t,e,r,o){return this.on(e,t,r,o)},undelegate:function(t,e,r){return 1===arguments.length?this.off(t,"**"):this.off(e,t||"**",r)},hover:function(t,e){return this.mouseenter(t).mouseleave(e||t)}}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),(function(t,e){E.fn[e]=function(t,r){return arguments.length>0?this.on(e,null,t,r):this.trigger(e)}}));var xe=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;E.proxy=function(t,e){var r,o,n;if("string"==typeof e&&(r=t[e],e=t,t=r),b(t))return o=l.call(arguments,2),(n=function(){return t.apply(e||this,o.concat(l.call(arguments)))}).guid=t.guid=t.guid||E.guid++,n},E.holdReady=function(t){t?E.readyWait++:E.ready(!0)},E.isArray=Array.isArray,E.parseJSON=JSON.parse,E.nodeName=L,E.isFunction=b,E.isWindow=x,E.camelCase=K,E.type=k,E.now=Date.now,E.isNumeric=function(t){var e=E.type(t);return("number"===e||"string"===e)&&!isNaN(t-parseFloat(t))},E.trim=function(t){return null==t?"":(t+"").replace(xe,"")},void 0===(r=function(){return E}.apply(e,[]))||(t.exports=r);var ve=o.jQuery,we=o.$;return E.noConflict=function(t){return o.$===E&&(o.$=we),t&&o.jQuery===E&&(o.jQuery=ve),E},void 0===n&&(o.jQuery=o.$=E),E}))},379:t=>{"use strict";var e=[];function r(t){for(var r=-1,o=0;o{"use strict";var e={};t.exports=function(t,r){var o=function(t){if(void 0===e[t]){var r=document.querySelector(t);if(window.HTMLIFrameElement&&r instanceof window.HTMLIFrameElement)try{r=r.contentDocument.head}catch(t){r=null}e[t]=r}return e[t]}(t);if(!o)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");o.appendChild(r)}},216:t=>{"use strict";t.exports=function(t){var e=document.createElement("style");return t.setAttributes(e,t.attributes),t.insert(e),e}},565:(t,e,r)=>{"use strict";t.exports=function(t){var e=r.nc;e&&t.setAttribute("nonce",e)}},795:t=>{"use strict";t.exports=function(t){var e=t.insertStyleElement(t);return{update:function(r){!function(t,e,r){var o=r.css,n=r.media,i=r.sourceMap;n?t.setAttribute("media",n):t.removeAttribute("media"),i&&"undefined"!=typeof btoa&&(o+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(i))))," */")),e.styleTagTransform(o,t)}(e,t,r)},remove:function(){!function(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t)}(e)}}}},589:t=>{"use strict";t.exports=function(t,e){if(e.styleSheet)e.styleSheet.cssText=t;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(t))}}},204:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%27-4 -4 8 8%27%3e%3ccircle r=%272%27 fill=%27%23fff%27/%3e%3c/svg%3e"},609:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%27-4 -4 8 8%27%3e%3ccircle r=%273%27 fill=%27%2386b7fe%27/%3e%3c/svg%3e"},469:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%27-4 -4 8 8%27%3e%3ccircle r=%273%27 fill=%27%23fff%27/%3e%3c/svg%3e"},486:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%27-4 -4 8 8%27%3e%3ccircle r=%273%27 fill=%27rgba%280, 0, 0, 0.25%29%27/%3e%3c/svg%3e"},144:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 12 12%27 width=%2712%27 height=%2712%27 fill=%27none%27 stroke=%27%23dc3545%27%3e%3ccircle cx=%276%27 cy=%276%27 r=%274.5%27/%3e%3cpath stroke-linejoin=%27round%27 d=%27M5.8 3.6h.4L6 6.5z%27/%3e%3ccircle cx=%276%27 cy=%278.2%27 r=%27.6%27 fill=%27%23dc3545%27 stroke=%27none%27/%3e%3c/svg%3e"},175:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27 fill=%27%23000%27%3e%3cpath d=%27M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z%27/%3e%3c/svg%3e"},740:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27 fill=%27%230c63e4%27%3e%3cpath fill-rule=%27evenodd%27 d=%27M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z%27/%3e%3c/svg%3e"},460:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27 fill=%27%23212529%27%3e%3cpath fill-rule=%27evenodd%27 d=%27M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z%27/%3e%3c/svg%3e"},647:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27 fill=%27%23fff%27%3e%3cpath d=%27M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z%27/%3e%3c/svg%3e"},692:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27 fill=%27%23fff%27%3e%3cpath d=%27M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z%27/%3e%3c/svg%3e"},214:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%23343a40%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27M2 5l6 6 6-6%27/%3e%3c/svg%3e"},931:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 20 20%27%3e%3cpath fill=%27none%27 stroke=%27%23fff%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%273%27 d=%27M6 10h8%27/%3e%3c/svg%3e"},349:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 20 20%27%3e%3cpath fill=%27none%27 stroke=%27%23fff%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%273%27 d=%27M6 10l3 3l6-6%27/%3e%3c/svg%3e"},217:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 30 30%27%3e%3cpath stroke=%27rgba%280, 0, 0, 0.55%29%27 stroke-linecap=%27round%27 stroke-miterlimit=%2710%27 stroke-width=%272%27 d=%27M4 7h22M4 15h22M4 23h22%27/%3e%3c/svg%3e"},956:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 30 30%27%3e%3cpath stroke=%27rgba%28255, 255, 255, 0.55%29%27 stroke-linecap=%27round%27 stroke-miterlimit=%2710%27 stroke-width=%272%27 d=%27M4 7h22M4 15h22M4 23h22%27/%3e%3c/svg%3e"},819:t=>{"use strict";t.exports="data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 8 8%27%3e%3cpath fill=%27%23198754%27 d=%27M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z%27/%3e%3c/svg%3e"}},e={};function r(o){var n=e[o];if(void 0!==n)return n.exports;var i=e[o]={id:o,exports:{}};return t[o].call(i.exports,i,i.exports,r),i.exports}r.m=t,r.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return r.d(e,{a:e}),e},r.d=(t,e)=>{for(var o in e)r.o(e,o)&&!r.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.b=document.baseURI||self.location.href,(()=>{"use strict";var t=r(379),e=r.n(t),o=r(795),n=r.n(o),i=r(569),a=r.n(i),l=r(565),s=r.n(l),d=r(216),c=r.n(d),m=r(589),p=r.n(m),f=r(666),g={};g.styleTagTransform=p(),g.setAttributes=s(),g.insert=a().bind(null,"head"),g.domAPI=n(),g.insertStyleElement=c(),e()(f.Z,g),f.Z&&f.Z.locals&&f.Z.locals;var u=r(426),h={};h.styleTagTransform=p(),h.setAttributes=s(),h.insert=a().bind(null,"head"),h.domAPI=n(),h.insertStyleElement=c(),e()(u.Z,h),u.Z&&u.Z.locals&&u.Z.locals,window.$=r(70),window.bootstrap=r(169),window.too_old=function(){$("#refresh_badge").removeClass("d-none"),$("#refresh_status").addClass("d-inline"),$("#summary").removeClass("border-success border-danger"),$("#summary").addClass("border-warning")},window.update_age=function(t){const e=parseInt(Date.now()/1e3)-t.timestamp;var r;r=e<10?"just now":`${e} seconds ago`;const o=`Updated ${r} by ${t.host}\n (${t.version})`;$("#updated").html(o),$("#updatedfooter").html(o)}})()})(); simplemonitor-1.13.0/simplemonitor/html/dist/main.bundle.js.LICENSE.txt000066400000000000000000000016271464501162400257470ustar00rootroot00000000000000/*! * Bootstrap v5.1.0 (https://getbootstrap.com/) * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ /*! * Sizzle CSS Selector Engine v2.3.6 * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://js.foundation/ * * Date: 2021-02-16 */ /*! * jQuery JavaScript Library v3.6.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector * https://jquery.com/ * * Includes Sizzle.js * https://sizzlejs.com/ * * Copyright OpenJS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * * Date: 2021-03-02T17:08Z */ simplemonitor-1.13.0/simplemonitor/html/dist/maps.bundle.js000066400000000000000000004762451464501162400241000ustar00rootroot00000000000000/*! For license information please see maps.bundle.js.LICENSE.txt */ (()=>{var t={984:(t,e,i)=>{"use strict";i.d(e,{Z:()=>_});var n=i(645),o=i.n(n),r=i(667),s=i.n(r),a=new URL(i(803),i.b),h=new URL(i(134),i.b),l=new URL(i(94),i.b),u=o()((function(t){return t[1]})),c=s()(a),d=s()(h),p=s()(l);u.push([t.id,"/* required styles */\r\n\r\n.leaflet-pane,\r\n.leaflet-tile,\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow,\r\n.leaflet-tile-container,\r\n.leaflet-pane > svg,\r\n.leaflet-pane > canvas,\r\n.leaflet-zoom-box,\r\n.leaflet-image-layer,\r\n.leaflet-layer {\r\n\tposition: absolute;\r\n\tleft: 0;\r\n\ttop: 0;\r\n\t}\r\n.leaflet-container {\r\n\toverflow: hidden;\r\n\t}\r\n.leaflet-tile,\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow {\r\n\t-webkit-user-select: none;\r\n\t -moz-user-select: none;\r\n\t user-select: none;\r\n\t -webkit-user-drag: none;\r\n\t}\r\n/* Prevents IE11 from highlighting tiles in blue */\r\n.leaflet-tile::selection {\r\n\tbackground: transparent;\r\n}\r\n/* Safari renders non-retina tile on retina better with this, but Chrome is worse */\r\n.leaflet-safari .leaflet-tile {\r\n\timage-rendering: -webkit-optimize-contrast;\r\n\t}\r\n/* hack that prevents hw layers \"stretching\" when loading new tiles */\r\n.leaflet-safari .leaflet-tile-container {\r\n\twidth: 1600px;\r\n\theight: 1600px;\r\n\t-webkit-transform-origin: 0 0;\r\n\t}\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow {\r\n\tdisplay: block;\r\n\t}\r\n/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */\r\n/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */\r\n.leaflet-container .leaflet-overlay-pane svg,\r\n.leaflet-container .leaflet-marker-pane img,\r\n.leaflet-container .leaflet-shadow-pane img,\r\n.leaflet-container .leaflet-tile-pane img,\r\n.leaflet-container img.leaflet-image-layer,\r\n.leaflet-container .leaflet-tile {\r\n\tmax-width: none !important;\r\n\tmax-height: none !important;\r\n\t}\r\n\r\n.leaflet-container.leaflet-touch-zoom {\r\n\t-ms-touch-action: pan-x pan-y;\r\n\ttouch-action: pan-x pan-y;\r\n\t}\r\n.leaflet-container.leaflet-touch-drag {\r\n\t-ms-touch-action: pinch-zoom;\r\n\t/* Fallback for FF which doesn't support pinch-zoom */\r\n\ttouch-action: none;\r\n\ttouch-action: pinch-zoom;\r\n}\r\n.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {\r\n\t-ms-touch-action: none;\r\n\ttouch-action: none;\r\n}\r\n.leaflet-container {\r\n\t-webkit-tap-highlight-color: transparent;\r\n}\r\n.leaflet-container a {\r\n\t-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);\r\n}\r\n.leaflet-tile {\r\n\tfilter: inherit;\r\n\tvisibility: hidden;\r\n\t}\r\n.leaflet-tile-loaded {\r\n\tvisibility: inherit;\r\n\t}\r\n.leaflet-zoom-box {\r\n\twidth: 0;\r\n\theight: 0;\r\n\t-moz-box-sizing: border-box;\r\n\t box-sizing: border-box;\r\n\tz-index: 800;\r\n\t}\r\n/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */\r\n.leaflet-overlay-pane svg {\r\n\t-moz-user-select: none;\r\n\t}\r\n\r\n.leaflet-pane { z-index: 400; }\r\n\r\n.leaflet-tile-pane { z-index: 200; }\r\n.leaflet-overlay-pane { z-index: 400; }\r\n.leaflet-shadow-pane { z-index: 500; }\r\n.leaflet-marker-pane { z-index: 600; }\r\n.leaflet-tooltip-pane { z-index: 650; }\r\n.leaflet-popup-pane { z-index: 700; }\r\n\r\n.leaflet-map-pane canvas { z-index: 100; }\r\n.leaflet-map-pane svg { z-index: 200; }\r\n\r\n.leaflet-vml-shape {\r\n\twidth: 1px;\r\n\theight: 1px;\r\n\t}\r\n.lvml {\r\n\tbehavior: url(#default#VML);\r\n\tdisplay: inline-block;\r\n\tposition: absolute;\r\n\t}\r\n\r\n\r\n/* control positioning */\r\n\r\n.leaflet-control {\r\n\tposition: relative;\r\n\tz-index: 800;\r\n\tpointer-events: visiblePainted; /* IE 9-10 doesn't have auto */\r\n\tpointer-events: auto;\r\n\t}\r\n.leaflet-top,\r\n.leaflet-bottom {\r\n\tposition: absolute;\r\n\tz-index: 1000;\r\n\tpointer-events: none;\r\n\t}\r\n.leaflet-top {\r\n\ttop: 0;\r\n\t}\r\n.leaflet-right {\r\n\tright: 0;\r\n\t}\r\n.leaflet-bottom {\r\n\tbottom: 0;\r\n\t}\r\n.leaflet-left {\r\n\tleft: 0;\r\n\t}\r\n.leaflet-control {\r\n\tfloat: left;\r\n\tclear: both;\r\n\t}\r\n.leaflet-right .leaflet-control {\r\n\tfloat: right;\r\n\t}\r\n.leaflet-top .leaflet-control {\r\n\tmargin-top: 10px;\r\n\t}\r\n.leaflet-bottom .leaflet-control {\r\n\tmargin-bottom: 10px;\r\n\t}\r\n.leaflet-left .leaflet-control {\r\n\tmargin-left: 10px;\r\n\t}\r\n.leaflet-right .leaflet-control {\r\n\tmargin-right: 10px;\r\n\t}\r\n\r\n\r\n/* zoom and fade animations */\r\n\r\n.leaflet-fade-anim .leaflet-tile {\r\n\twill-change: opacity;\r\n\t}\r\n.leaflet-fade-anim .leaflet-popup {\r\n\topacity: 0;\r\n\t-webkit-transition: opacity 0.2s linear;\r\n\t -moz-transition: opacity 0.2s linear;\r\n\t transition: opacity 0.2s linear;\r\n\t}\r\n.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {\r\n\topacity: 1;\r\n\t}\r\n.leaflet-zoom-animated {\r\n\t-webkit-transform-origin: 0 0;\r\n\t -ms-transform-origin: 0 0;\r\n\t transform-origin: 0 0;\r\n\t}\r\n.leaflet-zoom-anim .leaflet-zoom-animated {\r\n\twill-change: transform;\r\n\t}\r\n.leaflet-zoom-anim .leaflet-zoom-animated {\r\n\t-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);\r\n\t -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);\r\n\t transition: transform 0.25s cubic-bezier(0,0,0.25,1);\r\n\t}\r\n.leaflet-zoom-anim .leaflet-tile,\r\n.leaflet-pan-anim .leaflet-tile {\r\n\t-webkit-transition: none;\r\n\t -moz-transition: none;\r\n\t transition: none;\r\n\t}\r\n\r\n.leaflet-zoom-anim .leaflet-zoom-hide {\r\n\tvisibility: hidden;\r\n\t}\r\n\r\n\r\n/* cursors */\r\n\r\n.leaflet-interactive {\r\n\tcursor: pointer;\r\n\t}\r\n.leaflet-grab {\r\n\tcursor: -webkit-grab;\r\n\tcursor: -moz-grab;\r\n\tcursor: grab;\r\n\t}\r\n.leaflet-crosshair,\r\n.leaflet-crosshair .leaflet-interactive {\r\n\tcursor: crosshair;\r\n\t}\r\n.leaflet-popup-pane,\r\n.leaflet-control {\r\n\tcursor: auto;\r\n\t}\r\n.leaflet-dragging .leaflet-grab,\r\n.leaflet-dragging .leaflet-grab .leaflet-interactive,\r\n.leaflet-dragging .leaflet-marker-draggable {\r\n\tcursor: move;\r\n\tcursor: -webkit-grabbing;\r\n\tcursor: -moz-grabbing;\r\n\tcursor: grabbing;\r\n\t}\r\n\r\n/* marker & overlays interactivity */\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow,\r\n.leaflet-image-layer,\r\n.leaflet-pane > svg path,\r\n.leaflet-tile-container {\r\n\tpointer-events: none;\r\n\t}\r\n\r\n.leaflet-marker-icon.leaflet-interactive,\r\n.leaflet-image-layer.leaflet-interactive,\r\n.leaflet-pane > svg path.leaflet-interactive,\r\nsvg.leaflet-image-layer.leaflet-interactive path {\r\n\tpointer-events: visiblePainted; /* IE 9-10 doesn't have auto */\r\n\tpointer-events: auto;\r\n\t}\r\n\r\n/* visual tweaks */\r\n\r\n.leaflet-container {\r\n\tbackground: #ddd;\r\n\toutline: 0;\r\n\t}\r\n.leaflet-container a {\r\n\tcolor: #0078A8;\r\n\t}\r\n.leaflet-container a.leaflet-active {\r\n\toutline: 2px solid orange;\r\n\t}\r\n.leaflet-zoom-box {\r\n\tborder: 2px dotted #38f;\r\n\tbackground: rgba(255,255,255,0.5);\r\n\t}\r\n\r\n\r\n/* general typography */\r\n.leaflet-container {\r\n\tfont: 12px/1.5 \"Helvetica Neue\", Arial, Helvetica, sans-serif;\r\n\t}\r\n\r\n\r\n/* general toolbar styles */\r\n\r\n.leaflet-bar {\r\n\tbox-shadow: 0 1px 5px rgba(0,0,0,0.65);\r\n\tborder-radius: 4px;\r\n\t}\r\n.leaflet-bar a,\r\n.leaflet-bar a:hover {\r\n\tbackground-color: #fff;\r\n\tborder-bottom: 1px solid #ccc;\r\n\twidth: 26px;\r\n\theight: 26px;\r\n\tline-height: 26px;\r\n\tdisplay: block;\r\n\ttext-align: center;\r\n\ttext-decoration: none;\r\n\tcolor: black;\r\n\t}\r\n.leaflet-bar a,\r\n.leaflet-control-layers-toggle {\r\n\tbackground-position: 50% 50%;\r\n\tbackground-repeat: no-repeat;\r\n\tdisplay: block;\r\n\t}\r\n.leaflet-bar a:hover {\r\n\tbackground-color: #f4f4f4;\r\n\t}\r\n.leaflet-bar a:first-child {\r\n\tborder-top-left-radius: 4px;\r\n\tborder-top-right-radius: 4px;\r\n\t}\r\n.leaflet-bar a:last-child {\r\n\tborder-bottom-left-radius: 4px;\r\n\tborder-bottom-right-radius: 4px;\r\n\tborder-bottom: none;\r\n\t}\r\n.leaflet-bar a.leaflet-disabled {\r\n\tcursor: default;\r\n\tbackground-color: #f4f4f4;\r\n\tcolor: #bbb;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-bar a {\r\n\twidth: 30px;\r\n\theight: 30px;\r\n\tline-height: 30px;\r\n\t}\r\n.leaflet-touch .leaflet-bar a:first-child {\r\n\tborder-top-left-radius: 2px;\r\n\tborder-top-right-radius: 2px;\r\n\t}\r\n.leaflet-touch .leaflet-bar a:last-child {\r\n\tborder-bottom-left-radius: 2px;\r\n\tborder-bottom-right-radius: 2px;\r\n\t}\r\n\r\n/* zoom control */\r\n\r\n.leaflet-control-zoom-in,\r\n.leaflet-control-zoom-out {\r\n\tfont: bold 18px 'Lucida Console', Monaco, monospace;\r\n\ttext-indent: 1px;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {\r\n\tfont-size: 22px;\r\n\t}\r\n\r\n\r\n/* layers control */\r\n\r\n.leaflet-control-layers {\r\n\tbox-shadow: 0 1px 5px rgba(0,0,0,0.4);\r\n\tbackground: #fff;\r\n\tborder-radius: 5px;\r\n\t}\r\n.leaflet-control-layers-toggle {\r\n\tbackground-image: url("+c+");\r\n\twidth: 36px;\r\n\theight: 36px;\r\n\t}\r\n.leaflet-retina .leaflet-control-layers-toggle {\r\n\tbackground-image: url("+d+");\r\n\tbackground-size: 26px 26px;\r\n\t}\r\n.leaflet-touch .leaflet-control-layers-toggle {\r\n\twidth: 44px;\r\n\theight: 44px;\r\n\t}\r\n.leaflet-control-layers .leaflet-control-layers-list,\r\n.leaflet-control-layers-expanded .leaflet-control-layers-toggle {\r\n\tdisplay: none;\r\n\t}\r\n.leaflet-control-layers-expanded .leaflet-control-layers-list {\r\n\tdisplay: block;\r\n\tposition: relative;\r\n\t}\r\n.leaflet-control-layers-expanded {\r\n\tpadding: 6px 10px 6px 6px;\r\n\tcolor: #333;\r\n\tbackground: #fff;\r\n\t}\r\n.leaflet-control-layers-scrollbar {\r\n\toverflow-y: scroll;\r\n\toverflow-x: hidden;\r\n\tpadding-right: 5px;\r\n\t}\r\n.leaflet-control-layers-selector {\r\n\tmargin-top: 2px;\r\n\tposition: relative;\r\n\ttop: 1px;\r\n\t}\r\n.leaflet-control-layers label {\r\n\tdisplay: block;\r\n\t}\r\n.leaflet-control-layers-separator {\r\n\theight: 0;\r\n\tborder-top: 1px solid #ddd;\r\n\tmargin: 5px -10px 5px -6px;\r\n\t}\r\n\r\n/* Default icon URLs */\r\n.leaflet-default-icon-path {\r\n\tbackground-image: url("+p+');\r\n\t}\r\n\r\n\r\n/* attribution and scale controls */\r\n\r\n.leaflet-container .leaflet-control-attribution {\r\n\tbackground: #fff;\r\n\tbackground: rgba(255, 255, 255, 0.7);\r\n\tmargin: 0;\r\n\t}\r\n.leaflet-control-attribution,\r\n.leaflet-control-scale-line {\r\n\tpadding: 0 5px;\r\n\tcolor: #333;\r\n\t}\r\n.leaflet-control-attribution a {\r\n\ttext-decoration: none;\r\n\t}\r\n.leaflet-control-attribution a:hover {\r\n\ttext-decoration: underline;\r\n\t}\r\n.leaflet-container .leaflet-control-attribution,\r\n.leaflet-container .leaflet-control-scale {\r\n\tfont-size: 11px;\r\n\t}\r\n.leaflet-left .leaflet-control-scale {\r\n\tmargin-left: 5px;\r\n\t}\r\n.leaflet-bottom .leaflet-control-scale {\r\n\tmargin-bottom: 5px;\r\n\t}\r\n.leaflet-control-scale-line {\r\n\tborder: 2px solid #777;\r\n\tborder-top: none;\r\n\tline-height: 1.1;\r\n\tpadding: 2px 5px 1px;\r\n\tfont-size: 11px;\r\n\twhite-space: nowrap;\r\n\toverflow: hidden;\r\n\t-moz-box-sizing: border-box;\r\n\t box-sizing: border-box;\r\n\r\n\tbackground: #fff;\r\n\tbackground: rgba(255, 255, 255, 0.5);\r\n\t}\r\n.leaflet-control-scale-line:not(:first-child) {\r\n\tborder-top: 2px solid #777;\r\n\tborder-bottom: none;\r\n\tmargin-top: -2px;\r\n\t}\r\n.leaflet-control-scale-line:not(:first-child):not(:last-child) {\r\n\tborder-bottom: 2px solid #777;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-control-attribution,\r\n.leaflet-touch .leaflet-control-layers,\r\n.leaflet-touch .leaflet-bar {\r\n\tbox-shadow: none;\r\n\t}\r\n.leaflet-touch .leaflet-control-layers,\r\n.leaflet-touch .leaflet-bar {\r\n\tborder: 2px solid rgba(0,0,0,0.2);\r\n\tbackground-clip: padding-box;\r\n\t}\r\n\r\n\r\n/* popup */\r\n\r\n.leaflet-popup {\r\n\tposition: absolute;\r\n\ttext-align: center;\r\n\tmargin-bottom: 20px;\r\n\t}\r\n.leaflet-popup-content-wrapper {\r\n\tpadding: 1px;\r\n\ttext-align: left;\r\n\tborder-radius: 12px;\r\n\t}\r\n.leaflet-popup-content {\r\n\tmargin: 13px 19px;\r\n\tline-height: 1.4;\r\n\t}\r\n.leaflet-popup-content p {\r\n\tmargin: 18px 0;\r\n\t}\r\n.leaflet-popup-tip-container {\r\n\twidth: 40px;\r\n\theight: 20px;\r\n\tposition: absolute;\r\n\tleft: 50%;\r\n\tmargin-left: -20px;\r\n\toverflow: hidden;\r\n\tpointer-events: none;\r\n\t}\r\n.leaflet-popup-tip {\r\n\twidth: 17px;\r\n\theight: 17px;\r\n\tpadding: 1px;\r\n\r\n\tmargin: -10px auto 0;\r\n\r\n\t-webkit-transform: rotate(45deg);\r\n\t -moz-transform: rotate(45deg);\r\n\t -ms-transform: rotate(45deg);\r\n\t transform: rotate(45deg);\r\n\t}\r\n.leaflet-popup-content-wrapper,\r\n.leaflet-popup-tip {\r\n\tbackground: white;\r\n\tcolor: #333;\r\n\tbox-shadow: 0 3px 14px rgba(0,0,0,0.4);\r\n\t}\r\n.leaflet-container a.leaflet-popup-close-button {\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tright: 0;\r\n\tpadding: 4px 4px 0 0;\r\n\tborder: none;\r\n\ttext-align: center;\r\n\twidth: 18px;\r\n\theight: 14px;\r\n\tfont: 16px/14px Tahoma, Verdana, sans-serif;\r\n\tcolor: #c3c3c3;\r\n\ttext-decoration: none;\r\n\tfont-weight: bold;\r\n\tbackground: transparent;\r\n\t}\r\n.leaflet-container a.leaflet-popup-close-button:hover {\r\n\tcolor: #999;\r\n\t}\r\n.leaflet-popup-scrolled {\r\n\toverflow: auto;\r\n\tborder-bottom: 1px solid #ddd;\r\n\tborder-top: 1px solid #ddd;\r\n\t}\r\n\r\n.leaflet-oldie .leaflet-popup-content-wrapper {\r\n\t-ms-zoom: 1;\r\n\t}\r\n.leaflet-oldie .leaflet-popup-tip {\r\n\twidth: 24px;\r\n\tmargin: 0 auto;\r\n\r\n\t-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";\r\n\tfilter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);\r\n\t}\r\n.leaflet-oldie .leaflet-popup-tip-container {\r\n\tmargin-top: -1px;\r\n\t}\r\n\r\n.leaflet-oldie .leaflet-control-zoom,\r\n.leaflet-oldie .leaflet-control-layers,\r\n.leaflet-oldie .leaflet-popup-content-wrapper,\r\n.leaflet-oldie .leaflet-popup-tip {\r\n\tborder: 1px solid #999;\r\n\t}\r\n\r\n\r\n/* div icon */\r\n\r\n.leaflet-div-icon {\r\n\tbackground: #fff;\r\n\tborder: 1px solid #666;\r\n\t}\r\n\r\n\r\n/* Tooltip */\r\n/* Base styles for the element that has a tooltip */\r\n.leaflet-tooltip {\r\n\tposition: absolute;\r\n\tpadding: 6px;\r\n\tbackground-color: #fff;\r\n\tborder: 1px solid #fff;\r\n\tborder-radius: 3px;\r\n\tcolor: #222;\r\n\twhite-space: nowrap;\r\n\t-webkit-user-select: none;\r\n\t-moz-user-select: none;\r\n\t-ms-user-select: none;\r\n\tuser-select: none;\r\n\tpointer-events: none;\r\n\tbox-shadow: 0 1px 3px rgba(0,0,0,0.4);\r\n\t}\r\n.leaflet-tooltip.leaflet-clickable {\r\n\tcursor: pointer;\r\n\tpointer-events: auto;\r\n\t}\r\n.leaflet-tooltip-top:before,\r\n.leaflet-tooltip-bottom:before,\r\n.leaflet-tooltip-left:before,\r\n.leaflet-tooltip-right:before {\r\n\tposition: absolute;\r\n\tpointer-events: none;\r\n\tborder: 6px solid transparent;\r\n\tbackground: transparent;\r\n\tcontent: "";\r\n\t}\r\n\r\n/* Directions */\r\n\r\n.leaflet-tooltip-bottom {\r\n\tmargin-top: 6px;\r\n}\r\n.leaflet-tooltip-top {\r\n\tmargin-top: -6px;\r\n}\r\n.leaflet-tooltip-bottom:before,\r\n.leaflet-tooltip-top:before {\r\n\tleft: 50%;\r\n\tmargin-left: -6px;\r\n\t}\r\n.leaflet-tooltip-top:before {\r\n\tbottom: 0;\r\n\tmargin-bottom: -12px;\r\n\tborder-top-color: #fff;\r\n\t}\r\n.leaflet-tooltip-bottom:before {\r\n\ttop: 0;\r\n\tmargin-top: -12px;\r\n\tmargin-left: -6px;\r\n\tborder-bottom-color: #fff;\r\n\t}\r\n.leaflet-tooltip-left {\r\n\tmargin-left: -6px;\r\n}\r\n.leaflet-tooltip-right {\r\n\tmargin-left: 6px;\r\n}\r\n.leaflet-tooltip-left:before,\r\n.leaflet-tooltip-right:before {\r\n\ttop: 50%;\r\n\tmargin-top: -6px;\r\n\t}\r\n.leaflet-tooltip-left:before {\r\n\tright: 0;\r\n\tmargin-right: -12px;\r\n\tborder-left-color: #fff;\r\n\t}\r\n.leaflet-tooltip-right:before {\r\n\tleft: 0;\r\n\tmargin-left: -12px;\r\n\tborder-right-color: #fff;\r\n\t}\r\n',""]);const _=u},645:t=>{"use strict";t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var i=t(e);return e[2]?"@media ".concat(e[2]," {").concat(i,"}"):i})).join("")},e.i=function(t,i,n){"string"==typeof t&&(t=[[null,t,""]]);var o={};if(n)for(var r=0;r{"use strict";t.exports=function(t,e){return e||(e={}),t?(t=String(t.__esModule?t.default:t),/^['"].*['"]$/.test(t)&&(t=t.slice(1,-1)),e.hash&&(t+=e.hash),/["'() \t\n]|(%20)/.test(t)||e.needQuotes?'"'.concat(t.replace(/"/g,'\\"').replace(/\n/g,"\\n"),'"'):t):t}},243:function(t,e){!function(t){"use strict";function e(t){var e,i,n,o;for(i=1,n=arguments.length;i0?Math.floor(t):Math.ceil(t)};function A(t,e,i){return t instanceof Z?t:m(t)?new Z(t[0],t[1]):null==t?t:"object"==typeof t&&"x"in t&&"y"in t?new Z(t.x,t.y):new Z(t,e,i)}function I(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;n=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=B(t);var e=this.min,i=this.max,n=t.min,o=t.max,r=o.x>=e.x&&n.x<=i.x,s=o.y>=e.y&&n.y<=i.y;return r&&s},overlaps:function(t){t=B(t);var e=this.min,i=this.max,n=t.min,o=t.max,r=o.x>e.x&&n.xe.y&&n.y=n.lat&&i.lat<=o.lat&&e.lng>=n.lng&&i.lng<=o.lng},intersects:function(t){t=R(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),r=o.lat>=e.lat&&n.lat<=i.lat,s=o.lng>=e.lng&&n.lng<=i.lng;return r&&s},overlaps:function(t){t=R(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),r=o.lat>e.lat&&n.late.lng&&n.lng1,zt=function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("testPassiveEventSupport",h,e),window.removeEventListener("testPassiveEventSupport",h,e)}catch(t){}return t}(),Mt=!!document.createElement("canvas").getContext,kt=!(!document.createElementNS||!Y("svg").createSVGRect),Ct=!kt&&function(){try{var t=document.createElement("div");t.innerHTML='';var e=t.firstChild;return e.style.behavior="url(#default#VML)",e&&"object"==typeof e.adj}catch(t){return!1}}();function St(t){return navigator.userAgent.toLowerCase().indexOf(t)>=0}var Zt={ie:$,ielt9:Q,edge:tt,webkit:et,android:it,android23:nt,androidStock:rt,opera:st,chrome:at,gecko:ht,safari:lt,phantom:ut,opera12:ct,win:dt,ie3d:pt,webkit3d:_t,gecko3d:ft,any3d:mt,mobile:gt,mobileWebkit:vt,mobileWebkit3d:yt,msPointer:xt,pointer:bt,touch:wt,mobileOpera:Pt,mobileGecko:Lt,retina:Tt,passiveEvents:zt,canvas:Mt,svg:kt,vml:Ct},Et=xt?"MSPointerDown":"pointerdown",At=xt?"MSPointerMove":"pointermove",It=xt?"MSPointerUp":"pointerup",Bt=xt?"MSPointerCancel":"pointercancel",Ot={},Rt=!1;function Nt(t,e,i,o){return"touchstart"===e?function(t,e,i){var o=n((function(t){t.MSPOINTER_TYPE_TOUCH&&t.pointerType===t.MSPOINTER_TYPE_TOUCH&&Be(t),Wt(t,e)}));t["_leaflet_touchstart"+i]=o,t.addEventListener(Et,o,!1),Rt||(document.addEventListener(Et,Dt,!0),document.addEventListener(At,jt,!0),document.addEventListener(It,Ht,!0),document.addEventListener(Bt,Ht,!0),Rt=!0)}(t,i,o):"touchmove"===e?function(t,e,i){var n=function(t){t.pointerType===(t.MSPOINTER_TYPE_MOUSE||"mouse")&&0===t.buttons||Wt(t,e)};t["_leaflet_touchmove"+i]=n,t.addEventListener(At,n,!1)}(t,i,o):"touchend"===e&&function(t,e,i){var n=function(t){Wt(t,e)};t["_leaflet_touchend"+i]=n,t.addEventListener(It,n,!1),t.addEventListener(Bt,n,!1)}(t,i,o),this}function Dt(t){Ot[t.pointerId]=t}function jt(t){Ot[t.pointerId]&&(Ot[t.pointerId]=t)}function Ht(t){delete Ot[t.pointerId]}function Wt(t,e){for(var i in t.touches=[],Ot)t.touches.push(Ot[i]);t.changedTouches=[t],e(t)}var Ft=xt?"MSPointerDown":bt?"pointerdown":"touchstart",Ut=xt?"MSPointerUp":bt?"pointerup":"touchend",Vt="_leaflet_";var qt,Gt,Kt,Yt,Xt,Jt=pe(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),$t=pe(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),Qt="webkitTransition"===$t||"OTransition"===$t?$t+"End":"transitionend";function te(t){return"string"==typeof t?document.getElementById(t):t}function ee(t,e){var i=t.style[e]||t.currentStyle&&t.currentStyle[e];if((!i||"auto"===i)&&document.defaultView){var n=document.defaultView.getComputedStyle(t,null);i=n?n[e]:null}return"auto"===i?null:i}function ie(t,e,i){var n=document.createElement(t);return n.className=e||"",i&&i.appendChild(n),n}function ne(t){var e=t.parentNode;e&&e.removeChild(t)}function oe(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function re(t){var e=t.parentNode;e&&e.lastChild!==t&&e.appendChild(t)}function se(t){var e=t.parentNode;e&&e.firstChild!==t&&e.insertBefore(t,e.firstChild)}function ae(t,e){if(void 0!==t.classList)return t.classList.contains(e);var i=ce(t);return i.length>0&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(i)}function he(t,e){if(void 0!==t.classList)for(var i=c(e),n=0,o=i.length;n1)return;var e=Date.now(),i=e-(n||e);o=t.touches?t.touches[0]:t,r=i>0&&i<=250,n=e}function a(t){if(r&&!o.cancelBubble){if(bt){if("mouse"===t.pointerType)return;var i,s,a={};for(s in o)i=o[s],a[s]=i&&i.bind?i.bind(o):i;o=a}o.type="dblclick",o.button=0,e(o),n=null}}t[Vt+Ft+i]=s,t[Vt+Ut+i]=a,t[Vt+"dblclick"+i]=e,t.addEventListener(Ft,s,!!zt&&{passive:!1}),t.addEventListener(Ut,a,!!zt&&{passive:!1}),t.addEventListener("dblclick",e,!1)}(t,s,o):"addEventListener"in t?"touchstart"===e||"touchmove"===e||"wheel"===e||"mousewheel"===e?t.addEventListener(Ce[e]||e,s,!!zt&&{passive:!1}):"mouseenter"===e||"mouseleave"===e?(s=function(e){e=e||window.event,Fe(t,e)&&a(e)},t.addEventListener(Ce[e],s,!1)):t.addEventListener(e,a,!1):"attachEvent"in t&&t.attachEvent("on"+e,s),t[ze]=t[ze]||{},t[ze][o]=s}function Ze(t,e,i,n){var o=e+r(i)+(n?"_"+r(n):""),s=t[ze]&&t[ze][o];if(!s)return this;bt&&0===e.indexOf("touch")?function(t,e,i){var n=t["_leaflet_"+e+i];"touchstart"===e?t.removeEventListener(Et,n,!1):"touchmove"===e?t.removeEventListener(At,n,!1):"touchend"===e&&(t.removeEventListener(It,n,!1),t.removeEventListener(Bt,n,!1))}(t,e,o):wt&&"dblclick"===e&&!ke()?function(t,e){var i=t[Vt+Ft+e],n=t[Vt+Ut+e],o=t[Vt+"dblclick"+e];t.removeEventListener(Ft,i,!!zt&&{passive:!1}),t.removeEventListener(Ut,n,!!zt&&{passive:!1}),t.removeEventListener("dblclick",o,!1)}(t,o):"removeEventListener"in t?t.removeEventListener(Ce[e]||e,s,!1):"detachEvent"in t&&t.detachEvent("on"+e,s),t[ze][o]=null}function Ee(t){return t.stopPropagation?t.stopPropagation():t.originalEvent?t.originalEvent._stopped=!0:t.cancelBubble=!0,We(t),this}function Ae(t){return Se(t,"wheel",Ee),this}function Ie(t){return Te(t,"mousedown touchstart dblclick",Ee),Se(t,"click",He),this}function Be(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this}function Oe(t){return Be(t),Ee(t),this}function Re(t,e){if(!e)return new Z(t.clientX,t.clientY);var i=Pe(e),n=i.boundingClientRect;return new Z((t.clientX-n.left)/i.x-e.clientLeft,(t.clientY-n.top)/i.y-e.clientTop)}var Ne=dt&&at?2*window.devicePixelRatio:ht?window.devicePixelRatio:1;function De(t){return tt?t.wheelDeltaY/2:t.deltaY&&0===t.deltaMode?-t.deltaY/Ne:t.deltaY&&1===t.deltaMode?20*-t.deltaY:t.deltaY&&2===t.deltaMode?60*-t.deltaY:t.deltaX||t.deltaZ?0:t.wheelDelta?(t.wheelDeltaY||t.wheelDelta)/2:t.detail&&Math.abs(t.detail)<32765?20*-t.detail:t.detail?t.detail/-32765*60:0}var je={};function He(t){je[t.type]=!0}function We(t){var e=je[t.type];return je[t.type]=!1,e}function Fe(t,e){var i=e.relatedTarget;if(!i)return!0;try{for(;i&&i!==t;)i=i.parentNode}catch(t){return!1}return i!==t}var Ue={on:Te,off:Me,stopPropagation:Ee,disableScrollPropagation:Ae,disableClickPropagation:Ie,preventDefault:Be,stop:Oe,getMousePosition:Re,getWheelDelta:De,fakeStop:He,skipped:We,isExternalTarget:Fe,addListener:Te,removeListener:Me},Ve=S.extend({run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=me(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=T(this._animate,this),this._step()},_step:function(t){var e=+new Date-this._startTime,i=1e3*this._duration;ethis.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,e){this._enforcingBounds=!0;var i=this.getCenter(),n=this._limitCenter(i,this._zoom,R(t));return i.equals(n)||this.panTo(n,e),this._enforcingBounds=!1,this},panInside:function(t,e){var i=A((e=e||{}).paddingTopLeft||e.padding||[0,0]),n=A(e.paddingBottomRight||e.padding||[0,0]),o=this.getCenter(),r=this.project(o),s=this.project(t),a=this.getPixelBounds(),h=a.getSize().divideBy(2),l=B([a.min.add(i),a.max.subtract(n)]);if(!l.contains(s)){this._enforcingBounds=!0;var u=r.subtract(s),c=A(s.x+u.x,s.y+u.y);(s.xl.max.x)&&(c.x=r.x-u.x,u.x>0?c.x+=h.x-i.x:c.x-=h.x-n.x),(s.yl.max.y)&&(c.y=r.y-u.y,u.y>0?c.y+=h.y-i.y:c.y-=h.y-n.y),this.panTo(this.unproject(c),e),this._enforcingBounds=!1}return this},invalidateSize:function(t){if(!this._loaded)return this;t=e({animate:!1,pan:!0},!0===t?{animate:!0}:t);var i=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var o=this.getSize(),r=i.divideBy(2).round(),s=o.divideBy(2).round(),a=r.subtract(s);return a.x||a.y?(t.animate&&t.pan?this.panBy(a):(t.pan&&this._rawPanBy(a),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(n(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:i,newSize:o})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=e({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var i=n(this._handleGeolocationResponse,this),o=n(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(i,o,t):navigator.geolocation.getCurrentPosition(i,o,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e=t.code,i=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+i+"."})},_handleGeolocationResponse:function(t){var e=new N(t.coords.latitude,t.coords.longitude),i=e.toBounds(2*t.coords.accuracy),n=this._locateOptions;if(n.setView){var o=this.getBoundsZoom(i);this.setView(e,n.maxZoom?Math.min(o,n.maxZoom):o)}var r={latlng:e,bounds:i,timestamp:t.timestamp};for(var s in t.coords)"number"==typeof t.coords[s]&&(r[s]=t.coords[s]);this.fire("locationfound",r)},addHandler:function(t,e){if(!e)return this;var i=this[t]=new e(this);return this._handlers.push(i),this.options[t]&&i.enable(),this},remove:function(){if(this._initEvents(!0),this.off("moveend",this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}var t;for(t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),ne(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(z(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)ne(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,e){var i=ie("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),e||this._mapPane);return t&&(this._panes[t]=i),i},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new O(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,e,i){t=R(t),i=A(i||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),r=this.getMaxZoom(),s=t.getNorthWest(),a=t.getSouthEast(),h=this.getSize().subtract(i),l=B(this.project(a,n),this.project(s,n)).getSize(),u=mt?this.options.zoomSnap:1,c=h.x/l.x,d=h.y/l.y,p=e?Math.max(c,d):Math.min(c,d);return n=this.getScaleZoom(p,n),u&&(n=Math.round(n/(u/100))*(u/100),n=e?Math.ceil(n/u)*u:Math.floor(n/u)*u),Math.max(o,Math.min(r,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new Z(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,e){var i=this._getTopLeftPoint(t,e);return new I(i,i.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,e){var i=this.options.crs;return e=void 0===e?this._zoom:e,i.scale(t)/i.scale(e)},getScaleZoom:function(t,e){var i=this.options.crs;e=void 0===e?this._zoom:e;var n=i.zoom(t*i.scale(e));return isNaN(n)?1/0:n},project:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.latLngToPoint(D(t),e)},unproject:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.pointToLatLng(A(t),e)},layerPointToLatLng:function(t){var e=A(t).add(this.getPixelOrigin());return this.unproject(e)},latLngToLayerPoint:function(t){return this.project(D(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(D(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(R(t))},distance:function(t,e){return this.options.crs.distance(D(t),D(e))},containerPointToLayerPoint:function(t){return A(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return A(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var e=this.containerPointToLayerPoint(A(t));return this.layerPointToLatLng(e)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(D(t)))},mouseEventToContainerPoint:function(t){return Re(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var e=this._container=te(t);if(!e)throw new Error("Map container not found.");if(e._leaflet_id)throw new Error("Map container is already initialized.");Te(e,"scroll",this._onScroll,this),this._containerId=r(e)},_initLayout:function(){var t=this._container;this._fadeAnimated=this.options.fadeAnimation&&mt,he(t,"leaflet-container"+(wt?" leaflet-touch":"")+(Tt?" leaflet-retina":"")+(Q?" leaflet-oldie":"")+(lt?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":""));var e=ee(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),fe(this._mapPane,new Z(0,0)),this.createPane("tilePane"),this.createPane("shadowPane"),this.createPane("overlayPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(he(t.markerPane,"leaflet-zoom-hide"),he(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,e){fe(this._mapPane,new Z(0,0));var i=!this._loaded;this._loaded=!0,e=this._limitZoom(e),this.fire("viewprereset");var n=this._zoom!==e;this._moveStart(n,!1)._move(t,e)._moveEnd(n),this.fire("viewreset"),i&&this.fire("load")},_moveStart:function(t,e){return t&&this.fire("zoomstart"),e||this.fire("movestart"),this},_move:function(t,e,i){void 0===e&&(e=this._zoom);var n=this._zoom!==e;return this._zoom=e,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),(n||i&&i.pinch)&&this.fire("zoom",i),this.fire("move",i)},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return z(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){fe(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={},this._targets[r(this._container)]=this;var e=t?Me:Te;e(this._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&e(window,"resize",this._onResize,this),mt&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){z(this._resizeRequest),this._resizeRequest=T((function(){this.invalidateSize({debounceMoveend:!0})}),this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,e){for(var i,n=[],o="mouseout"===e||"mouseover"===e,s=t.target||t.srcElement,a=!1;s;){if((i=this._targets[r(s)])&&("click"===e||"preclick"===e)&&!t._simulated&&this._draggableMoved(i)){a=!0;break}if(i&&i.listens(e,!0)){if(o&&!Fe(s,t))break;if(n.push(i),o)break}if(s===this._container)break;s=s.parentNode}return n.length||a||o||!Fe(s,t)||(n=[this]),n},_handleDOMEvent:function(t){if(this._loaded&&!We(t)){var e=t.type;"mousedown"!==e&&"keypress"!==e&&"keyup"!==e&&"keydown"!==e||xe(t.target||t.srcElement),this._fireDOMEvent(t,e)}},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,i,n){if("click"===t.type){var o=e({},t);o.type="preclick",this._fireDOMEvent(o,o.type,n)}if(!t._stopped&&(n=(n||[]).concat(this._findEventTargets(t,i))).length){var r=n[0];"contextmenu"===i&&r.listens(i,!0)&&Be(t);var s={originalEvent:t};if("keypress"!==t.type&&"keydown"!==t.type&&"keyup"!==t.type){var a=r.getLatLng&&(!r._radius||r._radius<=10);s.containerPoint=a?this.latLngToContainerPoint(r.getLatLng()):this.mouseEventToContainerPoint(t),s.layerPoint=this.containerPointToLayerPoint(s.containerPoint),s.latlng=a?r.getLatLng():this.layerPointToLatLng(s.layerPoint)}for(var h=0;h0?Math.round(t-e)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(e))},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom(),n=mt?this.options.zoomSnap:1;return n&&(t=Math.round(t/n)*n),Math.max(e,Math.min(i,t))},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){le(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){var i=this._getCenterOffset(t)._trunc();return!(!0!==(e&&e.animate)&&!this.getSize().contains(i)||(this.panBy(i,e),0))},_createAnimProxy:function(){var t=this._proxy=ie("div","leaflet-proxy leaflet-zoom-animated");this._panes.mapPane.appendChild(t),this.on("zoomanim",(function(t){var e=Jt,i=this._proxy.style[e];_e(this._proxy,this.project(t.center,t.zoom),this.getZoomScale(t.zoom,1)),i===this._proxy.style[e]&&this._animatingZoom&&this._onZoomTransitionEnd()}),this),this.on("load moveend",this._animMoveEnd,this),this._on("unload",this._destroyAnimProxy,this)},_destroyAnimProxy:function(){ne(this._proxy),this.off("load moveend",this._animMoveEnd,this),delete this._proxy},_animMoveEnd:function(){var t=this.getCenter(),e=this.getZoom();_e(this._proxy,this.project(t,e),this.getZoomScale(e,1))},_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,e,i){if(this._animatingZoom)return!0;if(i=i||{},!this._zoomAnimated||!1===i.animate||this._nothingToAnimate()||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),o=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==i.animate&&!this.getSize().contains(o)||(T((function(){this._moveStart(!0,!1)._animateZoom(t,e,!0)}),this),0))},_animateZoom:function(t,e,i,o){this._mapPane&&(i&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=e,he(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:e,noUpdate:o}),setTimeout(n(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&le(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),T((function(){this._moveEnd(!0)}),this))}});var Ge=k.extend({options:{position:"topright"},initialize:function(t){d(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),n=t._controlCorners[i];return he(e,"leaflet-control"),-1!==i.indexOf("bottom")?n.insertBefore(e,n.firstChild):n.appendChild(e),this._map.on("unload",this.remove,this),this},remove:function(){return this._map?(ne(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null,this):this},_refocusOnMap:function(t){this._map&&t&&t.screenX>0&&t.screenY>0&&this._map.getContainer().focus()}}),Ke=function(t){return new Ge(t)};qe.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){var t=this._controlCorners={},e="leaflet-",i=this._controlContainer=ie("div",e+"control-container",this._container);function n(n,o){var r=e+n+" "+e+o;t[n+o]=ie("div",r,i)}n("top","left"),n("top","right"),n("bottom","left"),n("bottom","right")},_clearControlPos:function(){for(var t in this._controlCorners)ne(this._controlCorners[t]);ne(this._controlContainer),delete this._controlCorners,delete this._controlContainer}});var Ye=Ge.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,e,i,n){return i1,this._baseLayersList.style.display=t?"":"none"),this._separator.style.display=e&&t?"":"none",this},_onLayerChange:function(t){this._handlingClick||this._update();var e=this._getLayer(r(t.target)),i=e.overlay?"add"===t.type?"overlayadd":"overlayremove":"add"===t.type?"baselayerchange":null;i&&this._map.fire(i,e)},_createRadioElement:function(t,e){var i='",n=document.createElement("div");return n.innerHTML=i,n.firstChild},_addItem:function(t){var e,i=document.createElement("label"),n=this._map.hasLayer(t.layer);t.overlay?((e=document.createElement("input")).type="checkbox",e.className="leaflet-control-layers-selector",e.defaultChecked=n):e=this._createRadioElement("leaflet-base-layers_"+r(this),n),this._layerControlInputs.push(e),e.layerId=r(t.layer),Te(e,"click",this._onInputClick,this);var o=document.createElement("span");o.innerHTML=" "+t.name;var s=document.createElement("div");return i.appendChild(s),s.appendChild(e),s.appendChild(o),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(i),this._checkDisabledLayers(),i},_onInputClick:function(){var t,e,i=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var r=i.length-1;r>=0;r--)t=i[r],e=this._getLayer(t.layerId).layer,t.checked?n.push(e):t.checked||o.push(e);for(r=0;r=0;o--)t=i[o],e=this._getLayer(t.layerId).layer,t.disabled=void 0!==e.options.minZoom&&ne.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),Xe=Ge.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"−",zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=ie("div",e+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,e+"-in",i,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,e+"-out",i,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,e,i,n,o){var r=ie("a",i,n);return r.innerHTML=t,r.href="#",r.title=e,r.setAttribute("role","button"),r.setAttribute("aria-label",e),Ie(r),Te(r,"click",Oe),Te(r,"click",o,this),Te(r,"click",this._refocusOnMap,this),r},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";le(this._zoomInButton,e),le(this._zoomOutButton,e),(this._disabled||t._zoom===t.getMinZoom())&&he(this._zoomOutButton,e),(this._disabled||t._zoom===t.getMaxZoom())&&he(this._zoomInButton,e)}});qe.mergeOptions({zoomControl:!0}),qe.addInitHook((function(){this.options.zoomControl&&(this.zoomControl=new Xe,this.addControl(this.zoomControl))}));var Je=Ge.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var e="leaflet-control-scale",i=ie("div",e),n=this.options;return this._addScales(n,e+"-line",i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=ie("div",e,i)),t.imperial&&(this._iScale=ie("div",e,i))},_update:function(){var t=this._map,e=t.getSize().y/2,i=t.distance(t.containerPointToLatLng([0,e]),t.containerPointToLatLng([this.options.maxWidth,e]));this._updateScales(i)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var e=this._getRoundNum(t),i=e<1e3?e+" m":e/1e3+" km";this._updateScale(this._mScale,i,e/t)},_updateImperial:function(t){var e,i,n,o=3.2808399*t;o>5280?(e=o/5280,i=this._getRoundNum(e),this._updateScale(this._iScale,i+" mi",i/e)):(n=this._getRoundNum(o),this._updateScale(this._iScale,n+" ft",n/o))},_updateScale:function(t,e,i){t.style.width=Math.round(this.options.maxWidth*i)+"px",t.innerHTML=e},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return e*(i>=10?10:i>=5?5:i>=3?3:i>=2?2:1)}}),$e=Ge.extend({options:{position:"bottomright",prefix:'Leaflet'},initialize:function(t){d(this,t),this._attributions={}},onAdd:function(t){for(var e in t.attributionControl=this,this._container=ie("div","leaflet-control-attribution"),Ie(this._container),t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):this},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):this},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" | ")}}});qe.mergeOptions({attributionControl:!0}),qe.addInitHook((function(){this.options.attributionControl&&(new $e).addTo(this)}));Ge.Layers=Ye,Ge.Zoom=Xe,Ge.Scale=Je,Ge.Attribution=$e,Ke.layers=function(t,e,i){return new Ye(t,e,i)},Ke.zoom=function(t){return new Xe(t)},Ke.scale=function(t){return new Je(t)},Ke.attribution=function(t){return new $e(t)};var Qe=k.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled?(this._enabled=!1,this.removeHooks(),this):this},enabled:function(){return!!this._enabled}});Qe.addTo=function(t,e){return t.addHandler(e,this),this};var ti,ei={Events:C},ii=wt?"touchstart mousedown":"mousedown",ni={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},oi={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},ri=S.extend({options:{clickTolerance:3},initialize:function(t,e,i,n){d(this,n),this._element=t,this._dragStartTarget=e||t,this._preventOutline=i},enable:function(){this._enabled||(Te(this._dragStartTarget,ii,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(ri._dragging===this&&this.finishDrag(),Me(this._dragStartTarget,ii,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){if(!t._simulated&&this._enabled&&(this._moved=!1,!ae(this._element,"leaflet-zoom-anim")&&!(ri._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(ri._dragging=this,this._preventOutline&&xe(this._element),ve(),qt(),this._moving)))){this.fire("down");var e=t.touches?t.touches[0]:t,i=we(this._element);this._startPoint=new Z(e.clientX,e.clientY),this._parentScale=Pe(i),Te(document,oi[t.type],this._onMove,this),Te(document,ni[t.type],this._onUp,this)}},_onMove:function(t){if(!t._simulated&&this._enabled)if(t.touches&&t.touches.length>1)this._moved=!0;else{var e=t.touches&&1===t.touches.length?t.touches[0]:t,i=new Z(e.clientX,e.clientY)._subtract(this._startPoint);(i.x||i.y)&&(Math.abs(i.x)+Math.abs(i.y)e&&(i.push(t[n]),o=n);return oh&&(r=s,h=a);h>i&&(e[r]=1,hi(t,e,i,n,r),hi(t,e,i,r,o))}function li(t,e,i,n,o){var r,s,a,h=n?ti:ci(t,i),l=ci(e,i);for(ti=l;;){if(!(h|l))return[t,e];if(h&l)return!1;a=ci(s=ui(t,e,r=h||l,i,o),i),r===h?(t=s,h=a):(e=s,l=a)}}function ui(t,e,i,n,o){var r,s,a=e.x-t.x,h=e.y-t.y,l=n.min,u=n.max;return 8&i?(r=t.x+a*(u.y-t.y)/h,s=u.y):4&i?(r=t.x+a*(l.y-t.y)/h,s=l.y):2&i?(r=u.x,s=t.y+h*(u.x-t.x)/a):1&i&&(r=l.x,s=t.y+h*(l.x-t.x)/a),new Z(r,s,o)}function ci(t,e){var i=0;return t.xe.max.x&&(i|=2),t.ye.max.y&&(i|=8),i}function di(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n}function pi(t,e,i,n){var o,r=e.x,s=e.y,a=i.x-r,h=i.y-s,l=a*a+h*h;return l>0&&((o=((t.x-r)*a+(t.y-s)*h)/l)>1?(r=i.x,s=i.y):o>0&&(r+=a*o,s+=h*o)),a=t.x-r,h=t.y-s,n?a*a+h*h:new Z(r,s)}function _i(t){return!m(t[0])||"object"!=typeof t[0][0]&&void 0!==t[0][0]}function fi(t){return console.warn("Deprecated use of _flat, please use L.LineUtil.isFlat instead."),_i(t)}var mi={simplify:si,pointToSegmentDistance:ai,closestPointOnSegment:function(t,e,i){return pi(t,e,i)},clipSegment:li,_getEdgeIntersection:ui,_getBitCode:ci,_sqClosestPointOnSegment:pi,isFlat:_i,_flat:fi};function gi(t,e,i){var n,o,r,s,a,h,l,u,c,d=[1,4,2,8];for(o=0,l=t.length;o1e-7;h++)e=r*Math.sin(a),e=Math.pow((1-e)/(1+e),r/2),a+=l=Math.PI/2-2*Math.atan(s*e)-a;return new N(a*i,t.x*i/n)}},bi={LonLat:yi,Mercator:xi,SphericalMercator:U},wi=e({},W,{code:"EPSG:3395",projection:xi,transformation:function(){var t=.5/(Math.PI*xi.R);return q(t,.5,-t,.5)}()}),Pi=e({},W,{code:"EPSG:4326",projection:yi,transformation:q(1/180,1,-1/180,.5)}),Li=e({},H,{projection:yi,transformation:q(1,0,-1,0),scale:function(t){return Math.pow(2,t)},zoom:function(t){return Math.log(t)/Math.LN2},distance:function(t,e){var i=e.lng-t.lng,n=e.lat-t.lat;return Math.sqrt(i*i+n*n)},infinite:!0});H.Earth=W,H.EPSG3395=wi,H.EPSG3857=G,H.EPSG900913=K,H.EPSG4326=Pi,H.Simple=Li;var Ti=S.extend({options:{pane:"overlayPane",attribution:null,bubblingMouseEvents:!0},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[r(t)]=this,this},removeInteractiveTarget:function(t){return delete this._map._targets[r(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var e=t.target;if(e.hasLayer(this)){if(this._map=e,this._zoomAnimated=e._zoomAnimated,this.getEvents){var i=this.getEvents();e.on(i,this),this.once("remove",(function(){e.off(i,this)}),this)}this.onAdd(e),this.getAttribution&&e.attributionControl&&e.attributionControl.addAttribution(this.getAttribution()),this.fire("add"),e.fire("layeradd",{layer:this})}}});qe.include({addLayer:function(t){if(!t._layerAdd)throw new Error("The provided object is not a Layer.");var e=r(t);return this._layers[e]||(this._layers[e]=t,t._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t)),this},removeLayer:function(t){var e=r(t);return this._layers[e]?(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[e],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null,this):this},hasLayer:function(t){return!!t&&r(t)in this._layers},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},_addLayers:function(t){for(var e=0,i=(t=t?m(t)?t:[t]:[]).length;ethis._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()e)return s=(n-e)/i,this._map.layerPointToLatLng([r.x-s*(r.x-o.x),r.y-s*(r.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,e){return e=e||this._defaultShape(),t=D(t),e.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new O,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return _i(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var e=[],i=_i(t),n=0,o=t.length;n=2&&e[0]instanceof N&&e[0].equals(e[i-1])&&e.pop(),e},_setLatLngs:function(t){Bi.prototype._setLatLngs.call(this,t),_i(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return _i(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,e=this.options.weight,i=new Z(e,e);if(t=new I(t.min.subtract(i),t.max.add(i)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var n,o=0,r=this._rings.length;ot.y!=n.y>t.y&&t.x<(n.x-i.x)*(t.y-i.y)/(n.y-i.y)+i.x&&(l=!l);return l||Bi.prototype._containsPoint.call(this,t,!0)}});var Ri=Mi.extend({initialize:function(t,e){d(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,n,o=m(t)?t:t.features;if(o){for(e=0,i=o.length;e0?o:[e.src]}else{m(this._url)||(this._url=[this._url]),!this.options.keepAspectRatio&&Object.prototype.hasOwnProperty.call(e.style,"objectFit")&&(e.style.objectFit="fill"),e.autoplay=!!this.options.autoplay,e.loop=!!this.options.loop,e.muted=!!this.options.muted;for(var s=0;so?(e.height=o+"px",he(t,r)):le(t,r),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),i=this._getAnchor();fe(this._container,e.add(i))},_adjustPan:function(){if(this.options.autoPan){this._map._panAnim&&this._map._panAnim.stop();var t=this._map,e=parseInt(ee(this._container,"marginBottom"),10)||0,i=this._container.offsetHeight+e,n=this._containerWidth,o=new Z(this._containerLeft,-i-this._containerBottom);o._add(me(this._container));var r=t.layerPointToContainerPoint(o),s=A(this.options.autoPanPadding),a=A(this.options.autoPanPaddingTopLeft||s),h=A(this.options.autoPanPaddingBottomRight||s),l=t.getSize(),u=0,c=0;r.x+n+h.x>l.x&&(u=r.x+n-l.x+h.x),r.x-u-a.x<0&&(u=r.x-a.x),r.y+i+h.y>l.y&&(c=r.y+i-l.y+h.y),r.y-c-a.y<0&&(c=r.y-a.y),(u||c)&&t.fire("autopanstart").panBy([u,c])}},_onCloseButtonClick:function(t){this._close(),Oe(t)},_getAnchor:function(){return A(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});qe.mergeOptions({closePopupOnClick:!0}),qe.include({openPopup:function(t,e,i){return t instanceof Qi||(t=new Qi(i).setContent(t)),e&&t.setLatLng(e),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),Ti.include({bindPopup:function(t,e){return t instanceof Qi?(d(t,e),this._popup=t,t._source=this):(this._popup&&!e||(this._popup=new Qi(e,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,e){return this._popup&&this._map&&(e=this._popup._prepareOpen(this,t,e),this._map.openPopup(this._popup,e)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var e=t.layer||t.target;this._popup&&this._map&&(Oe(t),e instanceof Ei?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===e?this.closePopup():this.openPopup(e,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var tn=$i.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){$i.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){$i.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=$i.prototype.getEvents.call(this);return wt&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=ie("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var e,i,n=this._map,o=this._container,r=n.latLngToContainerPoint(n.getCenter()),s=n.layerPointToContainerPoint(t),a=this.options.direction,h=o.offsetWidth,l=o.offsetHeight,u=A(this.options.offset),c=this._getAnchor();"top"===a?(e=h/2,i=l):"bottom"===a?(e=h/2,i=0):"center"===a?(e=h/2,i=l/2):"right"===a?(e=0,i=l/2):"left"===a?(e=h,i=l/2):s.xthis.options.maxZoom||in&&this._retainParent(o,r,s,n))},_retainChildren:function(t,e,i,n){for(var o=2*t;o<2*t+2;o++)for(var r=2*e;r<2*e+2;r++){var s=new Z(o,r);s.z=i+1;var a=this._tileCoordsToKey(s),h=this._tiles[a];h&&h.active?h.retain=!0:(h&&h.loaded&&(h.retain=!0),i+1this.options.maxZoom||void 0!==this.options.minZoom&&o1)this._setView(t,i);else{for(var c=o.min.y;c<=o.max.y;c++)for(var d=o.min.x;d<=o.max.x;d++){var p=new Z(d,c);if(p.z=this._tileZoom,this._isValidTile(p)){var _=this._tiles[this._tileCoordsToKey(p)];_?_.current=!0:s.push(p)}}if(s.sort((function(t,e){return t.distanceTo(r)-e.distanceTo(r)})),0!==s.length){this._loading||(this._loading=!0,this.fire("loading"));var f=document.createDocumentFragment();for(d=0;di.max.x)||!e.wrapLat&&(t.yi.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return R(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var e=this._map,i=this.getTileSize(),n=t.scaleBy(i),o=n.add(i);return[e.unproject(n,t.z),e.unproject(o,t.z)]},_tileCoordsToBounds:function(t){var e=this._tileCoordsToNwSe(t),i=new O(e[0],e[1]);return this.options.noWrap||(i=this._map.wrapLatLngBounds(i)),i},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var e=t.split(":"),i=new Z(+e[0],+e[1]);return i.z=+e[2],i},_removeTile:function(t){var e=this._tiles[t];e&&(ne(e.el),delete this._tiles[t],this.fire("tileunload",{tile:e.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){he(t,"leaflet-tile");var e=this.getTileSize();t.style.width=e.x+"px",t.style.height=e.y+"px",t.onselectstart=h,t.onmousemove=h,Q&&this.options.opacity<1&&de(t,this.options.opacity),it&&!nt&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,e){var i=this._getTilePos(t),o=this._tileCoordsToKey(t),r=this.createTile(this._wrapCoords(t),n(this._tileReady,this,t));this._initTile(r),this.createTile.length<2&&T(n(this._tileReady,this,t,null,r)),fe(r,i),this._tiles[o]={el:r,coords:t,current:!0},e.appendChild(r),this.fire("tileloadstart",{tile:r,coords:t})},_tileReady:function(t,e,i){e&&this.fire("tileerror",{error:e,tile:i,coords:t});var o=this._tileCoordsToKey(t);(i=this._tiles[o])&&(i.loaded=+new Date,this._map._fadeAnimated?(de(i.el,0),z(this._fadeFrame),this._fadeFrame=T(this._updateOpacity,this)):(i.active=!0,this._pruneTiles()),e||(he(i.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:i.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),Q||!this._map._fadeAnimated?T(this._pruneTiles,this):setTimeout(n(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var e=new Z(this._wrapX?a(t.x,this._wrapX):t.x,this._wrapY?a(t.y,this._wrapY):t.y);return e.z=t.z,e},_pxBoundsToTileRange:function(t){var e=this.getTileSize();return new I(t.min.unscaleBy(e).floor(),t.max.unscaleBy(e).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var on=nn.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,e){this._url=t,(e=d(this,e)).detectRetina&&Tt&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomReverse?(e.zoomOffset--,e.minZoom++):(e.zoomOffset++,e.maxZoom--),e.minZoom=Math.max(0,e.minZoom)),"string"==typeof e.subdomains&&(e.subdomains=e.subdomains.split("")),it||this.on("tileunload",this._onTileRemove)},setUrl:function(t,e){return this._url===t&&void 0===e&&(e=!0),this._url=t,e||this.redraw(),this},createTile:function(t,e){var i=document.createElement("img");return Te(i,"load",n(this._tileOnLoad,this,e,i)),Te(i,"error",n(this._tileOnError,this,e,i)),(this.options.crossOrigin||""===this.options.crossOrigin)&&(i.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),i.alt="",i.setAttribute("role","presentation"),i.src=this.getTileUrl(t),i},getTileUrl:function(t){var i={r:Tt?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var n=this._globalTileRange.max.y-t.y;this.options.tms&&(i.y=n),i["-y"]=n}return f(this._url,e(i,this.options))},_tileOnLoad:function(t,e){Q?setTimeout(n(t,this,null,e),0):t(null,e)},_tileOnError:function(t,e,i){var n=this.options.errorTileUrl;n&&e.getAttribute("src")!==n&&(e.src=n),t(i,e)},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,e=this.options.maxZoom;return this.options.zoomReverse&&(t=e-t),t+this.options.zoomOffset},_getSubdomain:function(t){var e=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_abortLoading:function(){var t,e;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&((e=this._tiles[t].el).onload=h,e.onerror=h,e.complete||(e.src=v,ne(e),delete this._tiles[t]))},_removeTile:function(t){var e=this._tiles[t];if(e)return rt||e.el.setAttribute("src",v),nn.prototype._removeTile.call(this,t)},_tileReady:function(t,e,i){if(this._map&&(!i||i.getAttribute("src")!==v))return nn.prototype._tileReady.call(this,t,e,i)}});function rn(t,e){return new on(t,e)}var sn=on.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,i){this._url=t;var n=e({},this.defaultWmsParams);for(var o in i)o in this.options||(n[o]=i[o]);var r=(i=d(this,i)).detectRetina&&Tt?2:1,s=this.getTileSize();n.width=s.x*r,n.height=s.y*r,this.wmsParams=n},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var e=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[e]=this._crs.code,on.prototype.onAdd.call(this,t)},getTileUrl:function(t){var e=this._tileCoordsToNwSe(t),i=this._crs,n=B(i.project(e[0]),i.project(e[1])),o=n.min,r=n.max,s=(this._wmsVersion>=1.3&&this._crs===Pi?[o.y,o.x,r.y,r.x]:[o.x,o.y,r.x,r.y]).join(","),a=on.prototype.getTileUrl.call(this,t);return a+p(this.wmsParams,a,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+s},setParams:function(t,i){return e(this.wmsParams,t),i||this.redraw(),this}});on.WMS=sn,rn.wms=function(t,e){return new sn(t,e)};var an=Ti.extend({options:{padding:.1,tolerance:0},initialize:function(t){d(this,t),r(this),this._layers=this._layers||{}},onAdd:function(){this._container||(this._initContainer(),this._zoomAnimated&&he(this._container,"leaflet-zoom-animated")),this.getPane().appendChild(this._container),this._update(),this.on("update",this._updatePaths,this)},onRemove:function(){this.off("update",this._updatePaths,this),this._destroyContainer()},getEvents:function(){var t={viewreset:this._reset,zoom:this._onZoom,moveend:this._update,zoomend:this._onZoomEnd};return this._zoomAnimated&&(t.zoomanim=this._onAnimZoom),t},_onAnimZoom:function(t){this._updateTransform(t.center,t.zoom)},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_updateTransform:function(t,e){var i=this._map.getZoomScale(e,this._zoom),n=me(this._container),o=this._map.getSize().multiplyBy(.5+this.options.padding),r=this._map.project(this._center,e),s=this._map.project(t,e).subtract(r),a=o.multiplyBy(-i).add(n).add(o).subtract(s);mt?_e(this._container,a,i):fe(this._container,a)},_reset:function(){for(var t in this._update(),this._updateTransform(this._center,this._zoom),this._layers)this._layers[t]._reset()},_onZoomEnd:function(){for(var t in this._layers)this._layers[t]._project()},_updatePaths:function(){for(var t in this._layers)this._layers[t]._update()},_update:function(){var t=this.options.padding,e=this._map.getSize(),i=this._map.containerPointToLayerPoint(e.multiplyBy(-t)).round();this._bounds=new I(i,i.add(e.multiplyBy(1+2*t)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom()}}),hn=an.extend({getEvents:function(){var t=an.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){an.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=document.createElement("canvas");Te(t,"mousemove",this._onMouseMove,this),Te(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this),Te(t,"mouseout",this._handleMouseOut,this),this._ctx=t.getContext("2d")},_destroyContainer:function(){z(this._redrawRequest),delete this._ctx,ne(this._container),Me(this._container),delete this._container},_updatePaths:function(){if(!this._postponeUpdatePaths){for(var t in this._redrawBounds=null,this._layers)this._layers[t]._update();this._redraw()}},_update:function(){if(!this._map._animatingZoom||!this._bounds){an.prototype._update.call(this);var t=this._bounds,e=this._container,i=t.getSize(),n=Tt?2:1;fe(e,t.min),e.width=n*i.x,e.height=n*i.y,e.style.width=i.x+"px",e.style.height=i.y+"px",Tt&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update")}},_reset:function(){an.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t),this._layers[r(t)]=t;var e=t._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=e),this._drawLast=e,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var e=t._order,i=e.next,n=e.prev;i?i.prev=n:this._drawLast=n,n?n.next=i:this._drawFirst=i,delete t._order,delete this._layers[r(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if("string"==typeof t.options.dashArray){var e,i,n=t.options.dashArray.split(/[, ]+/),o=[];for(i=0;i')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),cn={_initContainer:function(){this._container=ie("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(an.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var e=t._container=un("shape");he(e,"leaflet-vml-shape "+(this.options.className||"")),e.coordsize="1 1",t._path=un("path"),e.appendChild(t._path),this._updateStyle(t),this._layers[r(t)]=t},_addPath:function(t){var e=t._container;this._container.appendChild(e),t.options.interactive&&t.addInteractiveTarget(e)},_removePath:function(t){var e=t._container;ne(e),t.removeInteractiveTarget(e),delete this._layers[r(t)]},_updateStyle:function(t){var e=t._stroke,i=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(e||(e=t._stroke=un("stroke")),o.appendChild(e),e.weight=n.weight+"px",e.color=n.color,e.opacity=n.opacity,n.dashArray?e.dashStyle=m(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):e.dashStyle="",e.endcap=n.lineCap.replace("butt","flat"),e.joinstyle=n.lineJoin):e&&(o.removeChild(e),t._stroke=null),n.fill?(i||(i=t._fill=un("fill")),o.appendChild(i),i.color=n.fillColor||n.color,i.opacity=n.fillOpacity):i&&(o.removeChild(i),t._fill=null)},_updateCircle:function(t){var e=t._point.round(),i=Math.round(t._radius),n=Math.round(t._radiusY||i);this._setPath(t,t._empty()?"M0 0":"AL "+e.x+","+e.y+" "+i+","+n+" 0,23592600")},_setPath:function(t,e){t._path.v=e},_bringToFront:function(t){re(t._container)},_bringToBack:function(t){se(t._container)}},dn=Ct?un:Y,pn=an.extend({getEvents:function(){var t=an.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=dn("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=dn("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){ne(this._container),Me(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){if(!this._map._animatingZoom||!this._bounds){an.prototype._update.call(this);var t=this._bounds,e=t.getSize(),i=this._container;this._svgSize&&this._svgSize.equals(e)||(this._svgSize=e,i.setAttribute("width",e.x),i.setAttribute("height",e.y)),fe(i,t.min),i.setAttribute("viewBox",[t.min.x,t.min.y,e.x,e.y].join(" ")),this.fire("update")}},_initPath:function(t){var e=t._path=dn("path");t.options.className&&he(e,t.options.className),t.options.interactive&&he(e,"leaflet-interactive"),this._updateStyle(t),this._layers[r(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){ne(t._path),t.removeInteractiveTarget(t._path),delete this._layers[r(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var e=t._path,i=t.options;e&&(i.stroke?(e.setAttribute("stroke",i.color),e.setAttribute("stroke-opacity",i.opacity),e.setAttribute("stroke-width",i.weight),e.setAttribute("stroke-linecap",i.lineCap),e.setAttribute("stroke-linejoin",i.lineJoin),i.dashArray?e.setAttribute("stroke-dasharray",i.dashArray):e.removeAttribute("stroke-dasharray"),i.dashOffset?e.setAttribute("stroke-dashoffset",i.dashOffset):e.removeAttribute("stroke-dashoffset")):e.setAttribute("stroke","none"),i.fill?(e.setAttribute("fill",i.fillColor||i.color),e.setAttribute("fill-opacity",i.fillOpacity),e.setAttribute("fill-rule",i.fillRule||"evenodd")):e.setAttribute("fill","none"))},_updatePoly:function(t,e){this._setPath(t,X(t._parts,e))},_updateCircle:function(t){var e=t._point,i=Math.max(Math.round(t._radius),1),n="a"+i+","+(Math.max(Math.round(t._radiusY),1)||i)+" 0 1,0 ",o=t._empty()?"M0 0":"M"+(e.x-i)+","+e.y+n+2*i+",0 "+n+2*-i+",0 ";this._setPath(t,o)},_setPath:function(t,e){t._path.setAttribute("d",e)},_bringToFront:function(t){re(t._path)},_bringToBack:function(t){se(t._path)}});function _n(t){return kt||Ct?new pn(t):null}Ct&&pn.include(cn),qe.include({getRenderer:function(t){var e=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer;return e||(e=this._renderer=this._createRenderer()),this.hasLayer(e)||this.addLayer(e),e},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var e=this._paneRenderers[t];return void 0===e&&(e=this._createRenderer({pane:t}),this._paneRenderers[t]=e),e},_createRenderer:function(t){return this.options.preferCanvas&&ln(t)||_n(t)}});var fn=Oi.extend({initialize:function(t,e){Oi.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=R(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});pn.create=dn,pn.pointsToPath=X,Ri.geometryToLayer=Ni,Ri.coordsToLatLng=ji,Ri.coordsToLatLngs=Hi,Ri.latLngToCoords=Wi,Ri.latLngsToCoords=Fi,Ri.getFeature=Ui,Ri.asFeature=Vi,qe.mergeOptions({boxZoom:!0});var mn=Qe.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){Te(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){Me(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){ne(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),qt(),ve(),this._startPoint=this._map.mouseEventToContainerPoint(t),Te(document,{contextmenu:Oe,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=ie("div","leaflet-zoom-box",this._container),he(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var e=new I(this._point,this._startPoint),i=e.getSize();fe(this._box,e.min),this._box.style.width=i.x+"px",this._box.style.height=i.y+"px"},_finish:function(){this._moved&&(ne(this._box),le(this._container,"leaflet-crosshair")),Gt(),ye(),Me(document,{contextmenu:Oe,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(n(this._resetState,this),0);var e=new O(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(e).fire("boxzoomend",{boxZoomBounds:e})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});qe.addInitHook("addHandler","boxZoom",mn),qe.mergeOptions({doubleClickZoom:!0});var gn=Qe.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var e=this._map,i=e.getZoom(),n=e.options.zoomDelta,o=t.originalEvent.shiftKey?i-n:i+n;"center"===e.options.doubleClickZoom?e.setZoom(o):e.setZoomAround(t.containerPoint,o)}});qe.addInitHook("addHandler","doubleClickZoom",gn),qe.mergeOptions({dragging:!0,inertia:!nt,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var vn=Qe.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new ri(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))}he(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){le(this._map._container,"leaflet-grab"),le(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t=this._map;if(t._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity){var e=R(this._map.options.maxBounds);this._offsetLimit=B(this._map.latLngToContainerPoint(e.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(e.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))}else this._offsetLimit=null;t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){if(this._map.options.inertia){var e=this._lastTime=+new Date,i=this._lastPos=this._draggable._absPos||this._draggable._newPos;this._positions.push(i),this._times.push(e),this._prunePositions(e)}this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;this._positions.length>1&&t-this._times[0]>50;)this._positions.shift(),this._times.shift()},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),e=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,e){return t-(t-e)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),e=this._offsetLimit;t.xe.max.x&&(t.x=this._viscousLimit(t.x,e.max.x)),t.y>e.max.y&&(t.y=this._viscousLimit(t.y,e.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,r=(n+e+i)%t-e-i,s=Math.abs(o+i)0?r:-r))-e;this._delta=0,this._startTime=null,s&&("center"===t.options.scrollWheelZoom?t.setZoom(e+s):t.setZoomAround(this._lastMousePos,e+s))}});qe.addInitHook("addHandler","scrollWheelZoom",xn),qe.mergeOptions({tap:!0,tapTolerance:15});var bn=Qe.extend({addHooks:function(){Te(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){Me(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(Be(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var e=t.touches[0],i=e.target;this._startPos=this._newPos=new Z(e.clientX,e.clientY),i.tagName&&"a"===i.tagName.toLowerCase()&&he(i,"leaflet-active"),this._holdTimeout=setTimeout(n((function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",e))}),this),1e3),this._simulateEvent("mousedown",e),Te(document,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),Me(document,{touchmove:this._onMove,touchend:this._onUp},this),this._fireClick&&t&&t.changedTouches){var e=t.changedTouches[0],i=e.target;i&&i.tagName&&"a"===i.tagName.toLowerCase()&&le(i,"leaflet-active"),this._simulateEvent("mouseup",e),this._isTapValid()&&this._simulateEvent("click",e)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var e=t.touches[0];this._newPos=new Z(e.clientX,e.clientY),this._simulateEvent("mousemove",e)},_simulateEvent:function(t,e){var i=document.createEvent("MouseEvents");i._simulated=!0,e.target._simulatedClick=!0,i.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,!1,!1,!1,!1,0,null),e.target.dispatchEvent(i)}});!wt||bt&&!lt||qe.addInitHook("addHandler","tap",bn),qe.mergeOptions({touchZoom:wt&&!nt,bounceAtZoomLimits:!0});var wn=Qe.extend({addHooks:function(){he(this._map._container,"leaflet-touch-zoom"),Te(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){le(this._map._container,"leaflet-touch-zoom"),Me(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var e=this._map;if(t.touches&&2===t.touches.length&&!e._animatingZoom&&!this._zooming){var i=e.mouseEventToContainerPoint(t.touches[0]),n=e.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=e.getSize()._divideBy(2),this._startLatLng=e.containerPointToLatLng(this._centerPoint),"center"!==e.options.touchZoom&&(this._pinchStartLatLng=e.containerPointToLatLng(i.add(n)._divideBy(2))),this._startDist=i.distanceTo(n),this._startZoom=e.getZoom(),this._moved=!1,this._zooming=!0,e._stop(),Te(document,"touchmove",this._onTouchMove,this),Te(document,"touchend",this._onTouchEnd,this),Be(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var e=this._map,i=e.mouseEventToContainerPoint(t.touches[0]),o=e.mouseEventToContainerPoint(t.touches[1]),r=i.distanceTo(o)/this._startDist;if(this._zoom=e.getScaleZoom(r,this._startZoom),!e.options.bounceAtZoomLimits&&(this._zoome.getMaxZoom()&&r>1)&&(this._zoom=e._limitZoom(this._zoom)),"center"===e.options.touchZoom){if(this._center=this._startLatLng,1===r)return}else{var s=i._add(o)._divideBy(2)._subtract(this._centerPoint);if(1===r&&0===s.x&&0===s.y)return;this._center=e.unproject(e.project(this._pinchStartLatLng,this._zoom).subtract(s),this._zoom)}this._moved||(e._moveStart(!0,!1),this._moved=!0),z(this._animRequest);var a=n(e._move,e,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=T(a,this,!0),Be(t)}},_onTouchEnd:function(){this._moved&&this._zooming?(this._zooming=!1,z(this._animRequest),Me(document,"touchmove",this._onTouchMove,this),Me(document,"touchend",this._onTouchEnd,this),this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom))):this._zooming=!1}});qe.addInitHook("addHandler","touchZoom",wn),qe.BoxZoom=mn,qe.DoubleClickZoom=gn,qe.Drag=vn,qe.Keyboard=yn,qe.ScrollWheelZoom=xn,qe.Tap=bn,qe.TouchZoom=wn,t.version="1.7.1",t.Control=Ge,t.control=Ke,t.Browser=Zt,t.Evented=S,t.Mixin=ei,t.Util=M,t.Class=k,t.Handler=Qe,t.extend=e,t.bind=n,t.stamp=r,t.setOptions=d,t.DomEvent=Ue,t.DomUtil=Le,t.PosAnimation=Ve,t.Draggable=ri,t.LineUtil=mi,t.PolyUtil=vi,t.Point=Z,t.point=A,t.Bounds=I,t.bounds=B,t.Transformation=V,t.transformation=q,t.Projection=bi,t.LatLng=N,t.latLng=D,t.LatLngBounds=O,t.latLngBounds=R,t.CRS=H,t.GeoJSON=Ri,t.geoJSON=Gi,t.geoJson=Ki,t.Layer=Ti,t.LayerGroup=zi,t.layerGroup=function(t,e){return new zi(t,e)},t.FeatureGroup=Mi,t.featureGroup=function(t,e){return new Mi(t,e)},t.ImageOverlay=Yi,t.imageOverlay=function(t,e,i){return new Yi(t,e,i)},t.VideoOverlay=Xi,t.videoOverlay=function(t,e,i){return new Xi(t,e,i)},t.SVGOverlay=Ji,t.svgOverlay=function(t,e,i){return new Ji(t,e,i)},t.DivOverlay=$i,t.Popup=Qi,t.popup=function(t,e){return new Qi(t,e)},t.Tooltip=tn,t.tooltip=function(t,e){return new tn(t,e)},t.Icon=ki,t.icon=function(t){return new ki(t)},t.DivIcon=en,t.divIcon=function(t){return new en(t)},t.Marker=Zi,t.marker=function(t,e){return new Zi(t,e)},t.TileLayer=on,t.tileLayer=rn,t.GridLayer=nn,t.gridLayer=function(t){return new nn(t)},t.SVG=pn,t.svg=_n,t.Renderer=an,t.Canvas=hn,t.canvas=ln,t.Path=Ei,t.CircleMarker=Ai,t.circleMarker=function(t,e){return new Ai(t,e)},t.Circle=Ii,t.circle=function(t,e,i){return new Ii(t,e,i)},t.Polyline=Bi,t.polyline=function(t,e){return new Bi(t,e)},t.Polygon=Oi,t.polygon=function(t,e){return new Oi(t,e)},t.Rectangle=fn,t.rectangle=function(t,e){return new fn(t,e)},t.Map=qe,t.map=function(t,e){return new qe(t,e)};var Pn=window.L;t.noConflict=function(){return window.L=Pn,this},window.L=t}(e)},379:t=>{"use strict";var e=[];function i(t){for(var i=-1,n=0;n{"use strict";var e={};t.exports=function(t,i){var n=function(t){if(void 0===e[t]){var i=document.querySelector(t);if(window.HTMLIFrameElement&&i instanceof window.HTMLIFrameElement)try{i=i.contentDocument.head}catch(t){i=null}e[t]=i}return e[t]}(t);if(!n)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");n.appendChild(i)}},216:t=>{"use strict";t.exports=function(t){var e=document.createElement("style");return t.setAttributes(e,t.attributes),t.insert(e),e}},565:(t,e,i)=>{"use strict";t.exports=function(t){var e=i.nc;e&&t.setAttribute("nonce",e)}},795:t=>{"use strict";t.exports=function(t){var e=t.insertStyleElement(t);return{update:function(i){!function(t,e,i){var n=i.css,o=i.media,r=i.sourceMap;o?t.setAttribute("media",o):t.removeAttribute("media"),r&&"undefined"!=typeof btoa&&(n+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(r))))," */")),e.styleTagTransform(n,t)}(e,t,i)},remove:function(){!function(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t)}(e)}}}},589:t=>{"use strict";t.exports=function(t,e){if(e.styleSheet)e.styleSheet.cssText=t;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(t))}}},134:(t,e,i)=>{"use strict";t.exports=i.p+"8f2c4d11474275fbc161.png"},803:(t,e,i)=>{"use strict";t.exports=i.p+"416d91365b44e4b4f477.png"},94:(t,e,i)=>{"use strict";t.exports=i.p+"2b3e1faf89f94a483539.png"},235:(t,e,i)=>{"use strict";t.exports=i.p+"fc32c7cbd6407867571b.png"},561:(t,e,i)=>{"use strict";t.exports=i.p+"a47ad8a0da980112e2cd.png"},149:(t,e,i)=>{"use strict";t.exports=i.p+"6733b337e5bc285232c2.png"}},e={};function i(n){var o=e[n];if(void 0!==o)return o.exports;var r=e[n]={id:n,exports:{}};return t[n].call(r.exports,r,r.exports,i),r.exports}i.m=t,i.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return i.d(e,{a:e}),e},i.d=(t,e)=>{for(var n in e)i.o(e,n)&&!i.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{var t;i.g.importScripts&&(t=i.g.location+"");var e=i.g.document;if(!t&&e&&(e.currentScript&&(t=e.currentScript.src),!t)){var n=e.getElementsByTagName("script");n.length&&(t=n[n.length-1].src)}if(!t)throw new Error("Automatic publicPath is not supported in this browser");t=t.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),i.p=t})(),i.b=document.baseURI||self.location.href,(()=>{"use strict";var t=i(379),e=i.n(t),n=i(795),o=i.n(n),r=i(569),s=i.n(r),a=i(565),h=i.n(a),l=i(216),u=i.n(l),c=i(589),d=i.n(c),p=i(984),_={};_.styleTagTransform=d(),_.setAttributes=h(),_.insert=s().bind(null,"head"),_.domAPI=o(),_.insertStyleElement=u(),e()(p.Z,_),p.Z&&p.Z.locals&&p.Z.locals;var f=i(149),m=i(561),g=i(235);window.L=i(243),window.markerIconUp=L.icon({iconUrl:f,shadowUrl:g,popupAnchor:[11,2]}),window.markerIconDown=L.icon({iconUrl:m,shadowUrl:g,popupAnchor:[11,2]})})()})();simplemonitor-1.13.0/simplemonitor/html/dist/maps.bundle.js.LICENSE.txt000066400000000000000000000002301464501162400257500ustar00rootroot00000000000000/* @preserve * Leaflet 1.7.1, a JS library for interactive maps. http://leafletjs.com * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade */ simplemonitor-1.13.0/simplemonitor/html/package-lock.json000066400000000000000000001422261464501162400235700ustar00rootroot00000000000000{ "name": "html", "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@discoveryjs/json-ext": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", "dev": true }, "@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", "dev": true, "requires": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.9" } }, "@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true }, "@jridgewell/set-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true }, "@jridgewell/source-map": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" } }, "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, "@jridgewell/trace-mapping": { "version": "0.3.17", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dev": true, "requires": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" } }, "@popperjs/core": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.3.tgz", "integrity": "sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==" }, "@types/eslint": { "version": "8.21.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.2.tgz", "integrity": "sha512-EMpxUyystd3uZVByZap1DACsMXvb82ypQnGn89e1Y0a+LYu3JJscUd/gqhRsVFDkaD2MIiWo0MT8EfXr3DGRKw==", "dev": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" } }, "@types/eslint-scope": { "version": "3.7.4", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, "requires": { "@types/eslint": "*", "@types/estree": "*" } }, "@types/estree": { "version": "0.0.51", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, "@types/node": { "version": "18.15.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", "dev": true }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", "dev": true, "requires": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" } }, "@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", "dev": true }, "@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", "dev": true }, "@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", "dev": true }, "@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", "dev": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", "dev": true }, "@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", "@webassemblyjs/wasm-gen": "1.11.1" } }, "@webassemblyjs/ieee754": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", "dev": true }, "@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", "@webassemblyjs/helper-wasm-section": "1.11.1", "@webassemblyjs/wasm-gen": "1.11.1", "@webassemblyjs/wasm-opt": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", "@webassemblyjs/wast-printer": "1.11.1" } }, "@webassemblyjs/wasm-gen": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", "@webassemblyjs/ieee754": "1.11.1", "@webassemblyjs/leb128": "1.11.1", "@webassemblyjs/utf8": "1.11.1" } }, "@webassemblyjs/wasm-opt": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", "@webassemblyjs/wasm-gen": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1" } }, "@webassemblyjs/wasm-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", "@webassemblyjs/ieee754": "1.11.1", "@webassemblyjs/leb128": "1.11.1", "@webassemblyjs/utf8": "1.11.1" } }, "@webassemblyjs/wast-printer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" } }, "@webpack-cli/configtest": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.4.tgz", "integrity": "sha512-cs3XLy+UcxiP6bj0A6u7MLLuwdXJ1c3Dtc0RkKg+wiI1g/Ti1om8+/2hc2A2B60NbBNAbMgyBMHvyymWm/j4wQ==", "dev": true }, "@webpack-cli/info": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.3.0.tgz", "integrity": "sha512-ASiVB3t9LOKHs5DyVUcxpraBXDOKubYu/ihHhU+t1UPpxsivg6Od2E2qU4gJCekfEddzRBzHhzA/Acyw/mlK/w==", "dev": true, "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.1.tgz", "integrity": "sha512-4vSVUiOPJLmr45S8rMGy7WDvpWxfFxfP/Qx/cxZFCfvoypTYpPPL1X8VIZMe0WTA+Jr7blUxwUSEZNkjoMTgSw==", "dev": true }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, "acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true }, "acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", "dev": true }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, "bootstrap": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz", "integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew==" }, "browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "dev": true, "requires": { "caniuse-lite": "^1.0.30001449", "electron-to-chromium": "^1.4.284", "node-releases": "^2.0.8", "update-browserslist-db": "^1.0.10" } }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "caniuse-lite": { "version": "1.0.30001466", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001466.tgz", "integrity": "sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==", "dev": true }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, "clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "requires": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" } }, "colorette": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", "dev": true }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "css-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.2.0.tgz", "integrity": "sha512-/rvHfYRjIpymZblf49w8jYcRo2y9gj6rV8UroHGmBxKrIyGLokpycyKzp9OkitvqT29ZSpzJ0Ic7SpnJX3sC8g==", "dev": true, "requires": { "icss-utils": "^5.1.0", "postcss": "^8.2.15", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.1.0", "semver": "^7.3.5" } }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, "electron-to-chromium": { "version": "1.4.328", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.328.tgz", "integrity": "sha512-DE9tTy2PNmy1v55AZAO542ui+MLC2cvINMK4P2LXGsJdput/ThVG9t+QGecPuAZZSgC8XoI+Jh9M1OG9IoNSCw==", "dev": true }, "enhanced-resolve": { "version": "5.12.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "dev": true, "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { "estraverse": "^5.2.0" }, "dependencies": { "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } }, "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "requires": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "exports-loader": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-3.0.0.tgz", "integrity": "sha512-b23Yg5SKR63ZvikGrQgfGgwd40MDehaYb7vOXgD7C0fMV04wS8U1I4f7n1j1wEhtQNKUqgdFox/ol2rOruOpOA==", "dev": true, "requires": { "source-map": "^0.6.1" } }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { "function-bind": "^1.1.1" } }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, "icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true }, "import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", "dev": true, "requires": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" } }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true }, "is-core-module": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", "dev": true, "requires": { "has": "^1.0.3" } }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { "isobject": "^3.0.1" } }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "jquery": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "leaflet": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" }, "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { "p-locate": "^4.1.0" } }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { "yallist": "^4.0.0" } }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "requires": { "mime-db": "1.52.0" } }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, "node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", "dev": true }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { "path-key": "^3.0.0" } }, "onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" } }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { "p-limit": "^2.2.0" }, "dependencies": { "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" } } } }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { "find-up": "^4.0.0" } }, "postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "requires": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, "dependencies": { "nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true } } }, "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", "dev": true }, "postcss-modules-local-by-default": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", "dev": true, "requires": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^6.0.2", "postcss-value-parser": "^4.1.0" } }, "postcss-modules-scope": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.4" } }, "postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, "requires": { "icss-utils": "^5.0.0" } }, "postcss-selector-parser": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", "dev": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "postcss-value-parser": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, "punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { "safe-buffer": "^5.1.0" } }, "rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", "dev": true, "requires": { "resolve": "^1.9.0" } }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, "resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "requires": { "resolve-from": "^5.0.0" } }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, "serialize-javascript": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, "requires": { "randombytes": "^2.1.0" } }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "requires": { "kind-of": "^6.0.2" } }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { "shebang-regex": "^3.0.0" } }, "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, "style-loader": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.2.1.tgz", "integrity": "sha512-1k9ZosJCRFaRbY6hH49JFlRB0fVSbmnyq1iTPjNxUmGVjBNEmwrrHPenhlp+Lgo51BojHSf6pl2FcqYaN3PfVg==", "dev": true }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" } }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true }, "terser": { "version": "5.16.6", "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz", "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" } }, "terser-webpack-plugin": { "version": "5.3.7", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", "terser": "^5.16.5" } }, "update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", "dev": true, "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" } }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "webpack": { "version": "5.76.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" } }, "webpack-cli": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.7.2.tgz", "integrity": "sha512-mEoLmnmOIZQNiRl0ebnjzQ74Hk0iKS5SiEEnpq3dRezoyR3yPaeQZCMCe+db4524pj1Pd5ghZXjT41KLzIhSLw==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.0.4", "@webpack-cli/info": "^1.3.0", "@webpack-cli/serve": "^1.5.1", "colorette": "^1.2.1", "commander": "^7.0.0", "execa": "^5.0.0", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", "rechoir": "^0.7.0", "v8-compile-cache": "^2.2.0", "webpack-merge": "^5.7.3" }, "dependencies": { "commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true } } }, "webpack-merge": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", "dev": true, "requires": { "clone-deep": "^4.0.1", "wildcard": "^2.0.0" } }, "webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } } simplemonitor-1.13.0/simplemonitor/html/package.json000066400000000000000000000010361464501162400226330ustar00rootroot00000000000000{ "name": "html", "version": "1.0.0", "description": "", "private": true, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "css-loader": "^6.2.0", "style-loader": "^3.2.1", "webpack": "^5.76.0", "webpack-cli": "^4.7.2", "exports-loader": "^3.0.0" }, "dependencies": { "@popperjs/core": "^2.9.3", "bootstrap": "^5.1.0", "jquery": "^3.6.0", "leaflet": "^1.7.1" }, "sideEffects": true } simplemonitor-1.13.0/simplemonitor/html/src/000077500000000000000000000000001464501162400211345ustar00rootroot00000000000000simplemonitor-1.13.0/simplemonitor/html/src/index.js000066400000000000000000000016711464501162400226060ustar00rootroot00000000000000window.$ = require("jquery"); window.bootstrap = require("bootstrap"); import "bootstrap/dist/css/bootstrap.min.css"; import "./style.css"; window.too_old = function () { $("#refresh_badge").removeClass("d-none"); $("#refresh_status").addClass("d-inline"); $("#summary").removeClass("border-success border-danger"); $("#summary").addClass("border-warning"); }; window.update_age = function (props) { const now = parseInt(Date.now() / 1000); // ms to s const diff = now - props.timestamp; var updated = "at some point"; if (diff < 10) { updated = "just now"; } else { updated = `${diff} seconds ago`; } // not using Bootstrap tooltip here as it gets stuck if this element is // updated while the tooltip is shown const update_string = `Updated ${updated} by ${props.host} (${props.version})`; $("#updated").html(update_string); $("#updatedfooter").html(update_string); }; simplemonitor-1.13.0/simplemonitor/html/src/maps.js000066400000000000000000000006431464501162400224350ustar00rootroot00000000000000window.L = require("leaflet"); import "leaflet/dist/leaflet.css"; import IconUp from "./marker-single-up.png"; import IconDown from "./marker-single-down.png"; import IconShadow from "./marker-shadow.png"; window.markerIconUp = L.icon({ iconUrl: IconUp, shadowUrl: IconShadow, popupAnchor: [11, 2], }); window.markerIconDown = L.icon({ iconUrl: IconDown, shadowUrl: IconShadow, popupAnchor: [11, 2], }); simplemonitor-1.13.0/simplemonitor/html/src/marker-shadow.png000066400000000000000000000014351464501162400244110ustar00rootroot00000000000000PNG  IHDR))`IDATX[0㔴KYn].B KX$j;tj#'iT93KsR1␶6'[QГ l@F^hg;¹AOhWY0hSuKϨa~ C<{]N@A05H:za8Ր}pObxlOs.6Șoӊ5bpO\M)` _Z\''Er<}V_h}x>(̅b}t񳂨>߅yb{xNF@ףs ?0>Xegx!<5v鍓SOy?[\j&?wy|KLB1EtzX7cI?Jvlr{Y{-^-߸5xN?yՋs {8Q+Y2߼[V?*)fj+f_!Tc#A$>YdHnyjhA3JaDXb%7J)S?55jhSιۓ{%Tr)ܨkh[iG00ko13\0L32lϱ(V^eV_c70ˮ@ KXfơNd ˖{ё9kZ84mTkQϡJwkZ!Zr+;/x&YMڷO,^zP@u{,\NT2li6:h*`_YG-ڢAGpC=3NV}>Pd1 ,b 4Cֲ#elިt^g4Xz } /H->ó ^?8Sl:Y!S]AmXVmT.8R< ;m辏݆ 9KfίMfUۏVmt_]5YwnvȉVoRbsqڎ LFzj2# OT]ra{sܨ.7jhPs(l e-hhW8ٵ&zMX6Hs0Xqhv>AOWdv+o UB-'AԀ}P\ӓ7 EG0Gu!I/+ճƦ=BIRhl ׬ tQ'ܵ]K#]`!ӜcĆg@ 2+PRA9GR]Ir^4Q'8p\/@grF[EvZ*?gx]2NY}yCpO8+aB(ܸ48D(spJAfkY+  \@:DdWT Jߢn-"wX#"#GRt%Y}h4q e ?y/J[d]| 㳰ˡ)M䅟y/EU)jNP4ښI/!51QRpQ#@~@-( ~:13D8 Y? ` \@-@Laf<L-P uFM:$|hAU F;'U0gB P24łoA | |!%+{8PQK}A^Oh"uHB;m ]mI< dV $ نb;$ -V2ZJ$􂜀,8Ng!֩Hm"LRӀH٭)R0;u'9۱YB9쑅" n*HCaP@l0e2S`|#R`QF_$r^z&p+^Xp X˪"͊|q|d%/If'YxJ7W HN_V&ڧFe~(eh(s*Q"hzx|Gce|3C<:mwUJ4)]+NJ( xlO~fa@tCh49!i'EA$2XhfH( 9>q7t7I}80Ew=n? m)" }f6(Sb<]B񰓭Gn" Ҥ{[{ɭ@WQHáf -MB^ O1x5'PĞ*ǩ!*tl[$DDB4m/[ b(R)GdQ_8# 0_C`  y`ihA36 CqA1gX#%bq{XۏҚȯ Mk:"E$ PLa1JKЮ5փdxεM< ۩F-Ja=Rl nMa h`VPW#3ȦfiȆ[LXm}Q]0鮞4 rP#@Q0)DS6:f]X 0u Bb?5ݿtZfUi Z+~Fp;Y#%jŖ!Ъ4¿. HX{ԻBusg35VȔ(hdƊcꓨ}udER X>%Ga2hM$h| U)b*H{rijvgRZ#gD\ND.d6MJκ agiMڐ*B#l]9\(J /j{:~ą0;E*Z%p 3f ^G{ Dӊt"NW)A'" i9`y&ѭ`c*#~-&Qn0&l 5Vي\+Twp[bnB:"z{#2N2^f*u,298AOf{R: G@vœZB# /M$-DzUr8e(;lS]YEyH% VPt(xlECD!Qhi6s2 #~_p^d=>)!fC7eܾm|l7K_BƠuSyAI<30\:td!i!s=E3n0a>㤝41L58x2D!sQ.)e쵀D|jPrOsPk-=|#av~|xL3 $Q24Yn{PJM3>\u}85[N>%-8Uh+bB AF.!S7  5z8KA'=Ҁ/$5Ȋػyd 9YE~ ]||,JFraS. >2˲i2pfj f* AN`C1'uԎ&z}M= ^%-OL n;@-Ra}"t#pZҘN2Fl,OPӎe\MEq^p>cpBiQMy^!Rόp-&-j[4fyRK /'>JJxd i`E3$ \E.lA #}5R``.*OMť^T`e5:5">\]gB|3x$Lri&vґp L6 b-Kp'pAc#x,\ʣv)8! 5yi[F G<=k&_wL _m4Иň?=к37T)t!,xm4ndVQ AwX&@e$3LZ~<:nu4暶xӴJ\ Tד@ajދL)~nM\UcO\ OBxB2ky!'+Lsl %sguK̺0rV}i39-*'c #-RDt<܎Mԗֲtwq6Wy|$׭Sv"XlV 55 EnT:UpjS"kIOPB %jl6fkhFmk5 yJ4K'ce&~a`M޸2 !5z#- UCb<}gBUrG)ЄS?Ӷ5^`&"_g=Pޯp8}nJEJ07A$V_>S76+aco 邐hPJ=ŅK (}?[ezTXtRaw profile type iptcx=LA0 fnYol lN*(]ㄣ/[!60AI= ,o iCCPICC profilex}=HPOjE*"vqPĂTJ[UMGbYWWAqssRtK -bxsx>@jUT<&fs+b>tH <랺<˻Q&|", xxz9YIRω #e8xfȤb6f%C%"(FBeg\e{r봆"HB*6P()&Rt:$drmc?YpB1Ŷ?.Шm7N3p:0IzEm⺥{0KH~ZB7[{՝[@ft#E^xwWi;reiTXtXML:com.adobe.xmp P[2gAMA abKGD u7 pHYs  tIME0!8IDATX - j j j \j \j \j \j\\\ G?!!\fjj!!\\\ff#&#& \g#%\\#%\\\ \j ;jCkj;kjkjjk \j \j \j kkk  Z,gIENDB`simplemonitor-1.13.0/simplemonitor/html/src/marker-single-up.png000066400000000000000000000043041464501162400250250ustar00rootroot00000000000000PNG  IHDR)tEXtSoftwareAdobe ImageReadyqe<fiTXtXML:com.adobe.xmp IDATxڬW[h\U1$f:$Amɇ JcOATC6 hmֈ` mmWG+m"X:3I,ئiW>̝L&s9k?gAs2҅o18^~Vy }h 69>;_HPgG |>_QrWLH.h=\!;:QMQSW# mKUL\I5(W1Re=2]$-+؉A #X?%ozY/IJPbϊ@{bӈ~ ܺ>Y%勡bH^'FUFy '~БOEL5baY!YȫTI|V )MW\:)AWOzm,߮]yFi>ơ,H%I+Ҏ ZZ(Xqs7WޞP#Cֶ&Xu5ܻuܠ^$*nn> O鋷:QTLpY-u@ʱRk]])fk}-k*[y[.\J5Yo"c&6Ϩe1u+޳JJJ2 2~ (WFNX9+r}eA&oŻ"E1KY} wj_5Cm}V? +T6IENDB`simplemonitor-1.13.0/simplemonitor/html/src/style.css000066400000000000000000000000561464501162400230070ustar00rootroot00000000000000.table { font-size: 12px !important; } simplemonitor-1.13.0/simplemonitor/html/status-template.html000066400000000000000000000135101464501162400243670ustar00rootroot00000000000000 {{status}}@{{host}} monitor {%- if map -%} {%- endif %} {%- if map -%}
{%- else %} {% macro table_row(entry) -%} {{entry.status_text}} {% if entry.failures == 0 %} {% else %} {% endif %} {%- endmacro -%} {% for entry in fail_entries -%} {{table_row(entry)}} {% endfor -%} {% for entry in ok_entries -%} {{table_row(entry)}} {% endfor -%}
Monitor Status Host Failed at VFC Up/downtime Detail Failures Last Failure Age
{{entry.monitor_name}} {{entry.host}} {{entry.fail_time}} {% if entry.fail_count %}{{entry.fail_count}}{% endif %} {{entry.downtime}} ({{"%.2f" | format(entry.availability * 100)}}%) {{entry.fail_data}}{{entry.failures}} {{entry.last_failure}}{% if not entry.my_host %}{{entry.age}}{% endif %}
{%- endif %}
SimpleMonitor {{version}} » Documentation | » Code
simplemonitor-1.13.0/simplemonitor/html/webpack.config.js000066400000000000000000000006741464501162400235720ustar00rootroot00000000000000module.exports = { entry: { main: "./src/index.js", maps: "./src/maps.js", }, output: { filename: "[name].bundle.js", }, module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"], }, { test: /\.png$/, type: "asset/resource", }, ], }, resolve: { alias: { jquery: "jquery/dist/jquery.slim.js", }, }, mode: "production", }; simplemonitor-1.13.0/simplemonitor/monitor.py000066400000000000000000000204641464501162400214500ustar00rootroot00000000000000# coding=utf-8 """A (fairly) simple host/service monitor. """ import argparse import logging import os import sys from .simplemonitor import SimpleMonitor from .version import VERSION try: import colorlog except ImportError: pass main_logger = logging.getLogger("simplemonitor") def main() -> None: r"""This is where it happens \o/""" parser = argparse.ArgumentParser() parser.add_argument( "--version", action="version", version="%(prog)s {}".format(VERSION) ) output_group = parser.add_argument_group(title="Output controls") testing_group = parser.add_argument_group(title="Test and debug tools") output_group.add_argument( "-v", "--verbose", action="store_true", dest="verbose", default=False, help="Alias for --log-level=info", ) output_group.add_argument( "-q", "--quiet", action="store_true", dest="quiet", default=False, help="Alias for --log-level=critical", ) testing_group.add_argument( "-t", "--test", action="store_true", dest="test", default=False, help="Test config and exit", ) parser.add_argument( "-p", "--pidfile", dest="pidfile", default=None, help="Write PID into this file" ) parser.add_argument( "-N", "--no-network", dest="no_network", default=False, action="store_true", help="Disable network listening socket (if enabled in config)", ) output_group.add_argument( "-d", "--debug", dest="debug", default=False, action="store_true", help="Alias for --log-level=debug", ) parser.add_argument( "-f", "--config", dest="config", default="monitor.ini", help=( "configuration file (this is the main config; " "you also need monitors.ini (default filename))" ), ) parser.add_argument( "-j", "--threads", dest="threads", default=os.cpu_count(), # default used by the library anyway type=int, help=( f"number of threads to run for checking monitors (default (cpus): {os.cpu_count()})" ), ) output_group.add_argument( "-H", "--no-heartbeat", action="store_true", dest="no_heartbeat", default=False, help="Omit printing the '.' character when running checks", ) testing_group.add_argument( "-1", "--one-shot", action="store_true", dest="one_shot", default=False, help=( "Run the monitors once only, without alerting. Require monitors without " '"fail" in the name to succeed. Exit zero or non-zero accordingly' ), ) testing_group.add_argument( "--loops", dest="loops", default=-1, type=int, help="Number of iterations to run before exiting", ) output_group.add_argument( "-l", "--log-level", dest="loglevel", default="warn", help="Log level: critical, error, warn, info, debug", ) output_group.add_argument( "-C", "--no-colour", "--no-color", action="store_true", dest="no_colour", default=False, help="Do not colourise log output", ) output_group.add_argument( "--no-timestamps", action="store_true", dest="no_timestamps", default=False, help="Do not prefix log output with timestamps", ) testing_group.add_argument( "--dump-known-resources", action="store_true", dest="dump_resources", default=False, help="Print out loaded Monitor, Alerter and Logger types", ) options = parser.parse_args() if options.dump_resources: import pprint import simplemonitor.Alerters.alerter as alerter import simplemonitor.Loggers.logger as logger import simplemonitor.Monitors.monitor as monitor print("Monitors:") pprint.pprint(sorted(monitor.all_types()), compact=True) print("Loggers:") pprint.pprint(sorted(logger.all_types()), compact=True) print("Alerters:") pprint.pprint(sorted(alerter.all_types()), compact=True) sys.exit(0) if options.quiet: options.loglevel = "critical" if options.verbose: options.loglevel = "info" if options.debug: options.loglevel = "debug" if options.no_timestamps: logging_timestamp = "" else: logging_timestamp = "%(asctime)s " try: log_level = getattr(logging, options.loglevel.upper()) except AttributeError: print("Log level {0} is unknown".format(options.loglevel)) sys.exit(1) log_datefmt = "%Y-%m-%d %H:%M:%S" log_plain_format = logging_timestamp + "%(levelname)8s (%(name)s) %(message)s" if not options.no_colour: try: handler = colorlog.StreamHandler() handler.setFormatter( colorlog.ColoredFormatter( logging_timestamp + "%(log_color)s%(levelname)8s%(reset)s (%(name)s) %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) ) main_logger.addHandler(handler) except NameError: logging.basicConfig(format=log_plain_format, datefmt=log_datefmt) main_logger.error("Could not enable colorlog") else: logging.basicConfig(format=log_plain_format, datefmt=log_datefmt) main_logger.setLevel(log_level) if not options.quiet: main_logger.info("=== SimpleMonitor v%s", VERSION) main_logger.info("Loading main config from %s", options.config) m = SimpleMonitor( config_file=options.config, no_network=options.no_network, max_loops=options.loops, heartbeat=not options.no_heartbeat, one_shot=options.one_shot, max_workers=options.threads, ) if options.test: main_logger.warning("Config test complete. Exiting.") sys.exit(0) if options.one_shot: main_logger.warning( "One-shot mode: expecting monitors without 'fail' in the name to succeed, " "and with to fail. Will exit zero or non-zero accordingly." ) m.run() main_logger.info("Finished.") if options.one_shot: # pragma: no cover ok = True print("\n--> One-shot results:") tail_info = [] for this_monitor in sorted(m.monitors.keys()): if "fail" in this_monitor: if m.monitors[this_monitor].error_count == 0: tail_info.append( " Monitor {0} should have failed".format(this_monitor) ) tail_info.append( " {}".format(m.monitors[this_monitor].last_result) ) ok = False else: print(" Monitor {0} was ok (failed)".format(this_monitor)) elif "skip" in this_monitor: if m.monitors[this_monitor].skipped(): print(" Monitor {0} was ok (skipped)".format(this_monitor)) else: tail_info.append( " Monitor {0} should have been skipped".format(this_monitor) ) ok = False else: if m.monitors[this_monitor].error_count > 0: tail_info.append( " Monitor {0} failed and shouldn't have: {1}".format( this_monitor, m.monitors[this_monitor].last_result ) ) ok = False tail_info.append( " {}".format(m.monitors[this_monitor].last_result) ) else: print(" Monitor {0} was ok".format(this_monitor)) if len(tail_info): print() for line in tail_info: print(line) if not ok: print("Not all non-'fail' succeeded, or not all 'fail' monitors failed.") sys.exit(1) logging.shutdown() if __name__ == "__main__": main() simplemonitor-1.13.0/simplemonitor/simplemonitor.py000066400000000000000000001060321464501162400226560ustar00rootroot00000000000000# coding=utf-8 """Execution logic for SimpleMonitor.""" import concurrent.futures import copy import logging import os import signal import sys import time from pathlib import Path from socket import gethostname from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, cast from .Alerters.alerter import Alerter from .Alerters.alerter import all_types as all_alerter_types from .Alerters.alerter import get_class as get_alerter_class from .Loggers.logger import Logger from .Loggers.logger import all_types as all_logger_types from .Loggers.logger import get_class as get_logger_class from .Loggers.network import Listener from .Monitors.compound import CompoundMonitor from .Monitors.monitor import Monitor, MonitorState from .Monitors.monitor import all_types as all_monitor_types from .Monitors.monitor import get_class as get_monitor_class from .util import check_group_match, get_config_dict from .util.envconfig import EnvironmentAwareConfigParser module_logger = logging.getLogger("simplemonitor") class SimpleMonitor: """A fairly simple monitor.""" def __init__( self, config_file: Union[str, Path], *, hup_file: Optional[Path] = None, no_network: bool = False, max_loops: int = -1, heartbeat: bool = True, one_shot: bool = False, max_workers: Optional[int] = None, ) -> None: """Main class turn on.""" if isinstance(config_file, str): self._config_file = Path(config_file) elif isinstance(config_file, Path): self._config_file = config_file else: raise ValueError("config_file must be str or Path") self.monitors = {} # type: Dict[str, Monitor] self.failed = [] # type: List[str] self.still_failing = [] # type: List[str] self.skipped = [] # type: List[str] self.warning = [] # type: List[str] self.remote_monitors = {} # type: Dict[str, Dict[str, Monitor]] self.loggers = {} # type: Dict[str, Logger] self.alerters = {} # type: Dict[str, Alerter] self._hup_file = hup_file self._need_hup = False self._hup_timestamp = None # type: Optional[float] self._no_network = no_network self._remote_listening_thread = None # type: Optional[Listener] self._max_loops = max_loops self.heartbeat = heartbeat self.one_shot = one_shot self.pidfile = None # type: Optional[str] self._max_workers = max_workers self._setup_signals() self._load_config() def _load_config(self) -> None: """Load config, monitors, alerters and loggers.""" config = EnvironmentAwareConfigParser() if not self._config_file.exists(): raise RuntimeError( "Configuration file {} does not exist".format(self._config_file) ) config.read(self._config_file) self.interval = config.getint("monitor", "interval") self.pidfile = config.get("monitor", "pidfile", fallback=None) hup_file = config.get("monitor", "hup_file", fallback=None) if hup_file is not None: self._hup_file = Path(hup_file) module_logger.info( "Watching modification time of %s; increase it to trigger a config reload", hup_file, ) self._check_hup_file() if not self._no_network and config.get( "monitor", "remote", fallback="0" ).lower() in ["1", "true", "yes"]: self._network = True self._remote_port = int(config.get("monitor", "remote_port")) self._network_key = config.get("monitor", "key", fallback=None) self._network_bind_host = config.get("monitor", "bind_host", fallback="") self._ipv4_only = cast( bool, config.get("monitor", "ipv4_only", fallback=False) ) else: self._network = False monitors_files = [ Path(config.get("monitor", "monitors", fallback="monitors.ini")) ] monitors_dir = config.get("monitor", "monitors_dir", fallback=None) if monitors_files[0] == Path("."): module_logger.debug("No main monitors.ini file specified") monitors_files.pop(0) elif not monitors_files[0].exists(): raise RuntimeError( f"Monitors configuration file '{monitors_files[0]}' does not exist" ) if monitors_dir: monitors_files.extend(list(sorted(Path(monitors_dir).glob("*.ini")))) self._load_monitors(monitors_files) count = self.count_monitors() if count == 0: module_logger.critical("No monitors loaded") self._load_loggers(config) self._load_alerters(config) if not self._verify_dependencies(): raise RuntimeError("Broken dependency configuration") if not self.verify_alerting(): module_logger.warning("No alerters defined and no remote logger found") def _start_network_thread(self) -> None: if self._remote_listening_thread: # if the thread is running, check if it should be if not self._network: module_logger.info("Stopping remote listener thread") self._remote_listening_thread.running = False return if self._network: module_logger.info("Starting remote listener thread") self._remote_listening_thread = Listener( self, self._remote_port, self._network_key, bind_host=self._network_bind_host, ipv4_only=self._ipv4_only, ) self._remote_listening_thread.start() def _load_monitors(self, filenames: Sequence[Union[Path, str]]) -> None: """Load all the monitors from the config file.""" config = EnvironmentAwareConfigParser() config.read(filenames) module_logger.info( "Loaded monitor config from: %s", ", ".join(map(str, filenames)), ) monitors = config.sections() if "defaults" in monitors: default_config = get_config_dict(config, "defaults") monitors.remove("defaults") else: default_config = {} myhostname = gethostname().lower() module_logger.info("=== Loading monitors") for this_monitor in monitors: if config.has_option(this_monitor, "runon"): if myhostname != config.get(this_monitor, "runon").lower(): module_logger.warning( "Ignoring monitor %s because it's only for host %s", this_monitor, config.get(this_monitor, "runon"), ) continue monitor_type = config.get(this_monitor, "type") new_monitor = None config_options = default_config.copy() config_options.update(get_config_dict(config, this_monitor)) if self.has_monitor(this_monitor): if self.monitors[this_monitor].monitor_type == config_options["type"]: module_logger.info( "Updating configuration for monitor %s", this_monitor ) self.update_monitor_config(this_monitor, config_options) else: module_logger.error( "Cannot update monitor %s from type %s to type %s. " "Keeping original config for this monitor.", this_monitor, self.monitors[this_monitor].monitor_type, config_options["type"], ) continue try: cls = get_monitor_class(monitor_type) except KeyError: module_logger.error( "Unknown monitor type %s; valid types are: %s", monitor_type, ", ".join(all_monitor_types()), ) continue new_monitor = cls(this_monitor, config_options) # new_monitor.set_mon_refs(m) module_logger.info( "Adding %s monitor %s: %s", monitor_type, this_monitor, new_monitor ) self.add_monitor(this_monitor, new_monitor) for monitor in self.monitors.values(): monitor.set_mon_refs(self.monitors) monitor.post_config_setup() self.prune_monitors(monitors) module_logger.info("--- Loaded %d monitors", self.count_monitors()) def _load_loggers(self, config: EnvironmentAwareConfigParser) -> None: """Load the loggers listed in the config object.""" if config.has_option("reporting", "loggers"): loggers = config.get("reporting", "loggers").split(",") else: loggers = [] module_logger.info("=== Loading loggers") for config_logger in loggers: logger_type = config.get(config_logger, "type") config_options = get_config_dict(config, config_logger) config_options["_name"] = config_logger if self.has_logger(config_logger): if self.loggers[config_logger].logger_type == config_options["type"]: module_logger.info( "Updating configuration for logger %s", config_logger ) self.update_logger_config(config_logger, config_options) else: module_logger.error( "Cannot update logger %s from type %s to type %s. " "Keeping original config for this logger.", config_logger, self.loggers[config_logger].logger_type, config_options["type"], ) continue try: logger_cls = get_logger_class(logger_type) except KeyError: module_logger.error( "Unknown logger type %s; valid types are: %s", logger_type, ", ".join(all_logger_types()), ) continue new_logger = logger_cls(config_options) # type: Logger new_logger.set_global_info( {"interval": config.getint("monitor", "interval")} ) module_logger.info( "Adding %s logger %s: %s", logger_type, config_logger, new_logger ) self.add_logger(config_logger, new_logger) del new_logger self.prune_loggers(loggers) module_logger.info("--- Loaded %d loggers", len(self.loggers)) def _load_alerters(self, config: EnvironmentAwareConfigParser) -> None: """Load the alerters listed in the config object.""" if config.has_option("reporting", "alerters"): alerters = config.get("reporting", "alerters").split(",") else: alerters = [] module_logger.info("=== Loading alerters") for this_alerter in alerters: alerter_type = config.get(this_alerter, "type") config_options = get_config_dict(config, this_alerter) if self.has_alerter(this_alerter): if self.alerters[this_alerter].alerter_type == config_options["type"]: module_logger.info( "Updating configuration for alerter %s", this_alerter ) self.update_alerter_config(this_alerter, config_options) else: module_logger.error( "Cannot update alerter %s from type %s to type %s. " "Keeping original config for this alerter.", this_alerter, self.alerters[this_alerter].alerter_type, config_options["type"], ) continue try: alerter_cls = get_alerter_class(alerter_type) except KeyError: module_logger.error( "Unknown alerter type %s; valid types are: %s", alerter_type, ", ".join(all_alerter_types()), ) continue new_alerter = alerter_cls(config_options) # type: Alerter module_logger.info( "Adding %s alerter %s: %s", alerter_type, this_alerter, new_alerter.describe(), ) new_alerter.name = this_alerter self.add_alerter(this_alerter, new_alerter) del new_alerter self.prune_alerters(alerters) module_logger.info("--- Loaded %d alerters", len(self.alerters)) def _setup_signals(self) -> None: """Set up the SIGHUP handler.""" _message = ( "Unable to trap SIGHUP... maybe it doesn't exist on this platform. " "Set 'hup_file' in config and touch that file to trigger a config reload." ) try: signal.signal(signal.SIGHUP, self._handle_sighup) except ValueError: # pragma: no cover module_logger.warning(_message) except AttributeError: # pragma: no cover module_logger.warning(_message) def _handle_sighup(self, *_: Any) -> None: """Receive SIGHUP and process it.""" module_logger.warning("Received SIGHUP") self._need_hup = True def _check_hup_file(self) -> bool: """Check a file's timestamp, and if it's newer than last time, treat it the same as receiving SIGHUP so that a reload is triggered. This allows config reloading on platforms which don't support the signal (i.e. Windows)""" if self._hup_file is None: return False try: statinfo = os.stat(self._hup_file) except IOError: module_logger.debug( "Could not call stat() on path %s for file-based HUP", self._hup_file ) return False modification_time = statinfo.st_mtime if self._hup_timestamp is None: self._hup_timestamp = modification_time return True if modification_time > self._hup_timestamp: self._hup_timestamp = modification_time return True return False def _create_pid_file(self) -> None: if self.pidfile: my_pid = os.getpid() try: with open(self.pidfile, "w") as file_handle: file_handle.write("%d\n" % my_pid) except IOError: module_logger.error("Couldn't write to pidfile!") self.pidfile = None def _remove_pid_file(self) -> None: if self.pidfile: try: os.unlink(self.pidfile) except OSError: module_logger.error("Couldn't remove pidfile!") def add_monitor(self, name: str, monitor: Monitor) -> None: """Add a monitor.""" self.monitors[name] = monitor def update_monitor_config(self, name: str, config_options: dict) -> None: """Update the configuration for a monitor.""" self.monitors[name].__init__(name, config_options) # type: ignore def update_logger_config(self, name: str, config_options: dict) -> None: """Update the configration for a logger.""" self.loggers[name].__init__(config_options) # type: ignore def update_alerter_config(self, name: str, config_options: dict) -> None: """Update the configuration for an alerter.""" self.alerters[name].__init__(config_options) # type: ignore def has_monitor(self, monitor: str) -> bool: """Check if a montitor is known.""" return monitor in self.monitors.keys() def has_logger(self, logger: str) -> bool: """Check if a logger is known.""" return logger in self.loggers.keys() def has_alerter(self, alerter: str) -> bool: """Check if an alerter is known.""" return alerter in self.alerters.keys() def reset_monitors(self) -> None: """Clear all all monitors' dependency info back to default.""" for key in list(self.monitors.keys()): self.monitors[key].reset_dependencies() self.monitors[key].ran_this_time = False def _verify_dependencies(self) -> bool: """Check if all monitors have valid dependencies.""" ok = True monitors = self.monitors.keys() for key, monitor in self.monitors.items(): for dependency in monitor.dependencies: if dependency not in monitors: module_logger.critical( "Configuration error: dependency %s of monitor %s is not defined!", dependency, key, ) ok = False return ok def verify_alerting(self) -> bool: """Sanity check the configuration to see if we have at least an alerter, or network logging.""" sane = True if len(self.alerters) == 0: for _, logger in self.loggers.items(): if logger.logger_type == "network": break else: sane = False return sane def sort_joblist(self, joblist: List[str]) -> List[str]: """Order a list of monitors so that compound monitors are at the end""" new_list = [] # type: List[str] late_list = [] # type: List[str] for monitor in joblist: if self.monitors[monitor].monitor_type in ["compound"]: late_list.append(monitor) else: new_list.append(monitor) new_list.extend(late_list) return new_list def _failed_monitors(self) -> List[str]: """Return a list of the currently-failed monitors. Includes monitors which are disabled.""" failed = [ key for key, monitor in self.monitors.items() if monitor.state() == MonitorState.FAILED or not monitor.enabled ] return failed @staticmethod def _run_monitor(monitor: Monitor) -> bool: """Run a single monitor.""" did_run = False try: if monitor.should_run(): did_run = True monitor.ran_this_time = True start_time = time.time() monitor.run_test() end_time = time.time() monitor.last_run_duration = int(end_time - start_time) else: monitor.record_skip(None) module_logger.info("Not run: %s", monitor.name) except Exception as exception: module_logger.exception( "Monitor %s threw exception during run_test()", monitor.name ) monitor.record_fail("Unhandled exception: {}".format(exception)) if monitor.error_count > 0: if monitor.virtual_fail_count() == 0: module_logger.warning( "monitor failed but within tolerance: %s (%s)", monitor.name, monitor.last_result, ) else: module_logger.error( "monitor failed: %s (%s)", monitor.name, monitor.last_result, ) return False if not did_run: return False module_logger.info("monitor passed: %s", monitor.name) return True def run_tests(self) -> None: """Run the tests for all the monitors.""" self.reset_monitors() joblist = [k for (k, v) in self.monitors.items() if v.enabled] while joblist: new_joblist, skiplist = self._prepare_lists(joblist) joblist = self.sort_joblist(joblist) with concurrent.futures.ThreadPoolExecutor( max_workers=self._max_workers ) as executor: future_to_monitor = {} for monitor in joblist: if monitor in new_joblist or monitor in skiplist: module_logger.debug( "Skipping monitor %s because it's in the new job list or the skiplist", monitor, ) continue module_logger.debug("Trying monitor: %s", monitor) future_to_monitor[ executor.submit(self._run_monitor, self.monitors[monitor]) ] = monitor if len(future_to_monitor) == 0: module_logger.error("No more monitors are runnable!") return for future in concurrent.futures.as_completed(future_to_monitor): monitor = future_to_monitor[future] try: if future.result(): for monitor2 in joblist: self.monitors[monitor2].dependency_succeeded(monitor) except Exception: module_logger.exception( "Exception for monitor %s during thread execution", monitor ) joblist = copy.copy(new_joblist) def _prepare_lists(self, joblist: List[str]) -> Tuple[List[str], List[str]]: failed = self._failed_monitors() module_logger.debug( "Starting loop with joblist %s and failed list %s", ", ".join(joblist), ", ".join(failed), ) new_joblist = [] # List[str] skiplist = [] # List[str] for monitor in joblist: if monitor in failed: module_logger.error( "Received a failed logger in the joblist: %s", monitor ) continue if self.monitors[monitor].monitor_type == "compound": # special case handling for compound monitors compound_monitor = cast(CompoundMonitor, self.monitors[monitor]) needed_monitors = set(compound_monitor.monitors) remaining_monitors = needed_monitors & set(joblist) if remaining_monitors: module_logger.debug( "Added compound monitor %s to new joblist due to outstanding deps %s", monitor, remaining_monitors, ) new_joblist.append(monitor) continue if self.monitors[monitor].remaining_dependencies: # this monitor has outstanding deps, put it on the new joblist for next loop failed_deps = set( self.monitors[monitor].remaining_dependencies ).intersection(failed) if len(failed_deps) > 0: module_logger.warning( "Monitor %s has failed dependencies %s, skipping", monitor, ", ".join(failed_deps), ) self.monitors[monitor].record_skip(", ".join(failed_deps)) skiplist.append(monitor) else: new_joblist.append(monitor) module_logger.debug( "Added %s to new joblist due to outstanding deps %s", monitor, ", ".join(self.monitors[monitor].remaining_dependencies), ) continue return (new_joblist, skiplist) def log_result(self, logger: Logger) -> None: """Use the given logger object to log our state.""" logger.check_dependencies(self.failed + self.still_failing + self.skipped) with logger: for key, monitor in self.monitors.items(): if not check_group_match(monitor.group, logger.groups): module_logger.debug( "not logging for %s due to group mismatch (monitor in group %s, " "logger has groups %s", key, monitor.group, logger.groups, ) continue if logger.heartbeat and not monitor.ran_this_time: module_logger.debug( "not logging for %s as this logger is in heartbeat mode and" " the monitor did not run this loop", key, ) continue logger.save_result2(key, monitor) try: # need to work on a copy here to prevent the dicts changing under us # during the loop, as remote instances can connect and update our data # unpredictably for host_monitors in self.remote_monitors.copy().values(): for name, monitor in host_monitors.copy().items(): if check_group_match(monitor.group, logger.groups): logger.save_result2(name, monitor) else: module_logger.debug( "not logging for %s due to group mismatch (monitor in group %s, " "logger has groups %s", name, monitor.group, logger.groups, ) except Exception: # pragma: no cover module_logger.exception("exception while logging remote monitors") def do_alert(self, alerter: Alerter) -> None: """Use the given alerter object to send an alert, if needed.""" alerter.check_dependencies(self.failed + self.still_failing + self.skipped) for name, this_monitor in list(self.monitors.items()): # Don't generate alerts for monitors which want it done remotely if this_monitor.remote_alerting: module_logger.debug( "skipping alert for monitor %s as it wants remote alerting", name ) continue try: if this_monitor.notify: alerter.send_alert(name, this_monitor) else: module_logger.warning("monitor %s has notifications disabled", name) except Exception: # pragma: no cover module_logger.exception("exception caught while alerting for %s", name) for host_monitors in self.remote_monitors.copy().values(): for name, monitor in host_monitors.copy().items(): try: if monitor.remote_alerting: alerter.send_alert(name, monitor) else: module_logger.debug( "not alerting for monitor %s as it doesn't want remote alerts", name, ) except Exception: # pragma: no cover module_logger.exception( "exception caught while alerting for remote monitor %s", name ) def count_monitors(self) -> int: """Gets the number of monitors we have defined.""" return len(self.monitors) def add_alerter(self, name: str, alerter: Alerter) -> None: """Add an alerter.""" if isinstance(alerter, Alerter): self.alerters[name] = alerter else: module_logger.critical( "Failed to add alerter because it is not the right type" ) def add_logger(self, name: str, logger: Logger) -> None: """Add a logger.""" if isinstance(logger, Logger): self.loggers[name] = logger else: module_logger.critical( "Failed to add logger because it is not the right type" ) def prune_monitors(self, retain: List[str]) -> None: """Remove monitors which are in our list but not in the list passed to us. Used to tidy up after a config reload (which may have removed monitors)""" delete_list = [] for monitor in self.monitors: if monitor not in retain: module_logger.info("Removing monitor %s", monitor) delete_list.append(monitor) for monitor in delete_list: del self.monitors[monitor] if not self._verify_dependencies(): module_logger.critical( "Broken dependencies after pruning monitors, aborting!" ) sys.exit(1) def prune_alerters(self, retain: List[str]) -> None: """Remove alerters which are in our list but not in the list passed to us. Used to tidy up after a config reload (which may have removed alerters)""" delete_list = [] for alerter in self.alerters: if alerter not in retain: module_logger.info("Removing alerter %s", alerter) delete_list.append(alerter) for alerter in delete_list: del self.alerters[alerter] def prune_loggers(self, retain: List[str]) -> None: """Remove loggers which are in our list but not in the list passed to us. Used to tidy up after a config reload (which may have removed logger)""" delete_list = [] for logger in self.loggers: if logger not in retain: module_logger.info("Removing logger %s", logger) delete_list.append(logger) for logger in delete_list: del self.loggers[logger] def do_alerts(self) -> None: """Run the alert process for each alerter.""" for alerter in self.alerters.values(): self.do_alert(alerter) def do_recovery(self) -> None: """Attempt recovery for each monitor.""" for monitor in self.monitors.values(): monitor.attempt_recover() def do_recovered(self) -> None: """Run the recovered action for each monitor.""" for monitor in self.monitors.values(): monitor.run_recovered() def hup_loggers(self) -> None: """Inform each logger they need to HUP.""" for logger in self.loggers.values(): logger.hup() def do_logs(self) -> None: """Log result for each logger.""" for logger in self.loggers.values(): self.log_result(logger) def update_remote_monitor(self, data: Dict[str, dict], hostname: str) -> None: """Process a list of monitors received from a remote host.""" seen_monitors = [] # type: List[str] if hostname not in self.remote_monitors: self.remote_monitors[hostname] = {} for name, state in data.items(): module_logger.info( "updating remote monitor %s from host %s", name, hostname ) if isinstance(state, dict): try: remote_monitor = get_monitor_class( state["cls_type"] ).from_python_dict(state["data"]) self.remote_monitors[hostname][name] = remote_monitor seen_monitors.append(name) except KeyError: module_logger.exception( "Could not add remote monitor from host %s; " "possibly a monitor type we don't know?", hostname, ) else: module_logger.critical( "Could not deserialize state of monitor %s. " "If the remote host uses an old version of " "simplemonitor, you need to upgrade.", name, ) self._trim_remote_monitors(hostname, seen_monitors) def _trim_remote_monitors(self, hostname: str, seen_monitors: List[str]) -> None: """Remove remote monitors for a host which aren't in the given list.""" host_monitors = self.remote_monitors[hostname] forget_monitors = [] for name in host_monitors.keys(): if name not in seen_monitors: module_logger.info( "forgetting remote monitor %s from host %s", name, hostname ) forget_monitors.append(name) for name in forget_monitors: del self.remote_monitors[hostname][name] def run_loop(self) -> None: """Run the complete monitor loop once.""" module_logger.debug("Running tests") self.run_tests() module_logger.debug("Running recovery") self.do_recovery() self.do_recovered() module_logger.debug("Running alerts") self.do_alerts() module_logger.debug("Running logs") self.do_logs() module_logger.debug("Loop complete") def run(self) -> None: self._create_pid_file() self._start_network_thread() module_logger.info( "=== Starting... (loop runs every %ds) Hit ^C to stop", self.interval ) loop = True loops = self._max_loops heartbeat = True while loop: try: if loops > 0: loops -= 1 if loops == 0: module_logger.warning( "Ran out of loop counter, will stop after this one" ) loop = False if self._need_hup or self._check_hup_file(): try: module_logger.warning("Reloading configuration") self._load_config() self._start_network_thread() self.hup_loggers() self._need_hup = False except Exception: module_logger.exception("Error while reloading configuration") sys.exit(1) self.run_loop() if ( module_logger.level in ["error", "critical", "warn"] and self.heartbeat ): heartbeat = not heartbeat if heartbeat: sys.stdout.write(".") sys.stdout.flush() except KeyboardInterrupt: module_logger.info("Received ^C") loop = False except Exception: module_logger.exception("Caught unhandled exception during main loop") if loop and self._network: if ( self._remote_listening_thread and not self._remote_listening_thread.is_alive() ): module_logger.error("Listener thread died :(") self._start_network_thread() if self.one_shot: break try: if loop: time.sleep(self.interval) except Exception: module_logger.info("Quitting") loop = False self._remove_pid_file() simplemonitor-1.13.0/simplemonitor/util/000077500000000000000000000000001464501162400203565ustar00rootroot00000000000000simplemonitor-1.13.0/simplemonitor/util/__init__.py000066400000000000000000000230561464501162400224750ustar00rootroot00000000000000"""Utilities for SimpleMonitor.""" import datetime import os import shutil import socket from enum import Enum from typing import Any, Callable, Dict, List, Optional, Tuple, Union import arrow from .envconfig import EnvironmentAwareConfigParser class MonitorConfigurationError(ValueError): """A config error for a Monitor""" class AlerterConfigurationError(ValueError): """A config error for an Alerter""" class LoggerConfigurationError(ValueError): """A config error for a Logger""" class SimpleMonitorConfigurationError(ValueError): """A general config error""" class MonitorState(Enum): """Represent the state of a Monitor.""" UNKNOWN = 0 # state not known yet SKIPPED = 1 # monitor was skipped OK = 2 # monitor is ok FAILED = 3 # monitor has failed class UpDownTime: """Represent an up- or downtime""" days = 0 hours = 0 minutes = 0 seconds = 0 def __init__( self, days: int = 0, hours: int = 0, minutes: int = 0, seconds: int = 0 ) -> None: if not isinstance(days, int): raise TypeError("days must be an int") if not isinstance(hours, int): raise TypeError("days must be an int") if not isinstance(minutes, int): raise TypeError("days must be an int") if not isinstance(seconds, int): raise TypeError("days must be an int") self.days = days self.hours = hours self.minutes = minutes self.seconds = seconds if self.seconds >= 60: temp_min, self.seconds = divmod(self.seconds, 60) self.minutes += temp_min if self.minutes >= 60: temp_hour, self.minutes = divmod(self.minutes, 60) self.hours += temp_hour if self.hours >= 24: temp_day, self.hours = divmod(self.hours, 24) self.days += temp_day def __str__(self) -> str: """Format as d+h:m:s""" return "{}+{:02}:{:02}:{:02}".format( self.days, self.hours, self.minutes, int(self.seconds) ) def __repr__(self) -> str: return "<{}: {}>".format(self.__class__, self.__str__()) def __eq__(self, other: object) -> bool: if not isinstance(other, UpDownTime): return NotImplemented if ( self.days == other.days and self.hours == other.hours and self.minutes == other.minutes and self.seconds == other.seconds ): return True return False @staticmethod def from_timedelta(td: datetime.timedelta) -> "UpDownTime": """Generate an UpDownTime from a timedelta object""" if td is None: return UpDownTime() downtime_seconds = td.seconds (hours, minutes) = (0, 0) if downtime_seconds > 3600: (hours, downtime_seconds) = divmod(downtime_seconds, 3600) if downtime_seconds > 60: (minutes, downtime_seconds) = divmod(downtime_seconds, 60) return UpDownTime(td.days, hours, minutes, downtime_seconds) def get_config_option( config_options: Dict[str, Any], key: str, *, default: Any = None, required: bool = False, required_type: str = "str", allowed_values: Any = None, allow_empty: bool = True, minimum: Optional[Union[int, float]] = None, maximum: Optional[Union[int, float]] = None, ) -> Union[None, str, int, float, bool, List[str], List[int]]: """Get a value out of a dict, with possible default, required type and requiredness.""" if not isinstance(config_options, dict): raise TypeError("config_options should be a dict") value = config_options.get(key, default) if required and value is None: raise ValueError(f"config option {key} is missing and is required") if isinstance(value, str) and required_type: if required_type == "str" and value == "" and not allow_empty: raise ValueError(f"config option {key} cannot be empty") if required_type in ["int", "float"]: try: if required_type == "int": value = int(value) else: value = float(value) except TypeError as error: raise TypeError( f"config option {key} needs to be an {required_type}" ) from error if minimum is not None and value < minimum: raise ValueError(f"config option {key} needs to be >= {minimum}") if maximum is not None and value > maximum: raise ValueError(f"config option {key} needs to be <= {minimum}") if required_type == "[int]": if not isinstance(value, str): raise ValueError( f"config option {key} needs to be a list of int,int,..." ) try: value = [int(x) for x in value.split(",")] except ValueError as error: raise ValueError( f"config option {key} needs to be a list of int[int,...]" ) from error if required_type == "bool": value = bool(str(value).lower() in ["1", "true", "yes"]) if required_type == "[str]": if not isinstance(value, str): raise ValueError( f"config option {key} needs to be a list of int,int,..." ) value = [x.strip() for x in value.split(",")] if isinstance(value, list) and allowed_values: if not all(x in allowed_values for x in value): raise ValueError(f"config option {key} needs to be one of {allowed_values}") else: if allowed_values is not None and value not in allowed_values: raise ValueError(f"config option {key} needs to be one of {allowed_values}") return value def format_datetime( the_datetime: Optional[Union[arrow.Arrow, datetime.datetime]], tz: Optional[str] = None, ) -> str: """Return an isoformat()-like datetime without the microseconds.""" if the_datetime is None: retval = "" elif isinstance(the_datetime, (arrow.Arrow, datetime.datetime)): the_datetime = the_datetime.replace(microsecond=0) retval = the_datetime.isoformat(" ") if isinstance(the_datetime, arrow.Arrow): if tz is not None: retval = the_datetime.to(tz).isoformat(" ") else: retval = str(the_datetime) return retval def short_hostname() -> str: """Get just our machine name. TODO: This might actually be redundant. Python probably provides it's own version of this. """ return (socket.gethostname() + ".").split(".")[0] def get_config_dict( config: EnvironmentAwareConfigParser, monitor: str ) -> Dict[str, str]: options = config.items(monitor) ret = {} for key, value in options: ret[key] = value return ret def subclass_dict_handler( mod: str, base_cls: type, type_attr: str ) -> Tuple[Callable, Callable, Callable]: def _check_is_subclass(cls: Any) -> None: if not issubclass(cls, base_cls): raise TypeError( ("%s.register may only be used on subclasses " "of %s.%s") % (mod, mod, base_cls.__name__) ) _subclasses = {} def register(cls: Any) -> Any: """Decorator for monitor classes.""" _check_is_subclass(cls) if cls is None or getattr(cls, type_attr, "unknown") == "unknown": raise ValueError("Cannot register this class") _subclasses[getattr(cls, type_attr)] = cls return cls def get_class(type_: Any) -> Any: return _subclasses[type_] def all_types() -> list: return list(_subclasses) return (register, get_class, all_types) def check_group_match(group: str, group_list: List[str]) -> bool: """ Check if a group is contained in the group list. If the group list is a single element, "_all", then it matches. """ if group_list[0] == "_all": return True if group in group_list: return True return False def size_string_to_bytes(s: str) -> Optional[int]: if s is None: return None if s.endswith("G"): gigs = int(s[:-1]) _bytes = gigs * (1024**3) elif s.endswith("M"): megs = int(s[:-1]) _bytes = megs * (1024**2) elif s.endswith("K"): kilos = int(s[:-1]) _bytes = kilos * 1024 else: return int(s) return _bytes def bytes_to_size_string(b: int) -> str: """Convert a number in bytes to a sensible unit.""" kb = 1024 mb = kb * 1024 gb = mb * 1024 tb = gb * 1024 if b > tb: return "%0.2fTiB" % (b / float(tb)) if b > gb: return "%0.2fGiB" % (b / float(gb)) if b > mb: return "%0.2fMiB" % (b / float(mb)) if b > kb: return "%0.2fKiB" % (b / float(kb)) return str(b) def copy_if_different(source: str, dest: str) -> bool: """Copy a file from src to dest, if newer or a different size""" do_copy = False if not os.path.exists(source): return False if os.path.isdir(dest): dest = os.path.join(dest, os.path.basename(source)) if not os.path.exists(dest): do_copy = True else: source_fileinfo = os.stat(source) dest_fileinfo = os.stat(dest) if source_fileinfo.st_size != dest_fileinfo.st_size: do_copy = True elif source_fileinfo.st_mtime > dest_fileinfo.st_mtime: do_copy = True if not do_copy: return False try: shutil.copy(source, dest) except IOError: return False return True simplemonitor-1.13.0/simplemonitor/util/envconfig.py000066400000000000000000000053161464501162400227130ustar00rootroot00000000000000"""A version of ConfigParser which supports subsitutions from environment variables.""" import os import re from configparser import BasicInterpolation, ConfigParser from typing import Any, List, Optional class EnvironmentAwareConfigParser(ConfigParser): """A subclass of ConfigParser which allows %env:VAR% interpolation via the get method.""" r = re.compile("%env:([a-zA-Z0-9_]+)%") def __init__(self, *args: Any, **kwargs: Any) -> None: """Init with our specific interpolation class (for Python 3)""" interpolation = EnvironmentAwareInterpolation() kwargs["interpolation"] = interpolation ConfigParser.__init__(self, *args, **kwargs) def read(self, filenames: Any, encoding: Optional[str] = None) -> List[str]: """Load a config file and do environment variable interpolation on the section names.""" result = ConfigParser.read(self, filenames) for section in self.sections(): original_section = section matches = self.r.search(section) while matches: env_key = matches.group(1) if env_key in os.environ: section = section.replace(matches.group(0), os.environ[env_key]) else: raise ValueError( "Cannot find {0} in environment for config interpolation".format( env_key ) ) matches = self.r.search(section) if section != original_section: self.add_section(section) for option, value in self.items(original_section): self.set(section, option, value) self.remove_section(original_section) return result class EnvironmentAwareInterpolation(BasicInterpolation): """An interpolation which substitutes values from the environment.""" r = re.compile("%env:([a-zA-Z0-9_]+)%") def before_get( self, parser: Any, section: str, option: str, value: Any, defaults: Any ) -> Any: parser.get(section, option, raw=True, fallback=value) matches = self.r.search(value) old_value = value while matches: env_key = matches.group(1) if env_key in os.environ: value = value.replace(matches.group(0), os.environ[env_key]) else: raise ValueError( "Cannot find {0} in environment for config interpolation".format( env_key ) ) matches = self.r.search(value) if value == old_value: break old_value = value return value simplemonitor-1.13.0/simplemonitor/util/json_encoding.py000066400000000000000000000044531464501162400235550ustar00rootroot00000000000000import datetime import json import re from typing import Any import arrow from . import MonitorState DATETIME_MAGIC_TOKEN = "__simplemonitor_datetime" # nosec MONITORSTATE_MAGIC_TOKEN = "__simplemonitor_monitorstate" # nosec ARROW_MAGIC_TOKEN = "__simplemonitor_arrow" # nosec FORMAT = "%Y-%m-%d %H:%M:%S.%f" class JSONEncoder(json.JSONEncoder): _regexp_type = type(re.compile("")) def default(self, o: Any) -> Any: # pylint: disable=E0202 if isinstance(o, datetime.datetime): return {DATETIME_MAGIC_TOKEN: o.strftime(FORMAT)} if isinstance(o, self._regexp_type): return "" if isinstance(o, MonitorState): return {MONITORSTATE_MAGIC_TOKEN: o.name} if isinstance(o, arrow.Arrow): return {ARROW_MAGIC_TOKEN: o.for_json()} return super(JSONEncoder, self).default(o) class JSONDecoder(json.JSONDecoder): def __init__(self, *args: Any, **kwargs: Any) -> None: self._original_object_pairs_hook = kwargs.pop("object_pairs_hook", None) kwargs["object_pairs_hook"] = self.object_pairs_hook super(JSONDecoder, self).__init__(*args, **kwargs) _datetime_re = re.compile(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}") def object_pairs_hook(self, obj: Any) -> Any: # pylint: disable=E0202 if ( len(obj) == 1 and obj[0][0] == DATETIME_MAGIC_TOKEN and isinstance(obj[0][1], str) and self._datetime_re.match(obj[0][1]) ): # convert incoming datetimes to Arrow return arrow.get(obj[0][1]) elif ( len(obj) == 1 and obj[0][0] == MONITORSTATE_MAGIC_TOKEN and isinstance(obj[0][1], str) ): return MonitorState[obj[0][1]] elif ( len(obj) == 1 and obj[0][0] == ARROW_MAGIC_TOKEN and isinstance(obj[0][1], str) ): return arrow.get(obj[0][1]) elif self._original_object_pairs_hook: return self._original_object_pairs_hook(obj) else: return dict(obj) def json_dumps(data: Any) -> bytes: return JSONEncoder().encode(data).encode("ascii") def json_loads(string: bytes) -> Any: return JSONDecoder().decode(string.decode("ascii")) simplemonitor-1.13.0/simplemonitor/version.py000066400000000000000000000002471464501162400214430ustar00rootroot00000000000000from importlib_metadata import version # compat lib for < 3.8 # VERSION = pkg_resources.get_distribution("simplemonitor").version VERSION = version("simplemonitor") simplemonitor-1.13.0/simplemonitor/winmonitor.py000077500000000000000000000073161464501162400221720ustar00rootroot00000000000000import logging import multiprocessing as mp import os import socket import sys from typing import Any import win32event import win32service import win32serviceutil from simplemonitor import monitor r""" Notes: This service expects the 'monitor.ini' file to exist in the same directory. If you receive an error "The service did not respond to the start or control request in a timely fashion", it is likely/possible you need to include the python and pywin32 binaries in your path: e.g. (from an administrator prompt) setx /M PATH "%PATH%;c:\Python;c:\Python\scripts;c:\Python\Lib\site-packages\pywin32_system32;c:\Python\Lib\site-packages\win32" """ # Change this to the location of your config file, if required APP_PATH = os.path.realpath(os.path.dirname(__file__)) CONFIG = os.path.join(APP_PATH, "monitor.ini") LOGFILE = os.path.join(APP_PATH, "simplemonitor.log") # Setup Logging def configure_logger( logger: logging.Logger, level: int = logging.DEBUG ) -> logging.Logger: logger.setLevel(level) fh = logging.FileHandler(LOGFILE) fh.setLevel(level) formatter = logging.Formatter( "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) fh.setFormatter(formatter) logger.addHandler(fh) return logger def setup_logger(level: int = logging.DEBUG) -> logging.Logger: return configure_logger(mp.get_logger(), level) LOGGER = setup_logger(logging.INFO) class AppServerSvc(win32serviceutil.ServiceFramework): _svc_name_ = "SimpleMonitor" _svc_display_name_ = "SimpleMonitor" _svc_description_ = "A service wrapper for the python SimpleMonitor program" def __init__(self, args: Any) -> None: # Initialise service win32serviceutil.ServiceFramework.__init__(self, args) self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) socket.setdefaulttimeout(60) # Setup logger self.logger = LOGGER self.logger.info("Initialised %s service", self._svc_display_name_) def SvcStop(self) -> None: self.logger.info("Stopping %s service", self._svc_display_name_) self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) win32event.SetEvent(self.hWaitStop) def SvcDoRun(self) -> None: self.logger.info("Starting %s service", self._svc_display_name_) import servicemanager servicemanager.LogMsg( servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name, ""), ) # Start monitor p_mon = mp.Process(target=run_monitor) p_mon.start() self.logger.info("Started %s service", self._svc_display_name_) # Wait for Monitor to finish while True: try: # Watch monitor process for 2 seconds p_mon.join(timeout=2) if not p_mon.is_alive(): self.logger.warning("Service stopped prematurely.") self.SvcStop() # Check if we've received a stop command rc = win32event.WaitForSingleObject(self.hWaitStop, 500) if rc == win32event.WAIT_OBJECT_0: p_mon.terminate() p_mon.join() break self.logger.debug("Still running...") except KeyboardInterrupt: self.logger.warning("Interrupted %s service", self._svc_display_name_) break self.logger.info("Stopped %s service", self._svc_display_name_) def run_monitor() -> None: sys.argv = ["monitor.py", "-vH", "--config={}".format(CONFIG)] monitor.main() def main() -> None: win32serviceutil.HandleCommandLine(AppServerSvc) if __name__ == "__main__": main() simplemonitor-1.13.0/tests/000077500000000000000000000000001464501162400156425ustar00rootroot00000000000000simplemonitor-1.13.0/tests/html/000077500000000000000000000000001464501162400166065ustar00rootroot00000000000000simplemonitor-1.13.0/tests/html/map1.html000066400000000000000000000070301464501162400203320ustar00rootroot00000000000000 FAIL@fake_hostname.local monitor
SimpleMonitor __VERSION__ » Documentation | » Code
simplemonitor-1.13.0/tests/html/test1.html000066400000000000000000000121711464501162400205360ustar00rootroot00000000000000 FAIL@fake_hostname.local monitor
Monitor Status Host Failed at VFC Up/downtime Detail Failures Last Failure Age
fail FAIL fake_hostname 2020-04-18 12:00:00+00:00 1 0+00:00:00 (0.00%) This monitor always fails. 1 2020-04-18 12:00:00+00:00
disabled DISABLED fake_hostname 0+00:00:00 (0.00%)
unnamed OK fake_hostname 0+00:00:00 (0.00%)
SimpleMonitor __VERSION__ » Documentation | » Code
simplemonitor-1.13.0/tests/html/test2.html000066400000000000000000000121711464501162400205370ustar00rootroot00000000000000 FAIL@fake_hostname.local monitor
Monitor Status Host Failed at VFC Up/downtime Detail Failures Last Failure Age
fail FAIL fake_hostname 2020-04-18 14:00:00+02:00 1 0+00:00:00 (0.00%) This monitor always fails. 1 2020-04-18 14:00:00+02:00
disabled DISABLED fake_hostname 0+00:00:00 (0.00%)
unnamed OK fake_hostname 0+00:00:00 (0.00%)
SimpleMonitor __VERSION__ » Documentation | » Code
simplemonitor-1.13.0/tests/mocks/000077500000000000000000000000001464501162400167565ustar00rootroot00000000000000simplemonitor-1.13.0/tests/mocks/apcaccess-fail-mock/000077500000000000000000000000001464501162400225435ustar00rootroot00000000000000simplemonitor-1.13.0/tests/mocks/apcaccess-fail-mock/apcaccess000077500000000000000000000013721464501162400244210ustar00rootroot00000000000000#!/usr/bin/env bash cat < 1 problem(s) in your installed packages found. You are advised to update or deinstall the affected package(s) immediately. EOF simplemonitor-1.13.0/tests/mocks/rc000077500000000000000000000001521464501162400173060ustar00rootroot00000000000000#!/usr/bin/env bash # mock rc file for simplemonitor if [[ $1 != 'status' ]]; then exit 128 fi exit 0 simplemonitor-1.13.0/tests/mocks/rc-fail000077500000000000000000000001521464501162400202170ustar00rootroot00000000000000#!/usr/bin/env bash # mock rc file for simplemonitor if [[ $1 != 'status' ]]; then exit 128 fi exit 1 simplemonitor-1.13.0/tests/mocks/service000077500000000000000000000001211464501162400203360ustar00rootroot00000000000000#!/usr/bin/env bash if [[ $1 == "good_service" ]]; then exit 0 else exit 1 fi simplemonitor-1.13.0/tests/mocks/svok000077500000000000000000000002101464501162400176570ustar00rootroot00000000000000#!/usr/bin/env bash # mock svok file for simplemonitor if [[ $1 == 'pass' ]]; then exit 0 fi if [[ $1 == 'fail' ]]; then exit 1 fi simplemonitor-1.13.0/tests/monitor-badinterval.ini000066400000000000000000000000271464501162400223220ustar00rootroot00000000000000[monitor] interval=moo simplemonitor-1.13.0/tests/monitor-docker.ini000066400000000000000000000002131464501162400212730ustar00rootroot00000000000000[monitor] monitors=monitors.ini interval=5 pidfile=monitor.pid [reporting] loggers=html [html] type=html folder=html filename=index.html simplemonitor-1.13.0/tests/monitor-empty.ini000066400000000000000000000000701464501162400211630ustar00rootroot00000000000000[monitor] monitors=tests/monitors-empty.ini interval=60 simplemonitor-1.13.0/tests/monitor-env.ini000066400000000000000000000001551464501162400206210ustar00rootroot00000000000000[monitor] monitors=tests/monitors-%env:TEST_VALUE%.ini interval=5 [monitor-%env:TEST_VALUE%] monitors=hello simplemonitor-1.13.0/tests/monitor-html.ini000066400000000000000000000001701464501162400207720ustar00rootroot00000000000000[monitor] interval=10 monitors=tests/monitors-html.ini [reporting] loggers=html [html] type=html filename=output.html simplemonitor-1.13.0/tests/monitor-nointerval.ini000066400000000000000000000000211464501162400222020ustar00rootroot00000000000000[monitor] # oops simplemonitor-1.13.0/tests/monitor-ping.ini000066400000000000000000000001121464501162400207570ustar00rootroot00000000000000[monitor] monitors=tests/monitors-ping.ini interval=5 pidfile=monitor.pid simplemonitor-1.13.0/tests/monitor-sns.ini000066400000000000000000000002601464501162400206310ustar00rootroot00000000000000[monitor] interval=60 monitors=tests/monitors-sns.ini [reporting] alerters=sns [sns] type=sns topic=arn:aws:sns:us-east-1:108685319098:sns-billing-alert aws_region=us-east-1 simplemonitor-1.13.0/tests/monitor-windows.ini000066400000000000000000000035111464501162400215220ustar00rootroot00000000000000[monitor] monitors=tests/monitors-windows.ini interval=5 pidfile=monitor.pid [reporting] alerters=slack,sms,mail,ses,elks,pushover,execute,syslog,slack2,pushbullet,telegram,sns loggers=db1,db2,file1,file2,file3,file4,html,json,network [slack] type=slack url=https://hooks.slack.com/services/haha/nope/thisisjustatest channel=testing dry_run=1 [slack2] type=slack url=https://hooks.slack.com/services/haha/nope/thisisjustatest username=testing dry_run=1 [db1] type=db db_path=monitor.db [db2] type=dbstatus db_path=monitor2.db depend=filestat [file1] type=logfile filename=monitor1.log [file2] type=logfile filename=monitor2.log only_failures=1 [file3] type=logfile filename=monitor3.log buffered=0 [file4] type=logfile filename=monitor4.log dateformat=iso8601 [sms] type=bulksms username=a password=b target=123456789012 dry_run=1 [mail] type=email host=test.jamesoff.net from=james@jamesoff.net to=james@jamesoff.net username=username password=password ssl=starttls dry_run=1 [ses] type=ses from=james@jamesoff.net to=james@jamesoff.net dry_run=1 aws_region=us-east-1 aws_access_key=a aws_secret_key=b [sns] type=sns number=1234 aws_region=us-east-1 aws_access_key=a aws_secret_key=b [elks] type=46elks username=a password=b target=1 sender=+12345678 dry_run=1 [pushover] type=pushover token=a user=b dry_run=1 [html] type=html filename=status.html header=header.html footer=footer.html folder=html upload_command=/bin/true [execute] type=execute fail_command=echo hello from fail execute alerter success_command=echo hello from success execute alerter catchup_command=echo hello from catchup execute alerter [syslog] type=syslog [json] type=json filename=output.json [network] type=network host=127.0.0.1 port=6789 key=examplekey [pushbullet] type=pushbullet token=abc123 dry_run=1 [telegram] type=telegram token=test chat_id=test dry_run=1 simplemonitor-1.13.0/tests/monitor.ini000066400000000000000000000041561464501162400200400ustar00rootroot00000000000000[monitor] monitors=tests/monitors.ini interval=5 pidfile=monitor.pid [reporting] alerters=slack,sms,mail,ses,elks,pushover,execute,syslog,slack2,pushbullet,telegram,sns,sms77 loggers=db1,db2,file1,file2,file3,file4,html,json,network [slack] type=slack url=https://hooks.slack.com/services/haha/nope/thisisjustatest channel=testing dry_run=1 [slack2] type=slack url=https://hooks.slack.com/services/haha/nope/thisisjustatest username=testing dry_run=1 [db1] type=db db_path=monitor.db [db2] type=dbstatus db_path=monitor2.db depend=filestat [file1] type=logfile filename=monitor1.log [file2] type=logfile filename=monitor2.log only_failures=1 [file3] type=logfile filename=monitor3.log buffered=0 [file4] type=logfile filename=monitor4.log dateformat=iso8601 [file5] type=logfileng filename=monitor6.log rotation_type=time when=m interval=1 backup_count=1 dateformat=iso8601 [file6] type=logfileng filename=monitor7.log rotation_type=size max_bytes=1K [sms] type=bulksms username=a password=b target=123456789012 dry_run=1 [mail] type=email host=test.jamesoff.net from=james@jamesoff.net to=james@jamesoff.net username=username password=password ssl=starttls dry_run=1 [ses] type=ses from=james@jamesoff.net to=james@jamesoff.net dry_run=1 aws_region=us-east-1 aws_access_key=a aws_secret_key=b [sns] type=sns number=1234 aws_region=us-east-1 aws_access_key=a aws_secret_key=b dry_run=1 [elks] type=46elks username=a password=b target=1 sender=+12345678 dry_run=1 [pushover] type=pushover token=a user=b dry_run=1 [html] type=html filename=status.html header=header.html footer=footer.html folder=html upload_command=/bin/true tz=Europe/London [execute] type=execute fail_command=echo hello from fail execute alerter success_command=echo hello from success execute alerter catchup_command=echo hello from catchup execute alerter [syslog] type=syslog [json] type=json filename=output.json [network] type=network host=127.0.0.1 port=6789 key=examplekey [pushbullet] type=pushbullet token=abc123 dry_run=1 [telegram] type=telegram token=test chat_id=test dry_run=1 [sms77] type=sms77 api_key=secret_api_key target=441234123456 dry_run=1 simplemonitor-1.13.0/tests/monitors-docker.ini000066400000000000000000000001011464501162400214520ustar00rootroot00000000000000[defaults] testvalue=1 [test2-fail] type=fail [null] type=null simplemonitor-1.13.0/tests/monitors-empty.ini000066400000000000000000000000001464501162400213370ustar00rootroot00000000000000simplemonitor-1.13.0/tests/monitors-html.ini000066400000000000000000000000531464501162400211550ustar00rootroot00000000000000[monitor1] type=null [monitor2] type=fail simplemonitor-1.13.0/tests/monitors-myenv.ini000066400000000000000000000001161464501162400213470ustar00rootroot00000000000000[test1] type=host host=127.0.0.1 [test2:%env:USER%] type=host host=127.0.0.1 simplemonitor-1.13.0/tests/monitors-ping.ini000066400000000000000000000001121464501162400211420ustar00rootroot00000000000000[ping] type=host host=192.168.1.1 [ping-fail] type=host host=192.168.1.2 simplemonitor-1.13.0/tests/monitors-posthup.ini000066400000000000000000000000721464501162400217140ustar00rootroot00000000000000[monitor1] type=fail [monitor2] type=host host=127.0.0.2 simplemonitor-1.13.0/tests/monitors-prehup.ini000066400000000000000000000000721464501162400215150ustar00rootroot00000000000000[monitor1] type=null [monitor2] type=host host=127.0.0.1 simplemonitor-1.13.0/tests/monitors-sns.ini000066400000000000000000000000211464501162400210070ustar00rootroot00000000000000[fail] type=fail simplemonitor-1.13.0/tests/monitors-windows.ini000066400000000000000000000031051464501162400217040ustar00rootroot00000000000000[defaults] testvalue=1 [test2-fail] type=fail [http] type=http url=http://www.google.com [https] type=http url=https://www.google.com [https-fail] type=http url=https://expired.badssl.com [https-regexp] type=http regexp=Google url=https://www.google.com [https-404] type=http url=https://google.com/404 allowed_codes=404 [https-404-fail] type=http url=https://google.com/404 allowed_codes=200,302 [https-noverify] type=http url=https://wrong.host.badssl.com verify_hostname=false [filestat] type=filestat filename=simplemonitor/monitor.py [filestat2] type=filestat filename=simplemonitor/monitor.py minsize=1 [filestat3] type=filestat filename=simplemonitor/monitor.py maxage=525600 [filestat-fail] type=filestat filename=missing.txt [filestat2-fail] type=filestat filename=simplemonitor/monitor.py minsize=10G [filestat3-fail] type=filestat filename=simplemonitor/monitor.py maxage=1 [filestat4] type=filestat filename=simplemonitor/monitor.py maxsize=10G [filestat4-fail] type=filestat filename=simplemonitor/monitor.py maxsize=1 [null] type=null [tcp] type=tcp host=www.google.com port=80 [tcp-fail] type=tcp host=www.google.com port=81 group=a [ping] type=host host=127.0.0.1 [ping-fail] type=host host=bad.jamesoff.net [not-this-host] type=null runon=some-unlikely-hostname [memory] type=memory percent_free=0 [memory-fail] type=memory percent_free=100 [service] type=service service=W32Time [service1-fail] type=service service=fakeservice [service2-fail] type=service service=W32Time state=STOPPED [swap] type=swap percent_free=0 [swap-fail] type=swap percent_free=101 simplemonitor-1.13.0/tests/monitors.ini000066400000000000000000000105301464501162400202140ustar00rootroot00000000000000[defaults] testvalue=1 [test2-fail] type=fail [command1] type=command command=ls -l /tmp/ result_regexp=total [command2] type=command command=echo 5 result_max=6 [command3-fail] type=command command=echo 5 result_max=4 [command4-fail] type=command command=moo result_regexp=hello recover_command=echo 1 [http] type=http url=http://www.google.com [https] type=http url=https://www.google.com [https-fail] type=http url=https://expired.badssl.com [https-regexp] type=http regexp=Google url=https://www.google.com [https-404] type=http url=https://google.com/404 allowed_codes=404 [https-404-fail] type=http url=https://google.com/404 allowed_codes=200,302 [https-noverify] type=http url=https://wrong.host.badssl.com verify_hostname=false [dns] type=dns record=a.test.jamesoff.net [dns-mx] type=dns record=jamesoff.net record_type=MX [dns-fail] type=dns record=bad.jamesoff.net # I'm sure this'll come back to bite me [dns-value] type=dns record=a.test.jamesoff.net desired_val=1.2.3.4 [dns-multivalue] type=dns record=a-multi.test.jamesoff.net desired_val=1.2.3.4 2.3.4.5 [dns-multivalue-fail] type=dns record=a-multi.test.jamesoff.net desired_val=1.2.3.4 3.4.5.6 [dns-value-fail] type=dns record=a.test.jamesoff.net desired_val=127.0.0.1 [dns-server] type=dns record=a.test.jamesoff.net server=8.8.8.8 [dns-nxdomain] type=dns record=null.test.jamesoff.net desired_val=nxdomain [depends] type=command command=ls depend=command1 [depends-skip] type=command command=ls depend=https-fail [gap] type=command command=ls gap=1 [compound] type=compound monitors=command1,command2 [filestat] type=filestat filename=simplemonitor/monitor.py [filestat2] type=filestat filename=simplemonitor/monitor.py minsize=1 [filestat3] type=filestat filename=simplemonitor/monitor.py maxage=525600 [filestat-fail] type=filestat filename=missing.txt [filestat2-fail] type=filestat filename=simplemonitor/monitor.py minsize=10G [filestat3-fail] type=filestat filename=simplemonitor/monitor.py maxage=1 [filestat4] type=filestat filename=simplemonitor/monitor.py maxsize=10G [filestat4-fail] type=filestat filename=simplemonitor/monitor.py maxsize=1 [apc] type=apcupsd path=./tests/mocks/apcaccess-mock [apc-fail] type=apcupsd path=./tests/mocks/apcaccess-fail-mock [svc] type=svc path=pass [svc-fail] type=svc path=fail [rc] type=rc path=tests/mocks/rc service=pass [rc-fail] type=rc path=tests/mocks/rc-fail service=fail [exim] type=eximqueue path=tests/mocks max_length=1000 [exim-fail] type=eximqueue path=tests/mocks max_length=10 [null] type=null [tcp] type=tcp host=www.google.com port=80 [tcp-fail] type=tcp host=www.google.com port=81 group=a [ping] type=host host=127.0.0.1 [ping-fail] type=host host=bad.jamesoff.net [pkg-fail] type=pkgaudit path=./tests/mocks/pkg-fail [pkg-2-fail] type=pkgaudit path=./tests/mocks/pkg-fail-2 [pkg] type=pkgaudit path=./tests/mocks/pkg [portaudit] type=portaudit path=./tests/mocks/portaudit [portaudit-fail] type=portaudit path=./tests/mocks/portaudit-fail [loadavg1] type=loadavg which=0 max=100 [loadavg5] type=loadavg which=1 max=100 [loadavg15] type=loadavg which=2 max=100 [loadavg-fail] type=loadavg which=0 max=0.01 [not-this-host] type=null runon=some-unlikely-hostname [nc] type=nc [compound1-fail] type=compound monitors=ping-fail,pkg-fail [compound2] type=compound monitors=ping-fail,ping [compoud3] type=compound monitors=ping,null [memory] type=memory percent_free=0 [memory-fail] type=memory percent_free=100 [swap] type=swap percent_free=0 # Not testing swap-fail; think the test environment in GH Actions has no swap? [unix-service] type=unix_service service=good_service [unix-service-fail] type=unix_service service=bad_service state=running [unix-service-stopped] type=unix_service service=bad_service state=stopped [unix-service-stopped-fail] type=unix_service service=good_service state=stopped [process] type=process process_name=python3 [process-fail] type=process process_name=unlikely-process-name [process-max-fail] type=process process_name=python3 max_count=0 [process-min-fail] type=process process_name=python3 # if the test host has more than this many python processes you probably want tests to fail min_count=60000 [process-username-fail] type=process process_name=python3 username=unlikely-username [tls-expiry] type=tls_expiry host=www.amazon.com [tls-expiry-fail] type=tls_expiry host=expired.badssl.com simplemonitor-1.13.0/tests/network/000077500000000000000000000000001464501162400173335ustar00rootroot00000000000000simplemonitor-1.13.0/tests/network/client/000077500000000000000000000000001464501162400206115ustar00rootroot00000000000000simplemonitor-1.13.0/tests/network/client/monitor-id.ini000066400000000000000000000002601464501162400233710ustar00rootroot00000000000000[monitor] monitors=tests/network/client/monitors.ini interval=10 [reporting] loggers=network [network] type=network host=127.0.0.1 port=1234 key=test client_name=custom_name simplemonitor-1.13.0/tests/network/client/monitor-ipv6.ini000066400000000000000000000002221464501162400236570ustar00rootroot00000000000000[monitor] monitors=tests/network/client/monitors.ini interval=10 [reporting] loggers=network [network] type=network host=::1 port=1234 key=test simplemonitor-1.13.0/tests/network/client/monitor.ini000066400000000000000000000002301464501162400227740ustar00rootroot00000000000000[monitor] monitors=tests/network/client/monitors.ini interval=10 [reporting] loggers=network [network] type=network host=127.0.0.1 port=1234 key=test simplemonitor-1.13.0/tests/network/client/monitors.ini000066400000000000000000000000641464501162400231640ustar00rootroot00000000000000[test2] type=null [test3] type=host host=127.0.0.1 simplemonitor-1.13.0/tests/network/master/000077500000000000000000000000001464501162400206265ustar00rootroot00000000000000simplemonitor-1.13.0/tests/network/master/monitor.ini000066400000000000000000000002631464501162400230170ustar00rootroot00000000000000[monitor] remote=1 remote_port=1234 monitors=tests/network/master/monitors.ini interval=10 key=test [reporting] loggers=file [file] type=logfile filename=network.log buffered=0 simplemonitor-1.13.0/tests/network/master/monitors.ini000066400000000000000000000000221464501162400231730ustar00rootroot00000000000000[test1] type=null simplemonitor-1.13.0/tests/network/monitor.ini000066400000000000000000000000221464501162400215150ustar00rootroot00000000000000[test1] type=null simplemonitor-1.13.0/tests/test-network.sh000077500000000000000000000023111464501162400206440ustar00rootroot00000000000000#!/usr/bin/env bash set -exu without_coverage=${WITHOUT_COVERAGE:-0} if [[ $without_coverage -eq 1 ]]; then my_command="python" else my_command="coverage run --debug=dataio" fi run_test() { server_config=$1 client_config=$2 extra_grep=${3:-} echo "==> Running network test with server config $server_config and client config $client_config" rm -f network.log # start the master instance ( COVERAGE_FILE=.coverage.1 $my_command monitor.py -f "tests/network/master/$server_config" -d --loops=2 2>&1 | tee -a master.log ) & sleep 1 # run the client instance COVERAGE_FILE=.coverage.2 $my_command monitor.py -f "tests/network/client/$client_config" -1 -d 2>&1 | tee -a client.log # let them run sleep 15 # make sure the client reached the master grep test2 network.log grep test3 network.log if [[ -n $extra_grep ]]; then filename=$( echo "$extra_grep" | cut -d= -f1 ) expr=$( echo "$extra_grep" | cut -d= -f2 ) grep "$expr" "$filename" fi wait if [[ $without_coverage -ne 1 ]]; then coverage combine --append fi echo "==> Completed network test" echo } run_test monitor.ini monitor.ini run_test monitor.ini monitor-ipv6.ini run_test monitor.ini monitor-id.ini master.log=custom_name simplemonitor-1.13.0/tests/test_alerter.py000066400000000000000000000567051464501162400207260ustar00rootroot00000000000000# type: ignore import datetime import os import textwrap import unittest import arrow from freezegun import freeze_time from simplemonitor import util from simplemonitor.Alerters import alerter, sns from simplemonitor.Monitors import monitor class TestAlerter(unittest.TestCase): def setUp(self): # Work around to fix the freezegun times later to our local TZ, else they're UTC # and the time compared to is local (as it should be) self.tz = os.environ.get("TZ", "local") a = arrow.now() self.utcoffset = a.utcoffset() def test_groups(self): config_options = {"groups": "a,b,c"} a = alerter.Alerter(config_options) self.assertEqual(["a", "b", "c"], a.groups) def test_times_always(self): config_options = {"times_type": "always"} a = alerter.Alerter(config_options) self.assertEqual(a._times_type, alerter.AlertTimeFilter.ALWAYS) self.assertEqual(a._time_info, (None, None)) def test_times_only(self): config_options = { "times_type": "only", "time_lower": "10:00", "time_upper": "11:00", } a = alerter.Alerter(config_options) self.assertEqual(a._times_type, alerter.AlertTimeFilter.ONLY) self.assertEqual(a._time_info, (datetime.time(10, 00), datetime.time(11, 00))) def test_times_not(self): config_options = { "times_type": "not", "time_lower": "10:00", "time_upper": "11:00", } a = alerter.Alerter(config_options) self.assertEqual(a._times_type, alerter.AlertTimeFilter.NOT) self.assertEqual(a._time_info, (datetime.time(10, 00), datetime.time(11, 00))) def test_times_broken(self): config_options = { "times_type": "fake", "time_lower": "10:00", "time_upper": "11:00", } with self.assertRaises(ValueError): alerter.Alerter(config_options) def test_days(self): config_options = {"days": "0,1,4"} a = alerter.Alerter(config_options) self.assertEqual(a._days, [0, 1, 4]) def test_delay(self): config_options = {"delay": "1"} a = alerter.Alerter(config_options) self.assertEqual(a._delay_notification, True) def test_dryrun(self): config_options = {"dry_run": "1"} a = alerter.Alerter(config_options) self.assertEqual(a._dry_run, True) def test_oohrecovery(self): config_otions = {"ooh_recovery": "1"} a = alerter.Alerter(config_otions) self.assertEqual(a._ooh_recovery, True) def test_dependencies(self): config_options = {"depend": "a,b,c"} a = alerter.Alerter(config_options) self.assertEqual( a.dependencies, ["a", "b", "c"], "Alerter did not store dependencies" ) self.assertEqual( a.check_dependencies(["d", "e"]), True, "Alerter thinks a dependency failed" ) self.assertEqual( a.check_dependencies(["a"]), False, "Alerter did not notice a dependency failed", ) def test_limit(self): config_options = {"limit": "5"} a = alerter.Alerter(config_options) self.assertEqual(a._limit, 5) def test_repeat(self): config_options = {"repeat": "5"} a = alerter.Alerter(config_options) self.assertEqual(a._repeat, 5) def test_should_alert_basic_failure(self): # no special alert config a = alerter.Alerter(None) m = monitor.MonitorFail("fail", {}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) def test_should_alert_only_failure(self): # no special alert config a = alerter.Alerter({"only_failures": True}) m = monitor.MonitorFail("fail", {}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) def test_should_alert_basic_none(self): a = alerter.Alerter(None) m = monitor.MonitorNull() m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) def test_should_alert_basic_success(self): a = alerter.Alerter(None) m = monitor.MonitorFail("fail", {}) for _ in range(0, 6): m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.SUCCESS) def test_should_not_alert_basic_success(self): a = alerter.Alerter({"only_failures": True}) m = monitor.MonitorFail("fail", {}) for _ in range(0, 6): m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) def test_not_allowed_today(self): # a Tuesday with freeze_time("2020-03-10", tz_offset=self.utcoffset): a = alerter.Alerter({"days": "0,2,3,4,5,6"}) self.assertFalse(a._allowed_today()) @freeze_time( "2020-03-09 22:00:00+00:00" ) # a Monday, but the TZ will push to Tuesday def test_not_allowed_today_tz(self): a = alerter.Alerter({"days": "0,2,3,4,5,6", "times_tz": "+05:00"}) self.assertFalse(a._allowed_today()) def test_allowed_today(self): with freeze_time("2020-03-10", tz_offset=self.utcoffset): a = alerter.Alerter({"days": "1"}) self.assertTrue(a._allowed_today()) @freeze_time( "2020-03-09 22:00:00+00:00" ) # a Monday, but the TZ will push to Tuesday def test_allowed_today_tz(self): a = alerter.Alerter({"days": "1", "times_tz": "+05:00"}) self.assertTrue(a._allowed_today()) @freeze_time("2020-03-10") def test_allowed_default(self): a = alerter.Alerter({}) self.assertTrue(a._allowed_today()) @freeze_time("10:00") def test_allowed_always(self): a = alerter.Alerter({}) self.assertTrue(a._allowed_time()) def test_allowed_only(self): a = alerter.Alerter( { "times_type": "only", "time_lower": "10:00", "time_upper": "11:00", "times_tz": "local", } ) with freeze_time("09:00", tz_offset=-self.utcoffset): self.assertFalse(a._allowed_time()) with freeze_time("10:30", tz_offset=-self.utcoffset): self.assertTrue(a._allowed_time()) with freeze_time("12:00", tz_offset=-self.utcoffset): self.assertFalse(a._allowed_time()) def test_allowed_only_tz(self): a = alerter.Alerter( { "times_type": "only", "time_lower": "15:00", "time_upper": "16:00", "times_tz": "+05:00", } ) with freeze_time("09:00"): self.assertFalse(a._allowed_time()) with freeze_time("10:30"): self.assertTrue(a._allowed_time()) with freeze_time("12:00"): self.assertFalse(a._allowed_time()) def test_allowed_not(self): a = alerter.Alerter( { "times_type": "not", "time_lower": "10:00", "time_upper": "11:00", "times_tz": "local", } ) with freeze_time("09:00", tz_offset=-self.utcoffset): self.assertTrue(a._allowed_time()) with freeze_time("10:30", tz_offset=-self.utcoffset): print(arrow.get()) self.assertFalse(a._allowed_time()) with freeze_time("12:00", tz_offset=-self.utcoffset): self.assertTrue(a._allowed_time()) def test_allowed_not_tz(self): a = alerter.Alerter( { "times_type": "not", "time_lower": "15:00", "time_upper": "16:00", "times_tz": "+05:00", } ) with freeze_time("09:00"): self.assertTrue(a._allowed_time()) with freeze_time("10:30"): self.assertFalse(a._allowed_time()) with freeze_time("12:00"): self.assertTrue(a._allowed_time()) def test_should_not_alert_ooh(self): config = {"times_type": "only", "time_lower": "10:00", "time_upper": "11:00"} a = alerter.Alerter(config) m = monitor.MonitorFail("fail", {}) m.run_test() with freeze_time("2020-03-10 09:00"): # out of hours on the right day; shouldn't alert self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) self.assertEqual(a._ooh_failures, ["fail"]) a = alerter.Alerter(config) with freeze_time("2020-03-10 12:00"): self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) self.assertEqual(a._ooh_failures, ["fail"]) def test_should_alert_ooh(self): config = {"times_type": "only", "time_lower": "10:00", "time_upper": "11:00"} a = alerter.Alerter(config) m = monitor.MonitorFail("fail", {}) m.run_test() with freeze_time("2020-03-10 10:30", tz_offset=self.utcoffset): self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) self.assertEqual(a._ooh_failures, []) def test_should_alert_limit(self): config = { "times_type": "only", "time_lower": "10:00", "time_upper": "11:00", "limit": 2, } a = alerter.Alerter(config) m = monitor.MonitorFail("fail", {}) m.run_test() with freeze_time("2020-03-10 10:30", tz_offset=self.utcoffset): self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) self.assertEqual(a._ooh_failures, []) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) self.assertEqual(a._ooh_failures, []) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) self.assertEqual(a._ooh_failures, []) def test_should_alert_limit_ooh(self): config = { "times_type": "only", "time_lower": "10:00", "time_upper": "11:00", "limit": 2, } a = alerter.Alerter(config) m = monitor.MonitorFail("fail", {}) m.run_test() with freeze_time("2020-03-10 09:00", tz_offset=self.utcoffset): self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) self.assertEqual(a._ooh_failures, []) a = alerter.Alerter(config) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) self.assertEqual(a._ooh_failures, ["fail"]) a = alerter.Alerter(config) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) self.assertEqual(a._ooh_failures, []) def test_should_alert_catchup(self): config = { "delay": 1, "times_type": "only", "time_lower": "10:00", "time_upper": "11:00", } a = alerter.Alerter(config) a.support_catchup = True m = monitor.MonitorFail("fail", {}) m.run_test() with freeze_time("2020-03-10 09:00", tz_offset=self.utcoffset): self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) self.assertEqual(a._ooh_failures, ["fail"]) with freeze_time("2020-03-10 10:30", tz_offset=self.utcoffset): self.assertEqual(a.should_alert(m), alerter.AlertType.CATCHUP) self.assertEqual(a._ooh_failures, []) def test_should_alert_no_catchup(self): config = { "delay": 1, "times_type": "only", "time_lower": "10:00", "time_upper": "11:00", } a = alerter.Alerter(config) m = monitor.MonitorFail("fail", {}) m.run_test() with freeze_time("2020-03-10 09:00", tz_offset=self.utcoffset): self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) self.assertEqual(a._ooh_failures, ["fail"]) with freeze_time("2020-03-10 10:30", tz_offset=self.utcoffset): self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) self.assertEqual(a._ooh_failures, []) def test_skip_group_alerter(self): a = alerter.Alerter({"groups": "test"}) m = monitor.MonitorFail("fail", {}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) def test_skip_group_monitor(self): a = alerter.Alerter() m = monitor.MonitorFail("fail", {"group": "test"}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) def test_groups_match(self): a = alerter.Alerter({"groups": "test"}) m = monitor.MonitorFail("fail", {"group": "test"}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) def test_groups_multi_match(self): a = alerter.Alerter({"groups": "test1, test2"}) m = monitor.MonitorFail("fail", {"group": "test1"}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) def test_groups_all_match(self): a = alerter.Alerter({"groups": "_all"}) m = monitor.MonitorFail("fail", {"group": "test1"}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) def test_disabled_monitor_no_alert(self): a = alerter.Alerter() m = monitor.MonitorFail("fail", {"enabled": 0}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) def test_alert_not_urgent(self): a = alerter.Alerter() m = monitor.MonitorFail("fail", {}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) def test_no_alert_urgent(self): a = alerter.Alerter({"urgent": "1"}) m = monitor.MonitorFail("fail", {"urgent": "0"}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.NONE) def test_alert_urgent(self): a = alerter.Alerter({"urgent": "1"}) m = monitor.MonitorFail("fail", {"urgent": "1"}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) def test_alert_alerter_not_urgent(self): a = alerter.Alerter({"urgent": "0"}) m = monitor.MonitorFail("fail", {"urgent": "1"}) m.run_test() self.assertEqual(a.should_alert(m), alerter.AlertType.FAILURE) class TestMessageBuilding(unittest.TestCase): def setUp(self): self.test_alerter = alerter.Alerter() self.freeze_time_value = "2020-03-10 09:00" self.tz = os.environ.get("TZ", "local") self.expected_time_string = ( arrow.get("2020-03-10 09:00:00+00:00").to(self.tz).format() ) a = arrow.now() self.utcoffset = a.utcoffset() def test_notification_format_failure(self): m = monitor.MonitorFail("test", {}) with freeze_time(self.freeze_time_value): m.run_test() self.assertEqual( self.test_alerter.build_message( alerter.AlertLength.NOTIFICATION, alerter.AlertType.FAILURE, m ), "Monitor test on %s failed" % self.test_alerter.hostname, ) def test_notification_format_success(self): m = monitor.MonitorNull("winning", {}) with freeze_time(self.freeze_time_value): for _ in range(0, 6): m.run_test() self.assertEqual( self.test_alerter.build_message( alerter.AlertLength.NOTIFICATION, alerter.AlertType.SUCCESS, m ), "Monitor winning on %s succeeded" % self.test_alerter.hostname, ) def test_oneline_format_failure(self): m = monitor.MonitorFail("test", {}) with freeze_time(self.freeze_time_value): m.run_test() self.assertEqual( self.test_alerter.build_message( alerter.AlertLength.ONELINE, alerter.AlertType.FAILURE, m ), "failure: test on {hostname} failed at {expected_time} (0+00:00:00): This monitor always fails.".format( hostname=util.short_hostname(), expected_time=self.expected_time_string, ), ) def test_oneline_format_success(self): m = monitor.MonitorNull("winning", {}) with freeze_time(self.freeze_time_value): for _ in range(0, 6): m.run_test() m.last_result = "a " * 80 desired = ( "success: winning on {hostname} succeeded at (0+00:00:00): ".format( hostname=util.short_hostname() ) + "a " * 80 ) output = self.test_alerter.build_message( alerter.AlertLength.ONELINE, alerter.AlertType.SUCCESS, m ) self.assertEqual(desired, output) def test_sms_format_failure(self): m = monitor.MonitorFail("test", {}) with freeze_time(self.freeze_time_value): m.run_test() self.assertEqual( self.test_alerter.build_message( alerter.AlertLength.SMS, alerter.AlertType.FAILURE, m ), "failure: test on {hostname} failed at {expected_time} (0+00:00:00): This monitor always fails.".format( hostname=util.short_hostname(), expected_time=self.expected_time_string, ), ) def test_sms_format_success(self): m = monitor.MonitorNull("winning", {}) with freeze_time(self.freeze_time_value): for _ in range(0, 6): m.run_test() m.last_result = "a " * 80 self.assertEqual( self.test_alerter.build_message( alerter.AlertLength.SMS, alerter.AlertType.SUCCESS, m ), textwrap.shorten( "success: winning on {hostname} succeeded at (0+00:00:00): {a}".format( hostname=util.short_hostname(), a=m.last_result ), width=160, placeholder="...", ), ) def test_full_format_failure(self): m = monitor.MonitorFail("test", {}) with freeze_time(self.freeze_time_value): m.run_test() self.assertEqual( self.test_alerter.build_message( alerter.AlertLength.FULL, alerter.AlertType.FAILURE, m ), textwrap.dedent( """ Monitor test on {hostname} failed! Failed at: {expected_time} (down 0+00:00:00) Virtual failure count: 1 Additional info: This monitor always fails. Description: A monitor which always fails. """.format( expected_time=self.expected_time_string, hostname=self.test_alerter.hostname, ) ), ) def test_full_format_failure_docs(self): m = monitor.MonitorFail("test", {"failure_doc": "whoops"}) with freeze_time(self.freeze_time_value): m.run_test() self.assertEqual( self.test_alerter.build_message( alerter.AlertLength.FULL, alerter.AlertType.FAILURE, m ), textwrap.dedent( """ Monitor test on {host} failed! Failed at: {expected_time} (down 0+00:00:00) Virtual failure count: 1 Additional info: This monitor always fails. Description: A monitor which always fails. Documentation: whoops """.format( expected_time=self.expected_time_string, host=self.test_alerter.hostname, ) ), ) def test_full_format_success(self): m = monitor.MonitorNull("winning", {}) with freeze_time(self.freeze_time_value): for _ in range(0, 6): m.run_test() self.assertEqual( self.test_alerter.build_message( alerter.AlertLength.FULL, alerter.AlertType.SUCCESS, m ), textwrap.dedent( """ Monitor winning on {host} succeeded! Recovered at: {expected_time} (was down for 0+00:00:00) Additional info: Description: (Monitor did not write an auto-biography.) """.format( # noqa: W291 expected_time=self.expected_time_string, host=self.test_alerter.hostname, ) ), ) def test_was_downtime(self): m = monitor.MonitorFail("test", {}) with freeze_time(self.freeze_time_value) as frozen_time: for _ in range(0, 6): m.run_test() frozen_time.tick(30) self.assertEqual(m.get_wasdowntime(), util.UpDownTime(0, 0, 2, 30)) class TestMessageBuildingTZ(TestMessageBuilding): def setUp(self): super().setUp() self.test_alerter._tz = "Europe/Warsaw" self.expected_time_string = "2020-03-10 10:00:00+01:00" class TestSNSAlerter(unittest.TestCase): def test_config(self): with self.assertRaises(util.AlerterConfigurationError): sns.SNSAlerter({}) with self.assertRaises(util.AlerterConfigurationError): sns.SNSAlerter({"topic": "a", "number": "b"}) def test_urgent(self): a = sns.SNSAlerter({"topic": "a"}) self.assertEqual(a.urgent, True) def test_not_urgent(self): a = sns.SNSAlerter({"topic": "a", "urgent": 0}) self.assertEqual(a.urgent, False) class TestDescription(unittest.TestCase): def setUp(self) -> None: self.tz = os.environ.get("TZ", "local") def test_times_always(self): a = alerter.Alerter({"times_type": "always"}) self.assertEqual(a._describe_times(), "(always)") def test_times_only_times(self): a = alerter.Alerter( {"times_type": "only", "time_lower": "09:00", "time_upper": "10:00"} ) self.assertEqual( a._describe_times(), f"only between 09:00 and 10:00 ({self.tz}) on any day" ) def test_times_only_days(self): a = alerter.Alerter( { "times_type": "only", "time_lower": "09:00", "time_upper": "10:00", "days": "0,1,2", } ) self.assertEqual( a._describe_times(), f"only between 09:00 and 10:00 ({self.tz}) on Mon, Tue, Wed", ) def test_times_not_time(self): a = alerter.Alerter( {"times_type": "not", "time_lower": "09:00", "time_upper": "10:00"} ) self.assertEqual( a._describe_times(), f"any time except between 09:00 and 10:00 ({self.tz}) on any day", ) def test_times_not_days(self): a = alerter.Alerter( { "times_type": "not", "time_lower": "09:00", "time_upper": "10:00", "days": "3,4,5,6", } ) self.assertEqual( a._describe_times(), f"any time except between 09:00 and 10:00 ({self.tz}) on Thu, Fri, Sat, Sun", ) simplemonitor-1.13.0/tests/test_envconfig.py000066400000000000000000000011221464501162400212250ustar00rootroot00000000000000# type: ignore import os import unittest from simplemonitor.util import envconfig os.environ["TEST_VALUE"] = "test1" class TestEnvConfig(unittest.TestCase): def test_EnvConfig(self): config = envconfig.EnvironmentAwareConfigParser() config.read("tests/monitor-env.ini") self.assertEqual(config.get("monitor", "monitors"), "tests/monitors-test1.ini") def test_EnvConfigKey(self): config = envconfig.EnvironmentAwareConfigParser() config.read("tests/monitor-env.ini") self.assertEqual(config.get("monitor-test1", "monitors"), "hello") simplemonitor-1.13.0/tests/test_fortysixelks.py000066400000000000000000000011441464501162400220210ustar00rootroot00000000000000# type: ignore import unittest from simplemonitor import util from simplemonitor.Alerters import fortysixelks class Test46Elks(unittest.TestCase): def test_46elks(self): config_options = {"username": "a", "password": "b", "target": "c"} config_options["sender"] = "ab" with self.assertRaises(util.AlerterConfigurationError): a = fortysixelks.FortySixElksAlerter(config_options=config_options) config_options["sender"] = "123456789012" a = fortysixelks.FortySixElksAlerter(config_options=config_options) self.assertEqual(a.sender, "12345678901") simplemonitor-1.13.0/tests/test_host.py000066400000000000000000000067171464501162400202430ustar00rootroot00000000000000# type: ignore import unittest from simplemonitor.Monitors import host class TestHostMonitors(unittest.TestCase): safe_config = {"partition": "/", "limit": "10G"} one_KB = 1024 one_MB = one_KB * 1024 one_GB = one_MB * 1024 one_TB = one_GB * 1024 def test_DiskSpace_brokenConfigOne(self): config_options = {} with self.assertRaises(ValueError): host.MonitorDiskSpace("test", config_options) def test_DiskSpace_brokenConfigTwo(self): config_options = {"partition": "/"} with self.assertRaises(ValueError): host.MonitorDiskSpace("test", config_options) def test_DiskSpace_brokenConfigThree(self): config_options = {"partition": "/", "limit": "moo"} with self.assertRaises(ValueError): host.MonitorDiskSpace("test", config_options) def test_DiskSpace_correctConfig(self): m = host.MonitorDiskSpace("test", self.safe_config) self.assertIsInstance(m, host.MonitorDiskSpace) def test_DiskSpace_meta(self): m = host.MonitorDiskSpace("test", self.safe_config) self.assertEqual( m.describe(), "Checking for at least 10.00GiB free space on /", "Failed to verify description", ) self.assertTupleEqual( m.get_params(), (10 * self.one_GB, "/"), "Failed to verify params" ) def test_DiskSpace_free(self): # Hopefully our test machine has at least 1 byte free on / config_options = {"partition": "/", "limit": "1"} m = host.MonitorDiskSpace("test", config_options) m.run_test() self.assertTrue(m.test_success()) # and hopefully it has a sensible-sized root partition config_options = {"partition": "/", "limit": "100000G"} m = host.MonitorDiskSpace("test", config_options) m.run_test() self.assertFalse(m.test_success()) def test_DiskSpace_invalid_partition(self): config_options = {"partition": "moo", "limit": "1"} m = host.MonitorDiskSpace("test", config_options) m.run_test() self.assertFalse(m.test_success(), "Monitor did not fail") self.assertRegex( m.last_result, "Couldn't get free disk space", "Monitor did not report error correctly", ) def test_DiskSpace_get_params(self): config_options = {"partition": "/", "limit": "1"} m = host.MonitorDiskSpace("test", config_options) self.assertTupleEqual(m.get_params(), (1, "/")) def test_Filestat_get_params(self): config_options = {"maxage": "10", "minsize": "20", "filename": "/test"} m = host.MonitorFileStat("test", config_options) self.assertTupleEqual(m.get_params(), ("/test", 20, 10)) def test_Command_get_params(self): config_options = {"command": "ls /", "result_regexp": "moo", "result_max": "10"} m = host.MonitorCommand("test", config_options) self.assertTupleEqual(m.get_params(), (["ls", "/"], "moo", None, False)) config_options = {"command": "ls /", "result_max": "10"} m = host.MonitorCommand("test", config_options) self.assertTupleEqual(m.get_params(), (["ls", "/"], "", 10, False)) config_options = {"command": "ls /", "result_max": "10", "show_output": True} m = host.MonitorCommand("test", config_options) self.assertTupleEqual(m.get_params(), (["ls", "/"], "", 10, True)) if __name__ == "__main__": unittest.main() simplemonitor-1.13.0/tests/test_htmllogger.py000066400000000000000000000027011464501162400214170ustar00rootroot00000000000000# type: ignore import os import unittest from simplemonitor.Loggers.file import HTMLLogger from simplemonitor.Monitors.monitor import MonitorNull class TestHTMLLogger(unittest.TestCase): def setUp(self): self._test_path = "test_html" try: os.mkdir(self._test_path) except FileExistsError: pass def test_html_logger(self): config_options = { "folder": "test_html", "copy_resources": True, "filename": "status.html", } test_logger = HTMLLogger(config_options) monitor = MonitorNull() monitor.run_test() test_logger.start_batch() test_logger.save_result2("test", monitor) test_logger.process_batch() test_logger.end_batch() self.assertTrue( os.path.exists(os.path.join("test_html", "status.html")), "status.html was not created", ) self.assertTrue( os.path.exists(os.path.join("test_html", "main.bundle.js")), "main.bundle.js was not copied", ) def tearDown(self): if os.path.isdir(self._test_path): for filename in os.listdir(self._test_path): try: os.unlink(os.path.join(self._test_path, filename)) except IOError: pass try: os.rmdir(self._test_path) except IOError: pass simplemonitor-1.13.0/tests/test_logger.py000066400000000000000000000375331464501162400205450ustar00rootroot00000000000000# type: ignore import os.path import socket import tempfile import time import unittest from pathlib import Path from unittest.mock import patch from freezegun import freeze_time from simplemonitor.Loggers import logger from simplemonitor.Loggers.file import FileLogger, FileLoggerNG, HTMLLogger from simplemonitor.Monitors.monitor import MonitorFail, MonitorNull from simplemonitor.simplemonitor import SimpleMonitor from simplemonitor.version import VERSION class TestLogger(unittest.TestCase): def test_default(self): config_options = {} test_logger = logger.Logger(config_options) self.assertEqual( test_logger.dependencies, [], "logger did not set default dependencies" ) def test_dependencies(self): config_options = {"depend": "a, b"} test_logger = logger.Logger(config_options) self.assertEqual( test_logger.dependencies, ["a", "b"], "logger did not set dependencies to given list", ) with self.assertRaises(TypeError): test_logger.dependencies = "moo" test_logger.dependencies = ["b", "c"] self.assertEqual( test_logger.check_dependencies(["a"]), True, "logger thought a dependency had failed", ) self.assertEqual( test_logger.connected, True, "logger did not think it was connected" ) self.assertEqual( test_logger.check_dependencies(["a", "b"]), False, "logger did not think a dependency failed", ) self.assertEqual( test_logger.connected, False, "logger thought it was connected" ) def test_groups(self): with patch.object(logger.Logger, "save_result2") as mock_method: this_logger = logger.Logger({"groups": "nondefault"}) s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("test", MonitorNull()) s.log_result(this_logger) mock_method.assert_not_called() with patch.object(logger.Logger, "save_result2") as mock_method: this_logger = logger.Logger({"groups": "nondefault"}) s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("test", MonitorNull("unnamed", {"group": "nondefault"})) s.log_result(this_logger) mock_method.assert_called_once() def test_skip_group_logger(self): with patch.object(logger.Logger, "save_result2") as mock_method: this_logger = logger.Logger({"groups": "test"}) s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("test", MonitorNull("unnamed", {})) s.log_result(this_logger) mock_method.assert_not_called() def test_skip_group_monitor(self): with patch.object(logger.Logger, "save_result2") as mock_method: this_logger = logger.Logger({}) s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("test", MonitorNull("unnamed", {"group": "test"})) s.log_result(this_logger) mock_method.assert_not_called() def test_groups_match(self): with patch.object(logger.Logger, "save_result2") as mock_method: this_logger = logger.Logger({"groups": "test"}) s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("test", MonitorNull("unnamed", {"group": "test"})) s.log_result(this_logger) mock_method.assert_called_once() def test_groups_multi_match(self): with patch.object(logger.Logger, "save_result2") as mock_method: this_logger = logger.Logger({"groups": "test1, test2"}) s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("test", MonitorNull("unnamed", {"group": "test1"})) s.log_result(this_logger) mock_method.assert_called_once() def test_groups_all_match(self): with patch.object(logger.Logger, "save_result2") as mock_method: this_logger = logger.Logger({"groups": "_all"}) s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("test", MonitorNull("unnamed", {"group": "test1"})) s.log_result(this_logger) mock_method.assert_called_once() def test_heartbeat_off(self): with patch.object(logger.Logger, "save_result2") as mock_method: this_logger = logger.Logger({}) s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("test", MonitorNull("unnamed")) s.add_logger("test", this_logger) s.run_loop() s.run_loop() self.assertEqual(mock_method.call_count, 2) def test_heartbeat_on(self): with patch.object(logger.Logger, "save_result2") as mock_method: this_logger = logger.Logger({"heartbeat": 1}) this_monitor = MonitorNull("unnamed", {"gap": 10}) s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("test", this_monitor) s.add_logger("test", this_logger) s.run_loop() s.run_loop() mock_method.assert_called_once() def test_reset_monitor(self): s = SimpleMonitor(Path("tests/monitor-empty.ini")) s.add_monitor("monitor1", MonitorNull("monitor1")) s.add_monitor("monitor2", MonitorNull("monitor2", {"depend": "monitor1"})) s.run_loop() self.assertTrue(s.monitors["monitor1"].ran_this_time) self.assertTrue(s.monitors["monitor2"].ran_this_time) self.assertEqual(s.monitors["monitor2"].remaining_dependencies, []) s.reset_monitors() self.assertFalse(s.monitors["monitor1"].ran_this_time) self.assertFalse(s.monitors["monitor2"].ran_this_time) self.assertEqual(s.monitors["monitor2"].remaining_dependencies, ["monitor1"]) class TestFileLogger(unittest.TestCase): @freeze_time("2020-04-18 12:00+00:00") def test_file_append(self): temp_logfile = tempfile.mkstemp()[1] with open(temp_logfile, "w") as fh: fh.write("the first line\n") file_logger = FileLogger({"filename": temp_logfile, "buffered": False}) monitor = MonitorNull() monitor.run_test() file_logger.save_result2("null", monitor) self.assertTrue(os.path.exists(temp_logfile)) ts = str(int(time.time())) with open(temp_logfile, "r") as fh: self.assertEqual(fh.readline().strip(), "the first line") self.assertEqual( fh.readline().strip(), "{} simplemonitor starting".format(ts) ) self.assertEqual(fh.readline().strip(), "{} null: ok (0.000s)".format(ts)) try: os.unlink(temp_logfile) except PermissionError: # Windows won't remove a file which is in use pass @freeze_time("2020-04-18 12:00+01:00") def test_file_nonutc(self): temp_logfile = tempfile.mkstemp()[1] file_logger = FileLogger({"filename": temp_logfile, "buffered": False}) monitor = MonitorNull() monitor.run_test() file_logger.save_result2("null", monitor) self.assertTrue(os.path.exists(temp_logfile)) ts = str(int(time.time())) with open(temp_logfile, "r") as fh: self.assertEqual( fh.readline().strip(), "{} simplemonitor starting".format(ts) ) self.assertEqual(fh.readline().strip(), "{} null: ok (0.000s)".format(ts)) try: os.unlink(temp_logfile) except PermissionError: # Windows won't remove a file which is in use pass @freeze_time("2020-04-18 12:00+00:00") def test_file_utc_iso(self): temp_logfile = tempfile.mkstemp()[1] file_logger = FileLogger( {"filename": temp_logfile, "buffered": False, "dateformat": "iso8601"} ) monitor = MonitorNull() monitor.run_test() file_logger.save_result2("null", monitor) self.assertTrue(os.path.exists(temp_logfile)) with open(temp_logfile, "r") as fh: self.assertEqual( fh.readline().strip(), "2020-04-18 12:00:00+00:00 simplemonitor starting", ) self.assertEqual( fh.readline().strip(), "2020-04-18 12:00:00+00:00 null: ok (0.000s)", ) try: os.unlink(temp_logfile) except PermissionError: # Windows won't remove a file which is in use pass @freeze_time("2020-04-18 12:00+01:00") def test_file_nonutc_iso_utctz(self): temp_logfile = tempfile.mkstemp()[1] file_logger = FileLogger( {"filename": temp_logfile, "buffered": False, "dateformat": "iso8601"} ) monitor = MonitorNull() monitor.run_test() file_logger.save_result2("null", monitor) self.assertTrue(os.path.exists(temp_logfile)) with open(temp_logfile, "r") as fh: self.assertEqual( fh.readline().strip(), "2020-04-18 11:00:00+00:00 simplemonitor starting", ) self.assertEqual( fh.readline().strip(), "2020-04-18 11:00:00+00:00 null: ok (0.000s)", ) try: os.unlink(temp_logfile) except PermissionError: # Windows won't remove a file which is in use pass @freeze_time("2020-04-18 12:00+01:00") def test_file_nonutc_iso_nonutctz(self): temp_logfile = tempfile.mkstemp()[1] file_logger = FileLogger( { "filename": temp_logfile, "buffered": False, "dateformat": "iso8601", "tz": "Europe/Warsaw", } ) monitor = MonitorNull() monitor.run_test() file_logger.save_result2("null", monitor) self.assertTrue(os.path.exists(temp_logfile)) with open(temp_logfile, "r") as fh: self.assertEqual( fh.readline().strip(), "2020-04-18 13:00:00+02:00 simplemonitor starting", ) self.assertEqual( fh.readline().strip(), "2020-04-18 13:00:00+02:00 null: ok (0.000s)", ) try: os.unlink(temp_logfile) except PermissionError: # Windows won't remove a file which is in use pass class TestLogFileNG(unittest.TestCase): @freeze_time("2020-04-18 12:00+00:00") def test_file_time(self): temp_logfile = tempfile.mkstemp()[1] file_logger = FileLoggerNG({"filename": temp_logfile, "rotation_type": "time"}) monitor = MonitorNull() monitor.run_test() file_logger.save_result2("null", monitor) self.assertTrue(os.path.exists(temp_logfile)) ts = str(int(time.time())) with open(temp_logfile, "r") as fh: self.assertEqual( fh.readline().strip(), "{} null: ok (0.000s) ()".format(ts) ) try: os.unlink(temp_logfile) except PermissionError: # Windows won't remove a file which is in use pass @freeze_time("2020-04-18 12:00+00:00") def test_file_size(self): temp_logfile = tempfile.mkstemp()[1] file_logger = FileLoggerNG( {"filename": temp_logfile, "rotation_type": "size", "max_bytes": "1K"} ) monitor = MonitorNull() monitor.run_test() file_logger.save_result2("null", monitor) self.assertTrue(os.path.exists(temp_logfile)) ts = str(int(time.time())) with open(temp_logfile, "r") as fh: self.assertEqual( fh.readline().strip(), "{} null: ok (0.000s) ()".format(ts) ) try: os.unlink(temp_logfile) except PermissionError: # Windows won't remove a file which is in use pass @freeze_time("2020-04-18 12:00+00:00") def test_file_only_failures(self): temp_logfile = tempfile.mkstemp()[1] file_logger = FileLoggerNG( { "filename": temp_logfile, "rotation_type": "size", "max_bytes": "1K", "only_failures": "1", "dateformat": "iso8601", } ) monitor = MonitorNull() monitor.run_test() monitor2 = MonitorFail("fail", {}) monitor2.run_test() file_logger.save_result2("null", monitor) file_logger.save_result2("fail", monitor2) self.assertTrue(os.path.exists(temp_logfile)) ts = "2020-04-18 12:00:00+00:00" with open(temp_logfile, "r") as fh: self.assertEqual( fh.readline().strip(), "{} fail: failed since {}; VFC=1 ({}) (0.000s)".format( ts, ts, monitor2.last_result ), ) try: os.unlink(temp_logfile) except PermissionError: # Windows won't remove a file which is in use pass def test_file_missing_rotation(self): with self.assertRaises(ValueError): _ = FileLoggerNG({"filename": "something.log"}) def test_file_missing_bytes(self): with self.assertRaises(ValueError): _ = FileLoggerNG({"filename": "something.log", "rotation_type": "size"}) def test_file_bad_rotation(self): with self.assertRaises(ValueError): _ = FileLoggerNG({"filename": "something.log", "rotation_type": "magic"}) class TestHTMLLogger(unittest.TestCase): def setUp(self) -> None: self.html_dir = tempfile.mkdtemp() print(f"writing html tests to {self.html_dir}") @freeze_time("2020-04-18 12:00:00+00:00") def _write_html(self, logger_options: dict = None) -> str: if logger_options is None: logger_options = {} with patch.object(socket, "gethostname", return_value="fake_hostname.local"): temp_htmlfile = tempfile.mkstemp()[1] logger_options.update({"filename": temp_htmlfile, "folder": self.html_dir}) html_logger = HTMLLogger(logger_options) monitor1 = MonitorNull(config_options={"gps": "52.01,1.01"}) monitor2 = MonitorFail("fail", {"gps": "52.02,1.02"}) monitor3 = MonitorFail("disabled", {"enabled": 0, "gps": "52.03,1.03"}) monitor1.run_test() monitor2.run_test() html_logger.start_batch() html_logger.save_result2("null", monitor1) html_logger.save_result2("fail", monitor2) html_logger.save_result2("disabled", monitor3) html_logger.end_batch() print(temp_htmlfile) return temp_htmlfile def _compare_files(self, test_file, golden_file): self.maxDiff = 6200 with open(test_file) as test_fh, open(golden_file) as golden_fh: golden_data = golden_fh.read() golden_data = golden_data.replace("__VERSION__", VERSION) self.assertMultiLineEqual(golden_data, test_fh.read()) def test_html(self): test_file = self._write_html() golden_file = "tests/html/test1.html" self._compare_files(test_file, golden_file) def test_html_tz(self): test_file = self._write_html({"tz": "Europe/Warsaw"}) golden_file = "tests/html/test2.html" self._compare_files(test_file, golden_file) def test_html_map(self): test_file = self._write_html( {"map_start": [52, 1, 12], "map": 1, "map_token": "secret_token"} ) golden_file = "tests/html/map1.html" self._compare_files(test_file, golden_file) def test_config_start(self): with self.assertRaises(RuntimeError): _ = HTMLLogger({"map": "1", "filename": "something"}) with self.assertRaises(RuntimeError): _ = HTMLLogger({"map": "1", "map_start": "1, 2", "filename": "something"}) simplemonitor-1.13.0/tests/test_main.py000066400000000000000000000130661464501162400202050ustar00rootroot00000000000000# type: ignore import configparser import os import os.path import sys import tempfile import time import unittest from pathlib import Path from unittest.mock import patch from simplemonitor import Alerters, monitor, simplemonitor from simplemonitor.Loggers import network from simplemonitor.Monitors.monitor import MonitorFail, MonitorNull class TestMonitor(unittest.TestCase): def test_MonitorConfigInterval(self): with self.assertRaises(configparser.NoOptionError): testargs = ["monitor.py", "-f", "tests/monitor-nointerval.ini"] with patch.object(sys, "argv", testargs): monitor.main() with self.assertRaises(ValueError): testargs = ["monitor.py", "-f", "tests/monitor-badinterval.ini"] with patch.object(sys, "argv", testargs): monitor.main() def test_file_hup(self): temp_file_info = tempfile.mkstemp() os.close(temp_file_info[0]) temp_file_name = temp_file_info[1] s = simplemonitor.SimpleMonitor( Path("tests/monitor-empty.ini"), hup_file=temp_file_name ) s._check_hup_file() time.sleep(2) Path(temp_file_name).touch() self.assertEqual( s._check_hup_file(), True, "check_hup_file did not trigger", ) self.assertEqual( s._check_hup_file(), False, "check_hup_file should not have triggered", ) os.unlink(temp_file_name) def test_disabled_monitor_not_run(self): s = simplemonitor.SimpleMonitor(Path("tests/monitor-empty.ini")) m = MonitorNull("unnamed", {"enabled": 0}) with patch.object(m, "run_test") as mock_method: s.add_monitor("test", m) s.run_tests() mock_method.assert_not_called() def test_enabled_monitor_run(self): s = simplemonitor.SimpleMonitor(Path("tests/monitor-empty.ini")) m = MonitorNull("unnamed", {"enabled": 1}) with patch.object(m, "run_test") as mock_method: s.add_monitor("test", m) s.run_tests() mock_method.assert_called_once() class TestPidFile(unittest.TestCase): def test_pidfile(self): s = simplemonitor.SimpleMonitor("tests/monitor-empty.ini") s.pidfile = "__pid_test" try: os.unlink(s.pidfile) except IOError: pass s._create_pid_file() self.assertTrue(os.path.exists(s.pidfile)) s._remove_pid_file() self.assertFalse(os.path.exists(s.pidfile)) class TestSanity(unittest.TestCase): def test_config_has_alerting(self): m = simplemonitor.SimpleMonitor("tests/monitor-empty.ini") self.assertFalse(m.verify_alerting()) m.add_alerter("testing", Alerters.alerter.Alerter({})) self.assertTrue(m.verify_alerting()) m = simplemonitor.SimpleMonitor("tests/monitor-empty.ini") m.add_logger( "testing", network.NetworkLogger({"host": "localhost", "port": 1234, "key": "hello"}), ) self.assertTrue(m.verify_alerting()) class TestNetworkMonitors(unittest.TestCase): def test_simple(self): s = simplemonitor.SimpleMonitor("tests/monitor-empty.ini") m = MonitorNull() data = { "test1": {"cls_type": m.monitor_type, "data": m.to_python_dict()}, "test2": {"cls_type": m.monitor_type, "data": m.to_python_dict()}, } s.update_remote_monitor(data, "remote.host") self.assertIn("remote.host", s.remote_monitors) self.assertIn("test1", s.remote_monitors["remote.host"]) self.assertIn("test2", s.remote_monitors["remote.host"]) def test_removal(self): s = simplemonitor.SimpleMonitor("tests/monitor-empty.ini") m = MonitorNull() data = { "test1": {"cls_type": m.monitor_type, "data": m.to_python_dict()}, "test2": {"cls_type": m.monitor_type, "data": m.to_python_dict()}, } s.update_remote_monitor(data, "remote.host") data = { "test1": {"cls_type": m.monitor_type, "data": m.to_python_dict()}, } s.update_remote_monitor(data, "remote.host") self.assertIn("test1", s.remote_monitors["remote.host"]) self.assertNotIn("test2", s.remote_monitors["remote.host"]) class TestFailedLogic(unittest.TestCase): def test_disabled(self): s = simplemonitor.SimpleMonitor("tests/monitor-empty.ini") m1 = MonitorFail("fail", config_options={}) m2 = MonitorNull("null", config_options={"enabled": "0"}) s.add_monitor("fail", m1) s.add_monitor("null", m2) m2.reset_dependencies() m1.run_test() failed = s._failed_monitors() self.assertListEqual(["fail", "null"], failed) def test_no_disabled(self): s = simplemonitor.SimpleMonitor("tests/monitor-empty.ini") m1 = MonitorFail("fail", config_options={}) m2 = MonitorNull("null", config_options={}) s.add_monitor("fail", m1) s.add_monitor("null", m2) m2.reset_dependencies() m1.run_test() failed = s._failed_monitors() self.assertListEqual(["fail"], failed) def test_no_failed(self): s = simplemonitor.SimpleMonitor("tests/monitor-empty.ini") m1 = MonitorNull("null1", config_options={}) m2 = MonitorNull("null2", config_options={}) s.add_monitor("null1", m1) s.add_monitor("null2", m2) m2.reset_dependencies() m1.run_test() failed = s._failed_monitors() self.assertListEqual([], failed) simplemonitor-1.13.0/tests/test_monitor.py000066400000000000000000000331541464501162400207500ustar00rootroot00000000000000# type: ignore import datetime import os import platform import time import unittest from pathlib import Path import arrow from simplemonitor.Monitors.compound import CompoundMonitor from simplemonitor.Monitors.monitor import Monitor, MonitorFail, MonitorNull from simplemonitor.simplemonitor import SimpleMonitor from simplemonitor.util import MonitorState, UpDownTime class TestMonitor(unittest.TestCase): safe_config = {"partition": "/", "limit": "10G"} one_KB = 1024 one_MB = one_KB * 1024 one_GB = one_MB * 1024 one_TB = one_GB * 1024 def test_MonitorInit(self): m = Monitor( config_options={ "depend": "a, b", "urgent": 0, "tolerance": 2, "remote_alert": 1, "recover_command": "true", "recovered_command": "true", } ) self.assertEqual(m.name, "unnamed", "Monitor did not set name") self.assertEqual(m.urgent, 0, "Monitor did not set urgent") self.assertEqual(m._tolerance, 2, "Monitor did not set tolerance") self.assertTrue(m.remote_alerting, "Monitor did not set remote_alerting") self.assertEqual( m._recover_command, "true", "Monitor did not set recover_command" ) self.assertEqual( m._recovered_command, "true", "Monitor did not set recovered_command" ) with self.assertRaises(ValueError): m.minimum_gap = -1 with self.assertRaises(TypeError): m.minimum_gap = "zero" with self.assertRaises(TypeError): m.notify = "true" m.notify = False self.assertEqual(m.notify, False, "monitor did not unset notify") m.notify = True self.assertEqual(m.notify, True, "monitor did not set notify") with self.assertRaises(TypeError): m.urgent = "true" m.urgent = True self.assertEqual(m.urgent, True, "monitor did not set urgent") m.urgent = 0 self.assertEqual(m.urgent, False, "monitor did not unset urgent with an int") m.urgent = 1 self.assertEqual(m.urgent, True, "monitor did not set urgent with an int") with self.assertRaises(TypeError): m.dependencies = "no at a list" m.dependencies = ["a", "b"] self.assertEqual( m.dependencies, ["a", "b"], "monitor did not set or return dependencies correctly", ) self.assertEqual( m.remaining_dependencies, ["a", "b"], "monitor did not set remaining dependencies", ) m.dependency_succeeded("a") self.assertEqual( m.remaining_dependencies, ["b"], "monitor did not remove dependencies from list", ) m.dependency_succeeded("a") # should be safe to remove again self.assertEqual( m.state(), MonitorState.UNKNOWN, "monitor did not initialise state correctly", ) def test_MonitorDependencies(self): m = Monitor() m.dependencies = ["a", "b", "c"] m.dependency_succeeded("b") self.assertEqual( m.remaining_dependencies, ["a", "c"], "monitor did not remove succeeded dependency", ) m.reset_dependencies() self.assertEqual( m.remaining_dependencies, ["a", "b", "c"], "monitor did not reset dependencies", ) def test_MonitorSuccess(self): m = Monitor() m.record_success("yay") self.assertEqual(m.error_count, 0, "Error count is not 0") self.assertEqual(m.get_success_count(), 1, "Success count is not 1") self.assertEqual(m.tests_run, 1, "Tests run is not 1") self.assertFalse(m.was_skipped, "was_skipped is not false") self.assertEqual(m.last_result, "yay", "Last result is not correct") self.assertEqual( m.state(), MonitorState.OK, "monitor did not report state correctly" ) self.assertEqual(m.virtual_fail_count(), 0, "monitor did not report VFC of 0") self.assertEqual(m.test_success(), True, "test_success is not True") def test_MonitorFail(self): m = Monitor() m.record_fail("boo") self.assertEqual(m.error_count, 1, "Error count is not 1") self.assertEqual(m.get_success_count(), 0, "Success count is not 0") self.assertEqual(m.tests_run, 1, "Tests run is not 1") self.assertFalse(m.was_skipped, "was_skipped is not false") self.assertEqual(m.last_result, "boo", "Last result is not correct") self.assertEqual( m.state(), MonitorState.FAILED, "monitor did not report state correctly" ) self.assertEqual( m.virtual_fail_count(), 1, "monitor did not calculate VFC correctly" ) self.assertEqual(m.test_success(), False, "test_success is not False") self.assertEqual(m.first_failure(), True, "First failure is not False") m.record_fail("cows") self.assertEqual(m.error_count, 2, "Error count is not 2") self.assertEqual(m.first_failure(), False, "first_failure is not False") self.assertEqual(m.state(), MonitorState.FAILED, "state is not False") def test_MonitorWindows(self): m = Monitor() if platform.system() == "Windows": self.assertTrue(m.is_windows()) else: self.assertFalse(m.is_windows()) def test_MonitorSkip(self): m = Monitor() m.record_skip("a") self.assertEqual(m.get_success_count(), 1, "Success count is not 1") self.assertTrue(m.was_skipped, "was_skipped is not true") self.assertEqual(m.skip_dep, "a", "skip_dep is not correct") self.assertTrue(m.skipped(), "skipped() is not true") self.assertEqual( m.state(), MonitorState.SKIPPED, "monitor did not report state correctly" ) def test_MonitorConfig(self): config_options = { "test_string": "a string", "test_int": "3", "test_[int]": "1,2, 3", "test_[str]": "a, b,c", "test_bool1": "1", "test_bool2": "yes", "test_bool3": "true", "test_bool4": "0", } m = Monitor("test", config_options) self.assertEqual(m.get_config_option("test_string"), "a string") self.assertEqual(m.get_config_option("test_int", required_type="int"), 3) self.assertEqual( m.get_config_option("test_[int]", required_type="[int]"), [1, 2, 3] ) self.assertEqual( m.get_config_option("test_[str]", required_type="[str]"), ["a", "b", "c"] ) for bool_test in list(range(1, 4)): self.assertEqual( m.get_config_option( "test_bool{0}".format(bool_test), required_type="bool" ), True, ) self.assertEqual(m.get_config_option("test_bool4", required_type="bool"), False) def test_downtime(self): m = Monitor() m._failed_at = arrow.utcnow() self.assertEqual(m.get_downtime(), UpDownTime()) m._failed_at = None self.assertEqual(m.get_downtime(), UpDownTime()) now = arrow.utcnow() two_h_thirty_m_ago = now - datetime.timedelta(hours=2, minutes=30) yesterday = now - datetime.timedelta(days=1) m._failed_at = two_h_thirty_m_ago self.assertEqual(m.get_downtime(), UpDownTime(0, 2, 30, 0)) m._failed_at = yesterday self.assertEqual(m.get_downtime(), UpDownTime(1, 0, 0, 0)) def test_sighup(self): m = SimpleMonitor("tests/monitor-empty.ini") m._load_monitors([Path("tests/monitors-prehup.ini")]) self.assertEqual(m._need_hup, False, "need_hup did not start False") m._handle_sighup(None, None) self.assertEqual(m._need_hup, True, "need_hup did not get set to True") self.assertEqual( m.monitors["monitor1"].monitor_type, "null", "monitor1 did not load correctly", ) self.assertEqual( m.monitors["monitor2"].monitor_type, "host", "monitor2 did not load correctly", ) self.assertEqual( m.monitors["monitor2"].host, "127.0.0.1", "monitor2 did not load correctly" ) m._load_monitors([Path("tests/monitors-posthup.ini")]) self.assertEqual( m.monitors["monitor1"].monitor_type, "null", "monitor1 changed type" ) self.assertEqual( m.monitors["monitor2"].monitor_type, "host", "monitor2 changed type" ) self.assertEqual( m.monitors["monitor2"].host, "127.0.0.2", "monitor2 did not update config" ) def test_should_run(self): m = Monitor() m.minimum_gap = 0 self.assertEqual(m.should_run(), True, "monitor did not should_run with 0 gap") m.minimum_gap = 300 m.record_fail("test") self.assertEqual( m.should_run(), True, "monitor did not should_run when it's failing" ) m.record_success("test") m._last_run = 0 self.assertEqual( m.should_run(), True, "monitor did not should_run when it had never run" ) m._last_run = time.time() - 350 self.assertEqual( m.should_run(), True, "monitor did not should_run when the gap was large enough", ) m._last_run = time.time() self.assertEqual( m.should_run(), False, "monitor did should_run when it shouldn't have" ) def test_compound_partial_fail(self): m = MonitorNull() m2 = MonitorFail("fail1", {}) m3 = MonitorFail("fail2", {}) compound_monitor = CompoundMonitor("compound", {"monitors": ["m", "m2", "m3"]}) monitors = {"m": m, "m2": m2, "m3": m3} compound_monitor.set_mon_refs(monitors) compound_monitor.post_config_setup() self.assertIn( "m", compound_monitor.all_monitors, "compound all_monitors not set" ) self.assertIn("m", compound_monitor.m, "compound m not set") m.run_test() m2.run_test() m3.run_test() result = compound_monitor.run_test() # should be ok because not all have failed self.assertEqual(result, True, "compound monitor should not have failed") self.assertEqual( compound_monitor.virtual_fail_count(), 0, "compound monitor should not report fail count", ) self.assertEqual( compound_monitor.get_result(), "2 of 3 services failed. Fail after: 3", "compound monitor did not report failures properly", ) def test_compound_no_fail(self): m = MonitorNull() m2 = MonitorNull() compound_monitor = CompoundMonitor("compound", {"monitors": ["m", "m2"]}) monitors = {"m": m, "m2": m2} compound_monitor.set_mon_refs(monitors) compound_monitor.post_config_setup() self.assertIn( "m", compound_monitor.all_monitors, "compound all_monitors not set" ) self.assertIn("m", compound_monitor.m, "compound m not set") m.run_test() m2.run_test() result = compound_monitor.run_test() # should be ok because not all have failed self.assertEqual(result, True, "compound monitor should not have failed") self.assertEqual( compound_monitor.virtual_fail_count(), 0, "compound monitor should not report fail count", ) self.assertEqual( compound_monitor.get_result(), "All 2 services OK", "compound monitor did not report status properly", ) def test_compound_fail(self): m = MonitorNull() m2 = MonitorFail("fail1", {}) m3 = MonitorFail("fail2", {}) compound_monitor = CompoundMonitor("compound", {"monitors": ["m2", "m3"]}) monitors = {"m": m, "m2": m2, "m3": m3} compound_monitor.set_mon_refs(monitors) compound_monitor.post_config_setup() self.assertIn( "m", compound_monitor.all_monitors, "compound all_monitors not set" ) self.assertIn("m2", compound_monitor.m, "compound m not set") self.assertNotIn( "m", compound_monitor.m, "compound m should not include monitor m" ) m2.run_test() m3.run_test() result = compound_monitor.run_test() # should be ok because not all have failed self.assertEqual(result, False, "compound monitor should have failed") self.assertEqual( compound_monitor.virtual_fail_count(), 1, "compound monitor should report fail count", ) self.assertEqual( compound_monitor.get_result(), "2 of 2 services failed. Fail after: 2", "compound monitor did not report failures properly", ) def test_recovery(self): m = MonitorFail("fail1", {"recover_command": "touch did_recovery"}) try: os.unlink("did_recovery") except FileNotFoundError: pass m.run_test() m.attempt_recover() # throws an exception if the file isn't there os.stat("did_recovery") def test_recovered(self): m = MonitorFail("fail9", {"recovered_command": "touch did_recovered"}) try: os.unlink("did_recovered") except FileNotFoundError: pass for i in range(0, 6): m.run_test() m.run_recovered() os.stat("did_recovered") simplemonitor-1.13.0/tests/test_network_new.py000066400000000000000000000043271464501162400216230ustar00rootroot00000000000000import unittest from requests import Response from requests.auth import HTTPBasicAuth from unittest.mock import patch, Mock from simplemonitor.Monitors import MonitorHTTP from simplemonitor.util import MonitorState class TestMonitorHTTP(unittest.TestCase): def test_get_ok_default_params(self): response_mock = Mock(spec=Response) response_mock.status_code = 200 monitor = MonitorHTTP( name="test_http_monitor", config_options={ "url": "http://example.com", "urgent": 0, "tolerance": 1, "remote_alert": 1, } ) with patch("requests.get", return_value=response_mock) as mock: monitor.run_test() result = monitor.get_result() state = monitor.state() mock.assert_called_once_with( "http://example.com", headers=None, timeout=5, verify=True, allow_redirects=True, ) self.assertEqual(state, MonitorState.OK) self.assertIn('200', result) def test_get_ok_non_default_params(self): response_mock = Mock(spec=Response) response_mock.status_code = 200 monitor = MonitorHTTP( name="test_http_monitor", config_options={ "url": "http://example.com", "urgent": 0, "tolerance": 1, "remote_alert": 1, "verify_hostname": False, "allow_redirects": False, "username": "test", "password": "pass", "headers": '{"test": "header"}', "timeout": 10, } ) with patch("requests.get", return_value=response_mock) as mock: monitor.run_test() result = monitor.get_result() state = monitor.state() mock.assert_called_once_with( "http://example.com", headers={"test": "header"}, timeout=10, verify=False, allow_redirects=False, auth=HTTPBasicAuth("test", "pass") ) self.assertEqual(state, MonitorState.OK) self.assertIn('200', result) pass simplemonitor-1.13.0/tests/test_service.py000066400000000000000000000033731464501162400207210ustar00rootroot00000000000000# type: ignore import unittest import subprocess from simplemonitor.Monitors import service from unittest.mock import patch class TestUnixServiceMonitors(unittest.TestCase): @patch("subprocess.run") def test_UnixService_ok(self, subprocess_run_fn): config_options = {"service": "unittest"} subprocess_run_fn.return_value = subprocess.CompletedProcess( ["service", config_options.get("service"), "status"], returncode=1 ) m = service.MonitorUnixService("unittest", config_options) m.run_test() self.assertFalse(m.test_success()) self.assertEqual(m.error_count, 1) m.run_test() self.assertFalse(m.test_success()) self.assertEqual(m.error_count, 2) @patch("subprocess.run") def test_UnixService_raiseFileNotFoundError(self, subprocess_run_fn): config_options = {"service": "unittest"} subprocess_run_fn.side_effect = FileNotFoundError( "[Errno 2] No such file or directory: 'service'" ) m = service.MonitorUnixService("unittest", config_options) self.assertRaises(FileNotFoundError, m.run_test) @patch("subprocess.run") def test_UnixService_raiseSubprocessError(self, subprocess_run_fn): config_options = {"service": "unittest"} subprocess_run_fn.side_effect = subprocess.SubprocessError( ["service", config_options.get("service"), "status"] ) m = service.MonitorUnixService("unittest", config_options) m.run_test() self.assertFalse(m.test_success()) self.assertEqual(m.error_count, 1) m.run_test() self.assertFalse(m.test_success()) self.assertEqual(m.error_count, 2) if __name__ == "__main__": unittest.main() simplemonitor-1.13.0/tests/test_util.py000066400000000000000000000177631464501162400202460ustar00rootroot00000000000000# type: ignore import datetime import unittest import arrow from simplemonitor import util class TestUtil(unittest.TestCase): one_KB = 1024 one_MB = one_KB * 1024 one_GB = one_MB * 1024 one_TB = one_GB * 1024 def test_Config(self): config_options = { "test_string": "a string", "test_int": "3", "test_[int]": "1,2, 3", "test_[str]": "a, b,c", "test_bool1": "1", "test_bool2": "yes", "test_bool3": "true", "test_bool4": "0", } self.assertEqual( util.get_config_option(config_options, "test_string"), "a string" ) self.assertEqual( util.get_config_option(config_options, "test_int", required_type="int"), 3 ) self.assertEqual( util.get_config_option(config_options, "test_[int]", required_type="[int]"), [1, 2, 3], ) self.assertEqual( util.get_config_option(config_options, "test_[str]", required_type="[str]"), ["a", "b", "c"], ) for bool_test in list(range(1, 4)): self.assertEqual( util.get_config_option( config_options, "test_bool{0}".format(bool_test), required_type="bool", ), True, ) self.assertEqual( util.get_config_option(config_options, "test_bool4", required_type="bool"), False, ) with self.assertRaises(TypeError): util.get_config_option(["not a dict"], "") with self.assertRaises(ValueError): util.get_config_option(config_options, "missing_value", required=True) with self.assertRaises(ValueError): util.get_config_option(config_options, "test_string", required_type="int") with self.assertRaises(ValueError): util.get_config_option(config_options, "test_string", required_type="float") with self.assertRaises(ValueError): util.get_config_option( config_options, "test_int", required_type="int", minimum=4 ) with self.assertRaises(ValueError): util.get_config_option( config_options, "test_int", required_type="int", maximum=2 ) with self.assertRaises(ValueError): util.get_config_option(config_options, "test_[str]", required_type="[int]") with self.assertRaises(ValueError): util.get_config_option( config_options, "test_[str]", required_type="[str]", allowed_values=["d"], ) with self.assertRaises(ValueError): util.get_config_option( config_options, "test_string", allowed_values=["other string", "other other string"], ) with self.assertRaises(ValueError): util.get_config_option( {"empty_string": ""}, "empty_string", required_type="str", allow_empty=False, ) def test_Format(self): self.assertEqual(util.format_datetime(None), "") self.assertEqual(util.format_datetime("a string"), "a string") self.assertEqual( util.format_datetime(datetime.datetime(2018, 5, 8, 13, 37, 0)), "2018-05-08 13:37:00", ) self.assertEqual( util.format_datetime(arrow.get("2020-03-13 13:37:00+00:00")), "2020-03-13 13:37:00+00:00", ) self.assertEqual( util.format_datetime(arrow.get("2020-04-11 13:37:00"), "Europe/London"), "2020-04-11 14:37:00+01:00", ) def test_bytes_to_size_string(self): s = util.bytes_to_size_string(10 * self.one_TB) self.assertEqual(s, "10.00TiB", "Failed to convert 10TiB to string") s = util.bytes_to_size_string(10 * self.one_GB) self.assertEqual(s, "10.00GiB", "Failed to convert 10GiB to string") s = util.bytes_to_size_string(10 * self.one_MB) self.assertEqual(s, "10.00MiB", "Failed to convert 10MiB to string") s = util.bytes_to_size_string(10 * self.one_KB) self.assertEqual(s, "10.00KiB", "Failed to convert 10KiB to string") s = util.bytes_to_size_string(1) self.assertEqual(s, "1", "Failed to convert 1B to string") def test_size_to_bytes(self): self.assertEqual(None, util.size_string_to_bytes(None)) size = util.size_string_to_bytes("10G") self.assertEqual(size, 10737418240, "Failed to convert 10G to bytes") size = util.size_string_to_bytes("10M") self.assertEqual(size, 10485760, "Failed to convert 10M to bytes") size = util.size_string_to_bytes("10K") self.assertEqual(size, 10240, "Failed to convert 10K to bytes") size = util.size_string_to_bytes("10") self.assertEqual(size, 10, "Failed to convert 10 to bytes") with self.assertRaises(ValueError): util.size_string_to_bytes("a") class TestUpDownTime(unittest.TestCase): def test_blankInit(self): u = util.UpDownTime() self.assertEqual(0, u.days, "days not defaulted to 0") self.assertEqual(0, u.hours, "hours not defaulted to 0") self.assertEqual(0, u.minutes, "minutes not defaulted to 0") self.assertEqual(0, u.seconds, "seconds not defaulted to 0") def test_givenInit(self): u = util.UpDownTime(1, 2, 3, 4) self.assertEqual(1, u.days, "days not inited to 1") self.assertEqual(2, u.hours, "hours not inited to 2") self.assertEqual(3, u.minutes, "minutes not inited to 3") self.assertEqual(4, u.seconds, "seconds not inited to 4") def test_initTimeDelta(self): diff = datetime.timedelta(1, 90) u = util.UpDownTime.from_timedelta(diff) self.assertEqual(1, u.days, "days not inited to 1") self.assertEqual(0, u.hours, "hours not inited to 0") self.assertEqual(1, u.minutes, "minutes not inited to 1") self.assertEqual(30, u.seconds, "seconds not inited to 30") def test_equal(self): u1 = util.UpDownTime(1, 2, 3, 4) u2 = util.UpDownTime(1, 2, 3, 4) self.assertEqual(u1, u2) def test_str(self): u1 = util.UpDownTime(1, 2, 3, 4) self.assertEqual(str(u1), "1+02:03:04") def test_bad_values(self): with self.assertRaises(TypeError): util.UpDownTime("a", 2, 3, 4) with self.assertRaises(TypeError): util.UpDownTime(1, "a", 3, 4) with self.assertRaises(TypeError): util.UpDownTime(1, 2, "a", 4) with self.assertRaises(TypeError): util.UpDownTime(1, 2, 3, "a") def test_bad_compare(self): u = util.UpDownTime(1, 2, 3, 4) self.assertFalse(u == "a") def test_false_compare(self): u1 = util.UpDownTime(1, 2, 3, 4) u2 = util.UpDownTime(1, 2, 3, 5) self.assertNotEqual(u1, u2) u2 = util.UpDownTime(1, 2, 4, 4) self.assertNotEqual(u1, u2) u2 = util.UpDownTime(1, 3, 3, 4) self.assertNotEqual(u1, u2) u2 = util.UpDownTime(2, 2, 3, 4) self.assertNotEqual(u1, u2) def test_minute(self): u = util.UpDownTime(0, 0, 1, 0) self.assertEqual(str(u), "0+00:01:00") def test_60_sec(self): u = util.UpDownTime(0, 0, 0, 60) self.assertEqual(str(u), "0+00:01:00") def test_values_overrun(self): u = util.UpDownTime(0, 23, 59, 60) self.assertEqual(str(u), "1+00:00:00") class TestGroupMatch(unittest.TestCase): def test_simple(self): self.assertTrue(util.check_group_match("default", ["default"])) def test_list(self): self.assertTrue(util.check_group_match("test", ["test", "test2"])) def test_not_list(self): self.assertFalse(util.check_group_match("default", ["test1", "test2"])) def test_all(self): self.assertTrue(util.check_group_match("test", ["_all"])) simplemonitor-1.13.0/tests/unittest.sh000077500000000000000000000001021464501162400200510ustar00rootroot00000000000000#!/usr/bin/env bash set -x python -m unittest discover -s tests simplemonitor-1.13.0/tox.ini000066400000000000000000000001771464501162400160200ustar00rootroot00000000000000[tox] envlist = py37,py38,py39,py310,py311 isolated_build = True [testenv] deps = pytest freezegun commands = pytest tests/ simplemonitor-1.13.0/winmonitor.py000066400000000000000000000000701464501162400172540ustar00rootroot00000000000000from simplemonitor import winmonitor winmonitor.main()