diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..54d0d69 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +log/ +.idea +.github +*.db +*.yml +*.md +*.sh +Makefile diff --git a/.github/workflows/bake_to_latest.yml b/.github/workflows/bake_to_latest.yml new file mode 100644 index 0000000..0d5c8c6 --- /dev/null +++ b/.github/workflows/bake_to_latest.yml @@ -0,0 +1,51 @@ +name: Build release-tags + +on: + workflow_dispatch: + push: + branches: + - master + +jobs: + bake-latest: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + with: + platforms: all + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1.6.0 + - name: Login to GitHub Container Registry + uses: docker/login-action@v1.10.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/docker-py3-kms/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 + push: true + tags: ghcr.io/py-kms-organization/py-kms:python3 + build-args: | + BUILD_COMMIT=${{ github.sha }} + BUILD_BRANCH=${{ github.ref_name }} + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/docker-py3-kms-minimal/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 + push: true + tags: ghcr.io/py-kms-organization/py-kms:latest,ghcr.io/py-kms-organization/py-kms:minimal + build-args: | + BUILD_COMMIT=${{ github.sha }} + BUILD_BRANCH=${{ github.ref_name }} diff --git a/.github/workflows/bake_to_next.yml b/.github/workflows/bake_to_next.yml new file mode 100644 index 0000000..5fc1b6c --- /dev/null +++ b/.github/workflows/bake_to_next.yml @@ -0,0 +1,51 @@ +name: Build next-tags + +on: + workflow_dispatch: + push: + branches: + - next + +jobs: + bake-next: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + with: + platforms: all + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1.6.0 + - name: Login to GitHub Container Registry + uses: docker/login-action@v1.10.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/docker-py3-kms/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 + push: true + tags: ghcr.io/py-kms-organization/py-kms:python3-next + build-args: | + BUILD_COMMIT=${{ github.sha }} + BUILD_BRANCH=${{ github.ref_name }} + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/docker-py3-kms-minimal/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 + push: true + tags: ghcr.io/py-kms-organization/py-kms:latest-next,ghcr.io/py-kms-organization/py-kms:minimal-next + build-args: | + BUILD_COMMIT=${{ github.sha }} + BUILD_BRANCH=${{ github.ref_name }} diff --git a/.github/workflows/bake_to_test.yml b/.github/workflows/bake_to_test.yml new file mode 100644 index 0000000..2790ba4 --- /dev/null +++ b/.github/workflows/bake_to_test.yml @@ -0,0 +1,38 @@ +name: Test-Build Docker Image + +on: + workflow_dispatch: + push: + +jobs: + bake-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + with: + platforms: all + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1.6.0 + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/docker-py3-kms/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 + push: false + build-args: | + BUILD_COMMIT=${{ github.sha }} + BUILD_BRANCH=${{ github.ref_name }} + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/docker-py3-kms-minimal/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 + push: false + build-args: | + BUILD_COMMIT=${{ github.sha }} + BUILD_BRANCH=${{ github.ref_name }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6d23810..6f4050f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ pykms_logserver.log* pykms_logclient.log* pykms_database.db* -etrigan.log* # Byte-compiled / optimized / DLL files __pycache__/ @@ -128,3 +127,8 @@ dmypy.json # Pyre type checker .pyre/ +# Helm +charts/*/*.tgz +/.idea/ +docker-compose-*.yml +*.sh diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..e962b25 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,12 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "3.10" + +python: + install: + - requirements: docs/requirements.txt + +formats: all diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c16912..47c1fac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,70 @@ # Changelog -### py-kms_2020-10-01 +## py-kms_2022-12-16 +- Added support for new web-gui into Docker +- Implemented whole-new web-based GUI with Flask +- Removed old GUI (Etrigan) from code and resources +- Removed sqliteweb +- Removed Etrigan (GUI) + +## py-kms_2022-12-07 +- Added warning about Etrigan (GUI) being deprecated +- More docs (do not run on same machine as client) +- Added Docker support for multiple listen IPs +- Added graceful Docker shutdowns + +## py-kms_2021-12-23 +- More Windows 10/11 keys +- Fixed some deprecation warnings +- Fixed SO_REUSEPORT platform checks +- Fixed loglevel "MININFO" with Docker +- Added Docker healthcheck +- Added UID/GID change support for Docker +- Dependabot alerts + +## py-kms_2021-10-22 +- Integrated Office 2021 GLVK keys & database +- Docker entrypoint fixes +- Updated docs to include SQLite stuff +- Fix for undefined timezones +- Removed LOGFILE extension checks +- Added support for Windows 11 + +## py-kms_2021-10-07 +- Helm charts for Kubernetes deployment +- Windows 2022 updates +- Faster Github Action builds + +## py-kms_2021-11-12 +- Addded GHCR support +- Docs table reformatted +- Updated GUI +- Windows Sandbox fix +- Added contribution guidelines +- Docker multiarch +- Reshot screenshots in docs + +## py-kms_2020-10-01 - Sql database path customizable. - Sql database file keeps different AppId. - Support for multi-address connection. - Added timeout send / receive. -### py-kms_2020-07-01 +## py-kms_2020-07-01 - py-kms Gui: now matches all cli options, added modes onlyserver / onlyclient, added some animations. - Added suboptions FILEOFF and STDOUTOFF of -F. - Created option for asynchronous messages. - Cleaned options parsing process. -### py-kms_2020-02-02 +## py-kms_2020-02-02 - Optimized pretty-print messages process. - Added -F FILESTDOUT option. - Added deamonization options (via [Etrigan](https://github.com/SystemRage/Etrigan) project). - py-kms GUI resurrected (and improved). - Cleaned, cleaned, cleaned. -### py-kms_2019-05-15 +## py-kms_2019-05-15 - Merging for Python2 / Python3 compatibility all-in-one. - Added new options: - timeout, [logsize](https://github.com/SystemRage/py-kms/pull/21). @@ -32,7 +76,7 @@ - Fixed activation threshold. - Renamed files, cosmetics and many other little big adjustments. -### py-kms_2018-11-15 +## py-kms_2018-11-15 - Implemented some good modifications inspired by [this](https://github.com/ThunderEX/py-kms) other fork. - Clean up code ( deleted no longer useful files randomHWID.py, randomEPID.py, timezones.py; erased useless functions and import modules ) @@ -41,128 +85,128 @@ - Improved random EPID generation. - Corrected [this](https://github.com/SystemRage/py-kms/issues/8) in kmsBase.py -### py-kms_2018-03-01 +## py-kms_2018-03-01 - *py-kms NOW is for Python3 too ( py3-kms ), the previous one ( written with Python2 ) is renamed py2-kms* - *Repaired logging messages* - *Added pretty processing messages* -### py-kms_2017-06-01 +## py-kms_2017-06-01 - *Added option verbose logging in a file* - *Updated "kmsBase.py" with new SKUIDs* - *Added a brief guide "py-kms-Guide.pdf" ( replaced "client-activation.txt" )* - *Added a well formatted and more complete list of volume keys "py-kms-ClientKeys.pdf" ( replaced "client-keys.txt" )* -### py-kms_2016-12-30 +## py-kms_2016-12-30 - *Updated kmsBase.py (Matches LicenseManager 4.6.0 by Hotbird64 HGM)* -### py-kms_2016-08-13 +## py-kms_2016-08-13 - *Fixed major bug on Response function* - *Fixed Random PID Generator (thanks: mkuba50)* -### py-kms_2016-08-12 +## py-kms_2016-08-12 - *Added missing UUID, credits: Hotbird64* - *Added Windows Server 2016 in random PID generator* -### py-kms_2016-08-11 +## py-kms_2016-08-11 - *Added Windows Server 2016 UUID* - *Fixed GroupID and PIDRange* - *Added Office 2016 CountKMSID* -### py-kms_2015-07-29 +## py-kms_2015-07-29 - *Added Windows 10 UUID* -### py-kms_2014-10-13 build 3: +## py-kms_2014-10-13 build 3: - *Added Client Activation Examples: "client-activation.txt"* - *Added Volume Keys: "client-keys.txt"* -### py-kms_2014-10-13 build 2: +## py-kms_2014-10-13 build 2: - *Added missing skuIds in file "kmsbase.py". Thanks (user_hidden)* -### py-kms_2014-10-13 build 1: +## py-kms_2014-10-13 build 1: - *The server now outputs the hwid in use.* - *The server hwid can be random by using parameter: "-w random". Example: "python server.py -w random"* - *Included file "randomHWID.py" to generate random hwid on demand.* - *Included file "randomPID.py" to generate random epid and hwid on demand.* -### py-kms_2014-03-21T232943Z: +## py-kms_2014-03-21T232943Z: - *The server HWID can now be specified on the command line.* - *The client will print the HWID when using the v6 protocol.* -### py-kms_2014-01-03T032458Z: +## py-kms_2014-01-03T032458Z: - *Made the sqlite3 module optional.* - *Changed the "log" flag to an "sqlite" flag and made a real log flag in preparation for when real request logging is implemented.* -### py-kms_2014-01-03T025524Z: +## py-kms_2014-01-03T025524Z: - *Added RPC response decoding to the KMS client emulator.* -### py-kms_2013-12-30T064443Z: +## py-kms_2013-12-30T064443Z: - *The v4 hash now uses the proper pre-expanded key.* -### py-kms_2013-12-28T073506Z: +## py-kms_2013-12-28T073506Z: - *Modified the v4 code to use the custom aes module in order to make it more streamlined and efficient.* -### py-kms_2013-12-20T051257Z: +## py-kms_2013-12-20T051257Z: - *Removed the need for the pre-computed table (tablecomplex.py) for v4 CMAC calculation, cutting the zip file size in half.* -### py-kms_2013-12-16T214638Z: +## py-kms_2013-12-16T214638Z: - *Switched to getting the to-be-logged request time from the KMS server instead of the client.* -### py-kms_2013-12-16T030001Z: +## py-kms_2013-12-16T030001Z: - *You can now specify the CMID and the Machine Name to use with the client emulator.* -### py-kms_2013-12-16T021215Z: +## py-kms_2013-12-16T021215Z: - *Added a request-logging feature to the server. It stores requests in an SQLite database and uses the ePIDs stored there on a per-CMID basis.* - *The client emulator now works for v4, v5, and v6 requests.* - *The client emulator now also verifies the KMS v4 responses it receives.* -### py-kms_2013-12-14T230215Z +## py-kms_2013-12-14T230215Z - *Added a client (work in progress) that right now can only generate and send RPC bind requests.* - *Added a bunch of new classes to handle RPC client stuff, but I might just end up moving their functions back into the old classes at some point.* - *Lots of other code shuffling.* - *Made the verbose and debug option help easier to read.* - *Added some server error messages.* -### py-kms_2013-12-08T051332Z: +## py-kms_2013-12-08T051332Z: - *Made some really huge internal changes to streamline packet parsing.* -### py-kms_2013-12-06T034100Z: +## py-kms_2013-12-06T034100Z: - *Added tons of new SKU IDs* -### py-kms_2013-12-05T044849Z: +## py-kms_2013-12-05T044849Z: - *Added Office SKU IDs* - *Small internal changes* -### py-kms_2013-12-04T010942Z: +## py-kms_2013-12-04T010942Z: - *Made the rpcResponseArray in rpcRequest output closer to spec* -### py-kms_2013-12-01T063938Z: +## py-kms_2013-12-01T063938Z: - *SKUID conversion: Converts the SKUID UUID into a human-readable product version for SKUIDs in its SKUID dictionary.* - *Fancy new timezone conversion stuff.* - *Enabled setting custom LCIDs.* - *Data parsing is now handled by structure.py.* - *Some other minor stuff you probably won't notice.* -### py-kms_2013-11-27T061658Z: +## py-kms_2013-11-27T061658Z: - *Got rid of custom functions module (finally)* -### py-kms_2013-11-27T054744Z: +## py-kms_2013-11-27T054744Z: - *Simplified custom functions module* - *Got rid of "v4" subfolder* - *Cleaned up a bunch of code* -### py-kms_2013-11-23T044244Z: +## py-kms_2013-11-23T044244Z: - *Added timestamps to verbose output* - *Made the verbose output look better* -### py-kms_2013-11-21T014002Z: +## py-kms_2013-11-21T014002Z: - *Moved some stuff into verbose output* - *Enabled server ePIDs of arbitrary length* -### py-kms_2013-11-20T180347Z: +## py-kms_2013-11-20T180347Z: - *Permanently fixed machineName decoding* - *Adheres closer to the DCE/RPC protocol spec* - *Added client info to program output* - *Small formatting changes* -### py-kms_2013-11-13: +## py-kms_2013-11-13: - *First working release added to the Mega folder.* diff --git a/LICENSE.gui.md b/LICENSE.gui.md deleted file mode 100644 index 1d06073..0000000 --- a/LICENSE.gui.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Matteo ℱan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 81d7160..987be20 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ # Readme -![repo-size](https://img.shields.io/github/repo-size/SystemRage/py-kms) -![open-issues](https://img.shields.io/github/issues/SystemRage/py-kms) -![last-commit](https://img.shields.io/github/last-commit/SystemRage/py-kms/master) -![docker-status](https://img.shields.io/docker/cloud/build/pykmsorg/py-kms) -![docker-pulls](https://img.shields.io/docker/pulls/pykmsorg/py-kms) +![repo-size](https://img.shields.io/github/repo-size/Py-KMS-Organization/py-kms) +![open-issues](https://img.shields.io/github/issues/Py-KMS-Organization/py-kms) +![last-commit](https://img.shields.io/github/last-commit/Py-KMS-Organization/py-kms/master) ![read-the-docs](https://img.shields.io/readthedocs/py-kms) *** +_Keep in mind that this project is not intended for production use. Feel free to use it to test your own systems or maybe even learn something from the protocol structure. :)_ + ## History _py-kms_ is a port of node-kms created by [cyrozap](http://forums.mydigitallife.info/members/183074-markedsword), which is a port of either the C#, C++, or .NET implementations of KMS Emulator. The original version was written by [CODYQX4](http://forums.mydigitallife.info/members/89933-CODYQX4) and is derived from the reverse-engineered code of Microsoft's official KMS. +This version of _py-kms_ is for itself a fork of the original implementation by [SystemRage](https://github.com/SystemRage/py-kms), which was abandoned early 2021. ## Features - Responds to `v4`, `v5`, and `v6` KMS requests. @@ -18,31 +19,33 @@ _py-kms_ is a port of node-kms created by [cyrozap](http://forums.mydigitallife. - Windows 8 - Windows 8.1 - Windows 10 ( 1511 / 1607 / 1703 / 1709 / 1803 / 1809 ) - - Windows 10 ( 1903 / 1909 / 20H1 ) + - Windows 10 ( 1903 / 1909 / 20H1, 20H2, 21H1, 21H2 ) + - Windows 11 ( 21H2 ) - Windows Server 2008 - Windows Server 2008 R2 - Windows Server 2012 - Windows Server 2012 R2 - Windows Server 2016 - Windows Server 2019 + - Windows Server 2022 + - Windows Server 2025 - Microsoft Office 2010 ( Volume License ) - Microsoft Office 2013 ( Volume License ) - Microsoft Office 2016 ( Volume License ) - Microsoft Office 2019 ( Volume License ) - - It's written in Python (tested with Python 3.6.9). - - Supports execution by `Docker`, `systemd`, `Upstart` and many more... - - Includes a GUI for simple managing. - - Uses `sqlite` for persistent data storage. + - Microsoft Office 2021 ( Volume License ) + - Microsoft Office 2024 ( Volume License ) + - It's written in Python (tested with Python 3.10.1). + - Supports execution by `Docker`, `systemd` and many more... + - Uses `sqlite` for persistent data storage (with a simple web-based explorer). ## Documentation -The wiki has been completly reworked and is now available on [readthedocs.com](https://py-kms.readthedocs.io/en/latest/). It should you provide all necessary information how to setup and to use _py-kms_ , all without clumping this readme. The documentation also houses more details about activation with _py-kms_ and how to get GVLK keys. +The wiki has been completly reworked and is now available on [readthedocs.io](https://py-kms.readthedocs.io/en/latest/). It should provide you all the necessary information about how to setup and to use _py-kms_ , all without clumping this readme. The documentation also houses more details about activation with _py-kms_ and how to get GVLK keys. ## Quick start -- To start the server, execute `python3 pykms_Server.py [IPADDRESS] [PORT]`, the default _IPADDRESS_ is `0.0.0.0` ( all interfaces ) and the default _PORT_ is `1688`. Note that both the address and port are optional. It's allowed to use IPv4 and IPv6 addresses. If you have a IPv6-capable dual-stack OS, a dual-stack socket is created when using a IPv6 address. -- To start the server automatically using Docker, execute `docker run -d --name py-kms --restart always -p 1688:1688 pykmsorg/py-kms`. +- To start the server, execute `python3 pykms_Server.py [IPADDRESS] [PORT]`, the default _IPADDRESS_ is `::` ( all interfaces ) and the default _PORT_ is `1688`. Note that both the address and port are optional. It's allowed to use IPv4 and IPv6 addresses. If you have a IPv6-capable dual-stack OS, a dual-stack socket is created when using a IPv6 address. **In case your OS does not support IPv6, make sure to explicitly specify the legacy IPv4 of `0.0.0.0`!** +- To start the server automatically using Docker, execute `docker run -d --name py-kms --restart always -p 1688:1688 ghcr.io/py-kms-organization/py-kms`. - To show the help pages type: `python3 pykms_Server.py -h` and `python3 pykms_Client.py -h`. -- For launching _py-kms_ GUI make the file `pykms_Server.py` executable with `chmod +x /path/to/folder/py-kms/pykms_Server.py`, then simply run `pykms_Server.py` by double-clicking. ## License - - _py-kms_ is [![Unlicense](https://img.shields.io/badge/license-unlicense-lightgray.svg)](https://github.com/SystemRage/py-kms/blob/master/LICENSE) - - _py-kms GUI_ is [![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/SystemRage/py-kms/blob/master/LICENSE.gui.md) © Matteo ℱan + - _py-kms_ is [![Unlicense](https://img.shields.io/badge/license-unlicense-lightgray.svg)](./LICENSE) diff --git a/charts/py-kms/.helmignore b/charts/py-kms/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/py-kms/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/py-kms/Chart.yaml b/charts/py-kms/Chart.yaml new file mode 100644 index 0000000..5ab290b --- /dev/null +++ b/charts/py-kms/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: py-kms +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "python3" diff --git a/charts/py-kms/README.md b/charts/py-kms/README.md new file mode 100644 index 0000000..6c676f4 --- /dev/null +++ b/charts/py-kms/README.md @@ -0,0 +1,60 @@ +# py-kms + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: python3](https://img.shields.io/badge/AppVersion-python3-informational?style=flat-square) + +A Helm chart for Kubernetes + +## Deployment + +Below is a basic overview of the steps required to deploy the Helm chart to an existing Kubernetes cluster which is accessible via Kubectl + +### Create py-kms Namespace (recommended) + +`kubectl create ns py-kms` + +### Deploy chart with default values.yaml + +`helm install -n py-kms -f myvalues.yaml charts/py-kms` + +For more information please refer to the Helm Install command documentation located at: https://helm.sh/docs/helm/helm_install/ + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | | +| autoscaling.enabled | bool | `false` | | +| autoscaling.maxReplicas | int | `100` | | +| autoscaling.minReplicas | int | `1` | | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | | +| fullnameOverride | string | `""` | | +| image.pullPolicy | string | `"IfNotPresent"` | | +| image.repository | string | `"ghcr.io/py-kms-organization/py-kms"` | | +| image.tag | string | `"python3"` | | +| imagePullSecrets | list | `[]` | | +| ingress.annotations | object | `{}` | | +| ingress.className | string | `""` | | +| ingress.enabled | bool | `false` | | +| ingress.hosts[0].host | string | `"chart-example.local"` | | +| ingress.hosts[0].paths[0].path | string | `"/"` | | +| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | | +| ingress.tls | list | `[]` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| podAnnotations | object | `{}` | | +| podSecurityContext | object | `{}` | | +| py-kms.environment.HWID | string | `"RANDOM"` | | +| py-kms.environment.IP | string | `"::"` | | +| py-kms.environment.LOGLEVEL | string | `"INFO"` | | +| py-kms.environment.LOGSIZE | int | `2` | | +| replicaCount | int | `1` | | +| resources | object | `{}` | | +| securityContext | object | `{}` | | +| service.httpPort | int | `80` | | +| service.kmsPort | int | `1688` | | +| service.type | string | `"ClusterIP"` | | +| serviceAccount | object | `{}` | | +| tolerations | list | `[]` | | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/charts/py-kms/templates/NOTES.txt b/charts/py-kms/templates/NOTES.txt new file mode 100644 index 0000000..7c70b5b --- /dev/null +++ b/charts/py-kms/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "py-kms.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "py-kms.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "py-kms.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "py-kms.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/charts/py-kms/templates/_helpers.tpl b/charts/py-kms/templates/_helpers.tpl new file mode 100644 index 0000000..803fd3a --- /dev/null +++ b/charts/py-kms/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "py-kms.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "py-kms.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "py-kms.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "py-kms.labels" -}} +helm.sh/chart: {{ include "py-kms.chart" . }} +{{ include "py-kms.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "py-kms.selectorLabels" -}} +app.kubernetes.io/name: {{ include "py-kms.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "py-kms.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "py-kms.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/py-kms/templates/deployment.yaml b/charts/py-kms/templates/deployment.yaml new file mode 100644 index 0000000..e0c150b --- /dev/null +++ b/charts/py-kms/templates/deployment.yaml @@ -0,0 +1,72 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "py-kms.fullname" . }} + labels: + {{- include "py-kms.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "py-kms.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "py-kms.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "py-kms.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + {{- range $key, $val := (index .Values "py-kms" "environment") }} + - name: {{ $key }} + value: {{ $val | quote }} + {{- end }} + ports: + - name: kms + containerPort: 1688 + protocol: TCP + - name: http + containerPort: 8080 + protocol: TCP + startupProbe: + httpGet: + port: http + path: /readyz + failureThreshold: 30 # 30 seconds seem to be enough under heavy workloads + periodSeconds: 1 + livenessProbe: + httpGet: + path: /livez + port: http + periodSeconds: 20 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/py-kms/templates/hpa.yaml b/charts/py-kms/templates/hpa.yaml new file mode 100644 index 0000000..db88558 --- /dev/null +++ b/charts/py-kms/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "py-kms.fullname" . }} + labels: + {{- include "py-kms.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "py-kms.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/py-kms/templates/ingress.yaml b/charts/py-kms/templates/ingress.yaml new file mode 100644 index 0000000..2270735 --- /dev/null +++ b/charts/py-kms/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "py-kms.fullname" . -}} +{{- $svcPort := .Values.service.httpPort -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "py-kms.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/py-kms/templates/service.yaml b/charts/py-kms/templates/service.yaml new file mode 100644 index 0000000..4a00fb6 --- /dev/null +++ b/charts/py-kms/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "py-kms.fullname" . }} + labels: + {{- include "py-kms.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.kmsPort }} + targetPort: 1688 + protocol: TCP + name: kms + - port: {{ .Values.service.httpPort }} + targetPort: 8080 + protocol: TCP + name: http + selector: + {{- include "py-kms.selectorLabels" . | nindent 4 }} diff --git a/charts/py-kms/templates/serviceaccount.yaml b/charts/py-kms/templates/serviceaccount.yaml new file mode 100644 index 0000000..5bcc80c --- /dev/null +++ b/charts/py-kms/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "py-kms.serviceAccountName" . }} + labels: + {{- include "py-kms.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/py-kms/templates/tests/test-connection.yaml b/charts/py-kms/templates/tests/test-connection.yaml new file mode 100644 index 0000000..01e7122 --- /dev/null +++ b/charts/py-kms/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "py-kms.fullname" . }}-test-connection" + labels: + {{- include "py-kms.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "py-kms.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/charts/py-kms/values.yaml b/charts/py-kms/values.yaml new file mode 100644 index 0000000..ae6728a --- /dev/null +++ b/charts/py-kms/values.yaml @@ -0,0 +1,91 @@ +# Default values for py-kms. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: ghcr.io/py-kms-organization/py-kms + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: python3 + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +py-kms: + environment: + LOGLEVEL: INFO + LOGSIZE: 2 + LOGFILE: /var/log/py-kms.log + HWID: RANDOM + IP: '::' + +serviceAccount: {} + # # Specifies whether a service account should be created + # create: true + # # Annotations to add to the service account + # annotations: {} + # # The name of the service account to use. + # # If not set and create is true, a name is generated using the fullname template + # name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + httpPort: 80 + kmsPort: 1688 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..56263c7 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,2 @@ +Both docker files must access the source code of this repository. Therefore the build context must be the root of the project directory. +Take a look into the build script for the normal py-kms version, as it demonstrates exactly that case and how to use these docker files. diff --git a/docker/docker-py3-kms-minimal/Dockerfile b/docker/docker-py3-kms-minimal/Dockerfile new file mode 100644 index 0000000..a2d0df9 --- /dev/null +++ b/docker/docker-py3-kms-minimal/Dockerfile @@ -0,0 +1,44 @@ +# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size +FROM alpine:3.15 + +ENV IP :: +ENV DUALSTACK 1 +ENV PORT 1688 +ENV EPID "" +ENV LCID 1033 +ENV CLIENT_COUNT 26 +ENV ACTIVATION_INTERVAL 120 +ENV RENEWAL_INTERVAL 10080 +ENV HWID RANDOM +ENV LOGLEVEL INFO +ENV LOGFILE STDOUT +ENV LOGSIZE "" +ENV WEBUI 0 + +COPY docker/docker-py3-kms-minimal/requirements.txt /home/py-kms/requirements.txt +RUN apk add --no-cache --update \ +bash \ + python3 \ + py3-pip \ + ca-certificates \ + shadow \ + tzdata \ + && pip3 install --no-cache-dir -r /home/py-kms/requirements.txt \ + && adduser -S py-kms -G users -s /bin/bash \ + && chown py-kms:users /home/py-kms \ + # Fix undefined timezone, in case the user did not mount the /etc/localtime + && ln -sf /usr/share/zoneinfo/UTC /etc/localtime + +COPY ./py-kms /home/py-kms +COPY docker/entrypoint.py /usr/bin/entrypoint.py +COPY docker/healthcheck.py /usr/bin/healthcheck.py +COPY docker/start.py /usr/bin/start.py +RUN chmod 555 /usr/bin/entrypoint.py /usr/bin/healthcheck.py /usr/bin/start.py + +WORKDIR /home/py-kms + +EXPOSE ${PORT}/tcp + +HEALTHCHECK --interval=5m --timeout=10s --start-period=10s --retries=3 CMD /usr/bin/python3 /usr/bin/healthcheck.py + +ENTRYPOINT ["/usr/bin/python3", "-u", "/usr/bin/entrypoint.py"] diff --git a/docker/docker-py3-kms-minimal/Dockerfile.amd64 b/docker/docker-py3-kms-minimal/Dockerfile.amd64 deleted file mode 100644 index afd3e42..0000000 --- a/docker/docker-py3-kms-minimal/Dockerfile.amd64 +++ /dev/null @@ -1,36 +0,0 @@ -# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size - -FROM alpine:3.12 - -ENV IP 0.0.0.0 -ENV PORT 1688 -ENV EPID "" -ENV LCID 1033 -ENV CLIENT_COUNT 26 -ENV ACTIVATION_INTERVAL 120 -ENV RENEWAL_INTERVAL 10080 -ENV HWID "RANDOM" -ENV LOGLEVEL INFO -ENV LOGFILE /var/log/pykms_logserver.log -ENV LOGSIZE "" - -RUN apk add --no-cache --update \ - bash \ - git \ - py3-argparse \ - py3-flask \ - py3-pygments \ - python3-tkinter \ - sqlite-libs \ - py3-pip && \ - pip3 install peewee tzlocal && \ - git clone https://github.com/SystemRage/py-kms/ /tmp/py-kms && \ - mv /tmp/py-kms/py-kms /home/ && \ - rm -rf /tmp/py-kms && \ - apk del git - -WORKDIR /home/py-kms - -EXPOSE ${PORT}/tcp - -ENTRYPOINT /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} diff --git a/docker/docker-py3-kms-minimal/Dockerfile.arm32v6 b/docker/docker-py3-kms-minimal/Dockerfile.arm32v6 deleted file mode 100644 index c555137..0000000 --- a/docker/docker-py3-kms-minimal/Dockerfile.arm32v6 +++ /dev/null @@ -1,44 +0,0 @@ -# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size - -# Prepare the multiarch env -FROM alpine AS builder -RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-arm.tar.gz" | tar zxvf - -C . --strip-components 1 - -# Switch to the target image -FROM arm32v6/alpine:3.12 - -# Import qemu from the preparation -COPY --from=builder qemu-arm-static /usr/bin - -ENV IP 0.0.0.0 -ENV PORT 1688 -ENV EPID "" -ENV LCID 1033 -ENV CLIENT_COUNT 26 -ENV ACTIVATION_INTERVAL 120 -ENV RENEWAL_INTERVAL 10080 -ENV HWID "RANDOM" -ENV LOGLEVEL INFO -ENV LOGFILE /var/log/pykms_logserver.log -ENV LOGSIZE "" - -RUN apk add --no-cache --update \ - bash \ - git \ - py3-argparse \ - py3-flask \ - py3-pygments \ - python3-tkinter \ - sqlite-libs \ - py3-pip && \ - pip3 install peewee tzlocal && \ - git clone https://github.com/SystemRage/py-kms/ /tmp/py-kms && \ - mv /tmp/py-kms/py-kms /home/ && \ - rm -rf /tmp/py-kms && \ - apk del git - -WORKDIR /home/py-kms - -EXPOSE ${PORT}/tcp - -ENTRYPOINT /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} diff --git a/docker/docker-py3-kms-minimal/Dockerfile.arm32v7 b/docker/docker-py3-kms-minimal/Dockerfile.arm32v7 deleted file mode 100644 index 08c1cc4..0000000 --- a/docker/docker-py3-kms-minimal/Dockerfile.arm32v7 +++ /dev/null @@ -1,44 +0,0 @@ -# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size - -# Prepare the multiarch env -FROM alpine AS builder -RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-arm.tar.gz" | tar zxvf - -C . --strip-components 1 - -# Switch to the target image -FROM arm32v7/alpine:3.12 - -# Import qemu from the preparation -COPY --from=builder qemu-arm-static /usr/bin - -ENV IP 0.0.0.0 -ENV PORT 1688 -ENV EPID "" -ENV LCID 1033 -ENV CLIENT_COUNT 26 -ENV ACTIVATION_INTERVAL 120 -ENV RENEWAL_INTERVAL 10080 -ENV HWID "RANDOM" -ENV LOGLEVEL INFO -ENV LOGFILE /var/log/pykms_logserver.log -ENV LOGSIZE "" - -RUN apk add --no-cache --update \ - bash \ - git \ - py3-argparse \ - py3-flask \ - py3-pygments \ - python3-tkinter \ - sqlite-libs \ - py3-pip && \ - pip3 install peewee tzlocal && \ - git clone https://github.com/SystemRage/py-kms/ /tmp/py-kms && \ - mv /tmp/py-kms/py-kms /home/ && \ - rm -rf /tmp/py-kms && \ - apk del git - -WORKDIR /home/py-kms - -EXPOSE ${PORT}/tcp - -ENTRYPOINT /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} diff --git a/docker/docker-py3-kms-minimal/Dockerfile.arm64v8 b/docker/docker-py3-kms-minimal/Dockerfile.arm64v8 deleted file mode 100644 index e3ebfb7..0000000 --- a/docker/docker-py3-kms-minimal/Dockerfile.arm64v8 +++ /dev/null @@ -1,44 +0,0 @@ -# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size - -# Prepare the multiarch env -FROM alpine AS builder -RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-aarch64.tar.gz" | tar zxvf - -C . --strip-components 1 - -# Switch to the target image -FROM arm64v8/alpine:3.12 - -# Import qemu from the preparation -COPY --from=builder qemu-aarch64-static /usr/bin - -ENV IP 0.0.0.0 -ENV PORT 1688 -ENV EPID "" -ENV LCID 1033 -ENV CLIENT_COUNT 26 -ENV ACTIVATION_INTERVAL 120 -ENV RENEWAL_INTERVAL 10080 -ENV HWID "RANDOM" -ENV LOGLEVEL INFO -ENV LOGFILE /var/log/pykms_logserver.log -ENV LOGSIZE "" - -RUN apk add --no-cache --update \ - bash \ - git \ - py3-argparse \ - py3-flask \ - py3-pygments \ - python3-tkinter \ - sqlite-libs \ - py3-pip && \ - pip3 install peewee tzlocal && \ - git clone https://github.com/SystemRage/py-kms/ /tmp/py-kms && \ - mv /tmp/py-kms/py-kms /home/ && \ - rm -rf /tmp/py-kms && \ - apk del git - -WORKDIR /home/py-kms - -EXPOSE ${PORT}/tcp - -ENTRYPOINT /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} diff --git a/docker/docker-py3-kms-minimal/hooks/post_push b/docker/docker-py3-kms-minimal/hooks/post_push deleted file mode 100644 index b1aa8e1..0000000 --- a/docker/docker-py3-kms-minimal/hooks/post_push +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# Use manifest-tool to create the manifest, given the experimental -# "docker manifest" command isn't available yet on Docker Hub. - -curl -Lo manifest-tool "https://github.com/estesp/manifest-tool/releases/download/v1.0.2/manifest-tool-linux-amd64" -chmod +x manifest-tool - -./manifest-tool push from-spec multi-arch-manifest-latest.yaml -./manifest-tool push from-spec multi-arch-manifest-minimal.yaml diff --git a/docker/docker-py3-kms-minimal/hooks/pre_build b/docker/docker-py3-kms-minimal/hooks/pre_build deleted file mode 100755 index 0a94f7e..0000000 --- a/docker/docker-py3-kms-minimal/hooks/pre_build +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Register qemu-*-static for all supported processors except the -# current one, but also remove all registered binfmt_misc before -docker run --rm --privileged multiarch/qemu-user-static:register --reset diff --git a/docker/docker-py3-kms-minimal/multi-arch-manifest-latest.yaml b/docker/docker-py3-kms-minimal/multi-arch-manifest-latest.yaml deleted file mode 100644 index 9747b43..0000000 --- a/docker/docker-py3-kms-minimal/multi-arch-manifest-latest.yaml +++ /dev/null @@ -1,21 +0,0 @@ -image: pykmsorg/py-kms:latest -manifests: - - image: pykmsorg/py-kms:minimal-amd64 - platform: - architecture: amd64 - os: linux - - image: pykmsorg/py-kms:minimal-arm32v6 - platform: - architecture: arm - os: linux - variant: v6 - - image: pykmsorg/py-kms:minimal-arm32v7 - platform: - architecture: arm - os: linux - variant: v7 - - image: pykmsorg/py-kms:minimal-arm64v8 - platform: - architecture: arm64 - os: linux - variant: v8 diff --git a/docker/docker-py3-kms-minimal/multi-arch-manifest-minimal.yaml b/docker/docker-py3-kms-minimal/multi-arch-manifest-minimal.yaml deleted file mode 100644 index a3daff2..0000000 --- a/docker/docker-py3-kms-minimal/multi-arch-manifest-minimal.yaml +++ /dev/null @@ -1,21 +0,0 @@ -image: pykmsorg/py-kms:minimal -manifests: - - image: pykmsorg/py-kms:minimal-amd64 - platform: - architecture: amd64 - os: linux - - image: pykmsorg/py-kms:minimal-arm32v6 - platform: - architecture: arm - os: linux - variant: v6 - - image: pykmsorg/py-kms:minimal-arm32v7 - platform: - architecture: arm - os: linux - variant: v7 - - image: pykmsorg/py-kms:minimal-arm64v8 - platform: - architecture: arm64 - os: linux - variant: v8 diff --git a/docker/docker-py3-kms-minimal/requirements.txt b/docker/docker-py3-kms-minimal/requirements.txt new file mode 100644 index 0000000..74edacf --- /dev/null +++ b/docker/docker-py3-kms-minimal/requirements.txt @@ -0,0 +1,2 @@ +dnspython==2.6.1 +tzlocal==4.2 \ No newline at end of file diff --git a/docker/docker-py3-kms/Dockerfile b/docker/docker-py3-kms/Dockerfile new file mode 100644 index 0000000..16b3ab5 --- /dev/null +++ b/docker/docker-py3-kms/Dockerfile @@ -0,0 +1,55 @@ +# Switch to the target image +FROM alpine:3.15 + +ARG BUILD_COMMIT=unknown +ARG BUILD_BRANCH=unknown + +ENV IP :: +ENV DUALSTACK 1 +ENV PORT 1688 +ENV EPID "" +ENV LCID 1033 +ENV CLIENT_COUNT 26 +ENV ACTIVATION_INTERVAL 120 +ENV RENEWAL_INTERVAL 10080 +ENV HWID RANDOM +ENV LOGLEVEL INFO +ENV LOGFILE STDOUT +ENV LOGSIZE "" +ENV TZ America/Chicago +ENV WEBUI 1 + +COPY docker/docker-py3-kms/requirements.txt /home/py-kms/ +RUN apk add --no-cache --update \ + bash \ + python3 \ + py3-pip \ + sqlite-libs \ + ca-certificates \ + tzdata \ + shadow \ + && pip3 install --no-cache-dir -r /home/py-kms/requirements.txt \ + && mkdir /db/ \ + && adduser -S py-kms -G users -s /bin/bash \ + && chown py-kms:users /home/py-kms \ + # Fix undefined timezone, in case the user did not mount the /etc/localtime + && ln -sf /usr/share/zoneinfo/UTC /etc/localtime + +COPY py-kms /home/py-kms/ +COPY docker/entrypoint.py /usr/bin/entrypoint.py +COPY docker/healthcheck.py /usr/bin/healthcheck.py +COPY docker/start.py /usr/bin/start.py +RUN chmod 555 /usr/bin/entrypoint.py /usr/bin/healthcheck.py /usr/bin/start.py + +# Web-interface specifics +COPY LICENSE /LICENSE +RUN echo "$BUILD_COMMIT" > /VERSION && echo "$BUILD_BRANCH" >> /VERSION + +WORKDIR /home/py-kms + +EXPOSE ${PORT}/tcp +EXPOSE 8080/tcp + +HEALTHCHECK --interval=5m --timeout=10s --start-period=10s --retries=3 CMD /usr/bin/python3 /usr/bin/healthcheck.py + +ENTRYPOINT [ "/usr/bin/python3", "-u", "/usr/bin/entrypoint.py" ] diff --git a/docker/docker-py3-kms/Dockerfile.amd64 b/docker/docker-py3-kms/Dockerfile.amd64 deleted file mode 100644 index 393962a..0000000 --- a/docker/docker-py3-kms/Dockerfile.amd64 +++ /dev/null @@ -1,41 +0,0 @@ -FROM alpine:3.12 - -ENV IP 0.0.0.0 -ENV PORT 1688 -ENV EPID "" -ENV LCID 1033 -ENV CLIENT_COUNT 26 -ENV ACTIVATION_INTERVAL 120 -ENV RENEWAL_INTERVAL 10080 -ENV SQLITE false -ENV HWID "364F463A8863D35F" -ENV LOGLEVEL ERROR -ENV LOGFILE /var/log/pykms_logserver.log -ENV LOGSIZE "" - -COPY start.sh /usr/bin/start.sh - -RUN apk add --no-cache --update \ - bash \ - git \ - py3-argparse \ - py3-flask \ - py3-pygments \ - python3-tkinter \ - sqlite-libs \ - py3-pip && \ - git clone https://github.com/SystemRage/py-kms.git /tmp/py-kms && \ - git clone https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web && \ - mv /tmp/py-kms/py-kms /home/ && \ - mv /tmp/sqlite_web/sqlite_web /home/ && \ - rm -rf /tmp/py-kms && \ - rm -rf /tmp/sqlite_web && \ - pip3 install peewee tzlocal pysqlite3 && \ - chmod a+x /usr/bin/start.sh && \ - apk del git - -WORKDIR /home/py-kms - -EXPOSE ${PORT}/tcp - -ENTRYPOINT ["/usr/bin/start.sh"] diff --git a/docker/docker-py3-kms/Dockerfile.arm32v6 b/docker/docker-py3-kms/Dockerfile.arm32v6 deleted file mode 100644 index 0ff4b31..0000000 --- a/docker/docker-py3-kms/Dockerfile.arm32v6 +++ /dev/null @@ -1,49 +0,0 @@ -# Prepare the multiarch env -FROM alpine AS builder -RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-arm.tar.gz" | tar zxvf - -C . --strip-components 1 - -# Switch to the target image -FROM arm32v6/alpine:3.12 - -# Import qemu from the preparation -COPY --from=builder qemu-arm-static /usr/bin - -ENV IP 0.0.0.0 -ENV PORT 1688 -ENV EPID "" -ENV LCID 1033 -ENV CLIENT_COUNT 26 -ENV ACTIVATION_INTERVAL 120 -ENV RENEWAL_INTERVAL 10080 -ENV SQLITE false -ENV HWID "364F463A8863D35F" -ENV LOGLEVEL ERROR -ENV LOGFILE /var/log/pykms_logserver.log -ENV LOGSIZE "" - -COPY start.sh /usr/bin/start.sh - -RUN apk add --no-cache --update \ - bash \ - git \ - py3-argparse \ - py3-flask \ - py3-pygments \ - python3-tkinter \ - sqlite-libs \ - py3-pip && \ - git clone https://github.com/SystemRage/py-kms.git /tmp/py-kms && \ - git clone https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web && \ - mv /tmp/py-kms/py-kms /home/ && \ - mv /tmp/sqlite_web/sqlite_web /home/ && \ - rm -rf /tmp/py-kms && \ - rm -rf /tmp/sqlite_web && \ - pip3 install peewee tzlocal pysqlite3 && \ - chmod a+x /usr/bin/start.sh && \ - apk del git - -WORKDIR /home/py-kms - -EXPOSE ${PORT}/tcp - -ENTRYPOINT ["/usr/bin/start.sh"] diff --git a/docker/docker-py3-kms/Dockerfile.arm32v7 b/docker/docker-py3-kms/Dockerfile.arm32v7 deleted file mode 100644 index b43baba..0000000 --- a/docker/docker-py3-kms/Dockerfile.arm32v7 +++ /dev/null @@ -1,49 +0,0 @@ -# Prepare the multiarch env -FROM alpine AS builder -RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-arm.tar.gz" | tar zxvf - -C . --strip-components 1 - -# Switch to the target image -FROM arm32v7/alpine:3.12 - -# Import qemu from the preparation -COPY --from=builder qemu-arm-static /usr/bin - -ENV IP 0.0.0.0 -ENV PORT 1688 -ENV EPID "" -ENV LCID 1033 -ENV CLIENT_COUNT 26 -ENV ACTIVATION_INTERVAL 120 -ENV RENEWAL_INTERVAL 10080 -ENV SQLITE false -ENV HWID "364F463A8863D35F" -ENV LOGLEVEL ERROR -ENV LOGFILE /var/log/pykms_logserver.log -ENV LOGSIZE "" - -COPY start.sh /usr/bin/start.sh - -RUN apk add --no-cache --update \ - bash \ - git \ - py3-argparse \ - py3-flask \ - py3-pygments \ - python3-tkinter \ - sqlite-libs \ - py3-pip && \ - git clone https://github.com/SystemRage/py-kms.git /tmp/py-kms && \ - git clone https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web && \ - mv /tmp/py-kms/py-kms /home/ && \ - mv /tmp/sqlite_web/sqlite_web /home/ && \ - rm -rf /tmp/py-kms && \ - rm -rf /tmp/sqlite_web && \ - pip3 install peewee tzlocal pysqlite3 && \ - chmod a+x /usr/bin/start.sh && \ - apk del git - -WORKDIR /home/py-kms - -EXPOSE ${PORT}/tcp - -ENTRYPOINT ["/usr/bin/start.sh"] diff --git a/docker/docker-py3-kms/Dockerfile.arm64v8 b/docker/docker-py3-kms/Dockerfile.arm64v8 deleted file mode 100644 index 31d1244..0000000 --- a/docker/docker-py3-kms/Dockerfile.arm64v8 +++ /dev/null @@ -1,49 +0,0 @@ -# Prepare the multiarch env -FROM alpine AS builder -RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-aarch64.tar.gz" | tar zxvf - -C . --strip-components 1 - -# Switch to the target image -FROM arm64v8/alpine:3.12 - -# Import qemu from the preparation -COPY --from=builder qemu-aarch64-static /usr/bin - -ENV IP 0.0.0.0 -ENV PORT 1688 -ENV EPID "" -ENV LCID 1033 -ENV CLIENT_COUNT 26 -ENV ACTIVATION_INTERVAL 120 -ENV RENEWAL_INTERVAL 10080 -ENV SQLITE false -ENV HWID "364F463A8863D35F" -ENV LOGLEVEL ERROR -ENV LOGFILE /var/log/pykms_logserver.log -ENV LOGSIZE "" - -COPY start.sh /usr/bin/start.sh - -RUN apk add --no-cache --update \ - bash \ - git \ - py3-argparse \ - py3-flask \ - py3-pygments \ - python3-tkinter \ - sqlite-libs \ - py3-pip && \ - git clone https://github.com/SystemRage/py-kms.git /tmp/py-kms && \ - git clone https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web && \ - mv /tmp/py-kms/py-kms /home/ && \ - mv /tmp/sqlite_web/sqlite_web /home/ && \ - rm -rf /tmp/py-kms && \ - rm -rf /tmp/sqlite_web && \ - pip3 install peewee tzlocal pysqlite3 && \ - chmod a+x /usr/bin/start.sh && \ - apk del git - -WORKDIR /home/py-kms - -EXPOSE ${PORT}/tcp - -ENTRYPOINT ["/usr/bin/start.sh"] diff --git a/docker/docker-py3-kms/build-py3-kms.sh b/docker/docker-py3-kms/build-py3-kms.sh deleted file mode 100755 index 616f1f5..0000000 --- a/docker/docker-py3-kms/build-py3-kms.sh +++ /dev/null @@ -1 +0,0 @@ -docker build -t pykms/pykms:py3-kms . --file Dockerfile.amd64 diff --git a/docker/docker-py3-kms/hooks/post_push b/docker/docker-py3-kms/hooks/post_push deleted file mode 100644 index 698e3ce..0000000 --- a/docker/docker-py3-kms/hooks/post_push +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -# Use manifest-tool to create the manifest, given the experimental -# "docker manifest" command isn't available yet on Docker Hub. - -curl -Lo manifest-tool "https://github.com/estesp/manifest-tool/releases/download/v1.0.2/manifest-tool-linux-amd64" -chmod +x manifest-tool - -./manifest-tool push from-spec multi-arch-manifest-python3.yaml diff --git a/docker/docker-py3-kms/hooks/pre_build b/docker/docker-py3-kms/hooks/pre_build deleted file mode 100755 index 0a94f7e..0000000 --- a/docker/docker-py3-kms/hooks/pre_build +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Register qemu-*-static for all supported processors except the -# current one, but also remove all registered binfmt_misc before -docker run --rm --privileged multiarch/qemu-user-static:register --reset diff --git a/docker/docker-py3-kms/multi-arch-manifest-python3.yaml b/docker/docker-py3-kms/multi-arch-manifest-python3.yaml deleted file mode 100644 index ede1020..0000000 --- a/docker/docker-py3-kms/multi-arch-manifest-python3.yaml +++ /dev/null @@ -1,21 +0,0 @@ -image: pykmsorg/py-kms:python3 -manifests: - - image: pykmsorg/py-kms:python3-amd64 - platform: - architecture: amd64 - os: linux - - image: pykmsorg/py-kms:python3-arm32v6 - platform: - architecture: arm - os: linux - variant: v6 - - image: pykmsorg/py-kms:python3-arm32v7 - platform: - architecture: arm - os: linux - variant: v7 - - image: pykmsorg/py-kms:python3-arm64v8 - platform: - architecture: arm64 - os: linux - variant: v8 diff --git a/docker/docker-py3-kms/requirements.txt b/docker/docker-py3-kms/requirements.txt new file mode 100644 index 0000000..3210657 --- /dev/null +++ b/docker/docker-py3-kms/requirements.txt @@ -0,0 +1,5 @@ +dnspython==2.6.1 +tzlocal==4.2 + +Flask==2.3.2 +gunicorn==22.0.0 \ No newline at end of file diff --git a/docker/docker-py3-kms/run-py3-kms.sh b/docker/docker-py3-kms/run-py3-kms.sh deleted file mode 100755 index a98e5d4..0000000 --- a/docker/docker-py3-kms/run-py3-kms.sh +++ /dev/null @@ -1,16 +0,0 @@ -docker stop py3-kms -docker rm py3-kms -docker run -d --name py3-kms \ - -t \ - -p 8080:8080 \ - -p 1688:1688 \ - -e IP=0.0.0.0 \ - -e PORT=1688 \ - -e SQLITE=true \ - -e HWID=RANDOM \ - -e LOGLEVEL=INFO \ - -e LOGFILE=/var/log/pykms_logserver.log \ - -e LOGSIZE=2 \ - -v /etc/localtime:/etc/localtime:ro \ - -v /var/log:/var/log:rw \ - --restart unless-stopped pykmsorg/py-kms:python3 diff --git a/docker/docker-py3-kms/start.sh b/docker/docker-py3-kms/start.sh deleted file mode 100644 index f6b1e01..0000000 --- a/docker/docker-py3-kms/start.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -cd /home/py-kms -if [ "$SQLITE" == false ]; -then - if [ "$EPID" == "" ]; - then - if [ "$LOGSIZE" == "" ]; - then - /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} - else - /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE} - fi - else - if [ "$LOGSIZE" == "" ]; - then - /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} - else - /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE} - fi - fi -else - if [ "$EPID" == "" ]; - then - if [ "$LOGSIZE" == "" ]; - then - /bin/bash -c "/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s ${PWD}/pykms_database.db -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} &" - sleep 5 - /usr/bin/python3 pykms_Client.py ${IP} ${PORT} -m Windows10 & - /usr/bin/python3 /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/pykms_database.db --read-only - else - /bin/bash -c "/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s ${PWD}/pykms_database.db -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE} &" - sleep 5 - /usr/bin/python3 pykms_Client.py ${IP} ${PORT} -m Windows10 & - /usr/bin/python3 /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/pykms_database.db --read-only - fi - else - if [ "$LOGSIZE" == "" ]; - then - /bin/bash -c "/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s ${PWD}/pykms_database.db -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} &" - sleep 5 - /usr/bin/python3 pykms_Client.py ${IP} ${PORT} -m Windows10 & - /usr/bin/python3 /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/pykms_database.db --read-only - else - /bin/sh -c "/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s ${PWD}/pykms_database.db -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE} &" - sleep 5 - /usr/bin/python3 pykms_Client.py ${IP} ${PORT} -m Windows10 & - /usr/bin/python3 /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/pykms_database.db --read-only - fi - fi -fi diff --git a/docker/entrypoint.py b/docker/entrypoint.py new file mode 100755 index 0000000..8035cd0 --- /dev/null +++ b/docker/entrypoint.py @@ -0,0 +1,81 @@ +#!/usr/bin/python3 -u + +# Need root privileges to change timezone, and user uid/gid, file/folder ownernship + +import grp +import logging +import os +import pwd +import subprocess +import sys +import signal +import time + +PYTHON3 = '/usr/bin/python3' +dbPath = os.path.join(os.sep, 'home', 'py-kms', 'db') # Do not include the database file name, as we must correct the folder permissions (the db file is recursively reachable) + +def change_uid_grp(logger): + if os.geteuid() != 0: + logger.info(f'not root user, cannot change uid/gid.') + return None + user_db_entries = pwd.getpwnam("py-kms") + user_grp_db_entries = grp.getgrnam("users") + uid = int(user_db_entries.pw_uid) + gid = int(user_grp_db_entries.gr_gid) + new_gid = int(os.getenv('GID', str(gid))) + new_uid = int(os.getenv('UID', str(uid))) + os.chown("/home/py-kms", new_uid, new_gid) + os.chown("/usr/bin/start.py", new_uid, new_gid) + if os.path.isdir(dbPath): + # Corret permissions recursively, as to access the database file, also its parent folder must be accessible + logger.debug(f'Correcting owner permissions on {dbPath}.') + os.chown(dbPath, new_uid, new_gid) + for root, dirs, files in os.walk(dbPath): + for dName in dirs: + dPath = os.path.join(root, dName) + logger.debug(f'Correcting owner permissions on {dPath}.') + os.chown(dPath, new_uid, new_gid) + for fName in files: + fPath = os.path.join(root, fName) + logger.debug(f'Correcting owner permissions on {fPath}.') + os.chown(fPath, new_uid, new_gid) + logger.debug(subprocess.check_output(['ls', '-la', dbPath]).decode()) + if 'LOGFILE' in os.environ and os.path.exists(os.environ['LOGFILE']): + # Oh, the user also wants a custom log file -> make sure start.py can access it by setting the correct permissions (777) + os.chmod(os.environ['LOGFILE'], 0o777) + logger.error(str(subprocess.check_output(['ls', '-la', os.environ['LOGFILE']]))) + logger.info("Setting gid to '%s'." % str(new_gid)) + os.setgid(new_gid) + + logger.info("Setting uid to '%s'." % str(new_uid)) + os.setuid(new_uid) + +def change_tz(logger): + tz = os.getenv('TZ', 'etc/UTC') + # TZ is not symlinked and defined TZ exists + if tz not in os.readlink('/etc/localtime') and os.path.isfile('/usr/share/zoneinfo/' + tz) and hasattr(time, 'tzset'): + logger.info("Setting timzeone to %s" % tz ) + # time.tzet() should be called on Unix, but doesn't exist on Windows. + time.tzset() + +if __name__ == "__main__": + log_level_bootstrap = log_level = os.getenv('LOGLEVEL', 'INFO') + if log_level_bootstrap == "MININFO": + log_level_bootstrap = "INFO" + loggersrv = logging.getLogger('entrypoint.py') + loggersrv.setLevel(log_level_bootstrap) + streamhandler = logging.StreamHandler(sys.stdout) + streamhandler.setLevel(log_level_bootstrap) + formatter = logging.Formatter(fmt = '\x1b[94m%(asctime)s %(levelname)-8s %(message)s', datefmt = '%a, %d %b %Y %H:%M:%S',) + streamhandler.setFormatter(formatter) + loggersrv.addHandler(streamhandler) + loggersrv.info("Log level: %s" % log_level) + loggersrv.debug("user id: %s" % os.getuid()) + + change_tz(loggersrv) + childProcess = subprocess.Popen(PYTHON3 + " -u /usr/bin/start.py", preexec_fn=change_uid_grp(loggersrv), shell=True) + def shutdown(signum, frame): + loggersrv.info("Received signal %s, shutting down..." % signum) + childProcess.terminate() # This will also cause communicate() from below to continue + signal.signal(signal.SIGTERM, shutdown) # This signal will be sent by Docker to request shutdown + childProcess.communicate() diff --git a/docker/healthcheck.py b/docker/healthcheck.py new file mode 100755 index 0000000..0ad45ca --- /dev/null +++ b/docker/healthcheck.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 -u +import os +import sys +import logging + +def do_check(logger): + import socket + listen_ip = os.environ.get('IP', '::').split() + listen_ip.insert(0, '127.0.0.1') # always try to connect to localhost first + listen_port = os.environ.get('PORT', '1688') + for ip in listen_ip: + try: + s = socket.socket(socket.AF_INET6 if ':' in ip else socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) # 1 second timeout + address = ip if ':' in ip else (ip, int(listen_port)) + logger.debug(f"Trying to connect to {address}...") + s.connect(address) + s.close() + return True + except: + pass + return False # no connection could be established + + +if __name__ == '__main__': + log_level_bootstrap = log_level = os.getenv('LOGLEVEL', 'INFO') + if log_level_bootstrap == "MININFO": + log_level_bootstrap = "INFO" + loggersrv = logging.getLogger('healthcheck.py') + loggersrv.setLevel(log_level_bootstrap) + streamhandler = logging.StreamHandler(sys.stdout) + streamhandler.setLevel(log_level_bootstrap) + formatter = logging.Formatter(fmt='\x1b[94m%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S') + streamhandler.setFormatter(formatter) + loggersrv.addHandler(streamhandler) + + sys.exit(0 if do_check(loggersrv) else 1) diff --git a/docker/start.py b/docker/start.py new file mode 100755 index 0000000..5b192fe --- /dev/null +++ b/docker/start.py @@ -0,0 +1,95 @@ +#!/usr/bin/python3 -u + +# This replaces the old start.sh and ensures all arguments are bound correctly from the environment variables... +import logging +import os +import subprocess +import sys +import time + +PYTHON3 = '/usr/bin/python3' +argumentVariableMapping = { + '-l': 'LCID', + '-c': 'CLIENT_COUNT', + '-a': 'ACTIVATION_INTERVAL', + '-r': 'RENEWAL_INTERVAL', + '-w': 'HWID', + '-V': 'LOGLEVEL', + '-F': 'LOGFILE', + '-S': 'LOGSIZE', + '-e': 'EPID' +} + +db_path = os.path.join(os.sep, 'home', 'py-kms', 'db', 'pykms_database.db') +log_file = os.environ.get('LOGFILE', 'STDOUT') +listen_ip = os.environ.get('IP', '::').split() +listen_port = os.environ.get('PORT', '1688') +want_webui = os.environ.get('WEBUI', '0') == '1' # if the variable is not provided, we assume the user does not want the webui + +def start_kms(logger): + # Make sure the full path to the db exists + if want_webui and not os.path.exists(os.path.dirname(db_path)): + os.makedirs(os.path.dirname(db_path), exist_ok=True) + + # Build the command to execute + command = [PYTHON3, '-u', 'pykms_Server.py', listen_ip[0], listen_port] + for (arg, env) in argumentVariableMapping.items(): + if env in os.environ and os.environ.get(env) != '': + command.append(arg) + command.append(os.environ.get(env)) + if want_webui: # add this command directly before the "connect" subparser - otherwise you'll get silent crashes! + command.append('-s') + command.append(db_path) + if len(listen_ip) > 1: + command.append("connect") + for i in range(1, len(listen_ip)): + command.append("-n") + command.append(listen_ip[i] + "," + listen_port) + if dual := os.environ.get('DUALSTACK'): + command.append("-d") + command.append(dual) + + logger.debug("server_cmd: %s" % (" ".join(str(x) for x in command).strip())) + pykms_process = subprocess.Popen(command) + pykms_webui_process = None + + try: + if want_webui: + time.sleep(2) # Wait for the server to start up + pykms_webui_env = os.environ.copy() + pykms_webui_env['PYKMS_SQLITE_DB_PATH'] = db_path + pykms_webui_env['PORT'] = '8080' + pykms_webui_env['PYKMS_LICENSE_PATH'] = '/LICENSE' + pykms_webui_env['PYKMS_VERSION_PATH'] = '/VERSION' + pykms_webui_process = subprocess.Popen(['gunicorn', '--log-level', os.environ.get('LOGLEVEL'), 'pykms_WebUI:app'], env=pykms_webui_env) + except Exception as e: + logger.error("Failed to start webui (ignoring and continuing anyways): %s" % e) + + try: + pykms_process.wait() + except Exception: + # In case of any error - just shut down + pass + except KeyboardInterrupt: + pass + + if pykms_webui_process: + pykms_webui_process.terminate() + pykms_process.terminate() + + +# Main +if __name__ == "__main__": + log_level_bootstrap = log_level = os.environ.get('LOGLEVEL', 'INFO') + if log_level_bootstrap == "MININFO": + log_level_bootstrap = "INFO" + loggersrv = logging.getLogger('start.py') + loggersrv.setLevel(log_level_bootstrap) + streamhandler = logging.StreamHandler(sys.stdout) + streamhandler.setLevel(log_level_bootstrap) + formatter = logging.Formatter(fmt='\x1b[94m%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S') + streamhandler.setFormatter(formatter) + loggersrv.addHandler(streamhandler) + loggersrv.debug("user id: %s" % os.getuid()) + + start_kms(loggersrv) diff --git a/docs/Getting Started.md b/docs/Getting Started.md index cc80a16..cb1e43e 100644 --- a/docs/Getting Started.md +++ b/docs/Getting Started.md @@ -2,29 +2,26 @@ What follows are some guides how to start the `pykms_Server.py` script, which provides the emulated server. ## Running as a service -*** You can simply manage a daemon that runs as a background process. This can be achieved by using any of the notes below or by writing your own solution. ### Docker -![docker-auto](https://img.shields.io/docker/cloud/automated/pykmsorg/py-kms) -![docker-status](https://img.shields.io/docker/cloud/build/pykmsorg/py-kms) -![docker-pulls](https://img.shields.io/docker/pulls/pykmsorg/py-kms) -![docker-size](https://img.shields.io/docker/image-size/pykmsorg/py-kms) - If you wish to get _py-kms_ just up and running without installing any dependencies or writing own scripts: Just use Docker ! Docker also solves problems regarding the explicit IPv4 and IPv6 usage (it just supports both). The following command will download, "install" and start _py-kms_ and also keep it alive after any service disruption. ```bash -docker run -d --name py-kms --restart always -p 1688:1688 pykmsorg/py-kms +docker run -d --name py-kms --restart always -p 1688:1688 -v /etc/localtime:/etc/localtime:ro ghcr.io/py-kms-organization/py-kms ``` -If you just want to use the image and don't want to build them yourself, you can always use the official image at the [Docker Hub](https://hub.docker.com/r/pykmsorg/py-kms) (`pykmsorg/py-kms`). To ensure that you are using always the -latest version you should check something like [watchtower](https://github.com/containrrr/watchtower) out ! +If you just want to use the image and don't want to build them yourself, you can always use the official image at the [GitHub Container Registry](https://github.com/Py-KMS-Organization/py-kms/pkgs/container/py-kms) (`ghcr.io/py-kms-organization/py-kms`). To ensure that you are using always the latest version you should check something like [watchtower](https://github.com/containrrr/watchtower) out! #### Tags There are currently three tags of the image available (select one just by appending `:` to the image from above): * `latest`, currently the same like `minimal`. * `minimal`, which is based on the python3 minimal configuration of py-kms. _This tag does NOT include `sqlite` support !_ -* `python3`, which is fully configurable and equipped with `sqlite` support and a web interface (make sure to expose port 8080) for management. +* `python3`, which is fully configurable and equipped with `sqlite` support and a web-interface (make sure to expose port `8080`) for management. + +Wait... Web-interface? Yes! `py-kms` now comes with a simple web-ui to let you browse the known clients or its supported products. In case you wonder, here is a screenshot of the web-ui (*note that this screenshot may not reflect the current state of the ui*): + +![web-ui](img/webinterface.png) #### Architectures There are currently the following architectures available (if you need an other, feel free to open an issue): @@ -37,45 +34,36 @@ _Please note that any architecture other than the classic `amd64` is slightly bi #### Docker Compose You can use `docker-compose` instead of building and running the Dockerfile, so you do not need to respecify your settings again and again. The following Docker Compose file will deploy the `latest` image with the log into your local directory. +Make sure to take a look into the `entrypoint.py` script to see all supported variable mappings! ```yaml version: '3' services: kms: - image: pykmsorg/py-kms:latest + image: ghcr.io/py-kms-organization/py-kms:python3 ports: - - 1688:1688 + - 1688:1688 # kms + - 8080:8080 # web-interface environment: - - IP=0.0.0.0 - - SQLITE=true - - HWID=RANDOM - - LOGLEVEL=INFO - - LOGSIZE=2 - - LOGFILE=/var/log/pykms_logserver.log + IP: "::" + HWID: RANDOM + LOGLEVEL: INFO restart: always volumes: + - ./db:/home/py-kms/db - /etc/localtime:/etc/localtime:ro - - ./:/var/log:rw ``` #### Parameters -Below is a fully expanded run command, detailing all the different supported environment variables to set. For further reference see the [start parameters](Usage.html#docker-environment) for the docker environment. +Below is a little bit more extended run command, detailing all the different supported environment variables to set. For further reference see the [start parameters](#docker-environment) for the docker environment. ```bash docker run -it -d --name py3-kms \ -p 8080:8080 \ -p 1688:1688 \ - -e IP=0.0.0.0 \ - -e PORT=1688 \ - -e SQLITE=true \ - -e HWID=RANDOM \ - -e LOGLEVEL=INFO \ - -e LOGSIZE=2 \ - -e LOGFILE=/var/log/pykms_logserver.log \ -v /etc/localtime:/etc/localtime:ro \ - -v /var/log:/var/log:rw \ - --restart unless-stopped pykmsorg/py-kms:[TAG] + --restart unless-stopped ghcr.io/py-kms-organization/py-kms:[TAG] ``` -You can omit the `-e SQLITE=...` and `-p 8080:8080` option if you plan to use the `minimal` or `latest` image, which does not include the respective module support. +You can omit the `-p 8080:8080` option if you plan to use the `minimal` or `latest` image, which does not include the `sqlite` module support. ### Systemd If you are running a Linux distro using `systemd`, create the file: `sudo nano /etc/systemd/system/py3-kms.service`, then add the following (change it where needed) and save: @@ -91,7 +79,7 @@ Restart=always RestartSec=1 KillMode=process User=root -ExecStart=/usr/bin/python3 /py-kms/pykms_Server.py 0.0.0.0 1688 -V DEBUG -F /pykms_logserver.log +ExecStart=/usr/bin/python3 /py-kms/pykms_Server.py :: 1688 -V DEBUG -F /pykms_logserver.log [Install] WantedBy=multi-user.target @@ -100,10 +88,6 @@ Check syntax with `sudo systemd-analyze verify py3-kms.service`, correct file pe start the daemon `sudo systemctl start py3-kms.service` and view its status `sudo systemctl status py3-kms.service`. Check if daemon is correctly running with `cat /pykms_logserver.log`. Finally a few generic commands useful for interact with your daemon [here](https://linoxide.com/linux-how-to/enable-disable-services-ubuntu-systemd-upstart/). -### Etrigan -You can run py-kms daemonized (via [Etrigan](https://github.com/SystemRage/Etrigan)) using a command like `python3 pykms_Server.py etrigan start` and stop it with `python3 pykms_Server.py etrigan stop`. With Etrigan you have another -way to launch py-kms GUI (specially suitable if you're using a virtualenv), so `python3 pykms_Server.py etrigan start -g` and stop the GUI with `python3 pykms_Server.py etrigan stop` (or interact with the `EXIT` button). - ### Upstart (deprecated) If you are running a Linux distro using `upstart` (deprecated), create the file: `sudo nano /etc/init/py3-kms.conf`, then add the following (change it where needed) and save: ``` @@ -114,7 +98,7 @@ env PYKMSPATH=/py-kms env LOGPATH=/pykms_logserver.log start on runlevel [2345] stop on runlevel [016] -exec $PYTHONPATH/python3 $PYKMSPATH/pykms_Server.py 0.0.0.0 1688 -V DEBUG -F $LOGPATH +exec $PYTHONPATH/python3 $PYKMSPATH/pykms_Server.py :: 1688 -V DEBUG -F $LOGPATH respawn ``` Check syntax with `sudo init-checkconf -d /etc/init/py3-kms.conf`, then reload upstart to recognise this process `sudo initctl reload-configuration`. Now start the service `sudo start py3-kms`, and you can see the logfile @@ -134,7 +118,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework): _svc_name_ = "py-kms" _svc_display_name_ = "py-kms" _proc = None - _cmd = ["C:\Windows\Python27\python.exe", "C:\Windows\Python27\py-kms\pykms_Server.py"] + _cmd = ["C:\Windows\Python27\python.exe", "C:\Windows\Python27\py-kms\pykms_Server.py"] # UPDATE THIS - because Python 2.7 is end of life and you will use other parameters anyway def __init__(self,args): win32serviceutil.ServiceFramework.__init__(self,args) @@ -171,17 +155,15 @@ They might be useful to you: - [FreeBSD](https://github.com/SystemRage/py-kms/issues/89) ## Manual Execution -*** ### Dependencies - Python 3.x. -- Tkinter module (for the GUI). - If the `tzlocal` module is installed, the "Request Time" in the verbose output will be converted into local time. Otherwise, it will be in UTC. - It can use the `sqlite3` module, storing activation data in a database so it can be recalled again. -- Installation example on Ubuntu / Mint: +- Installation example on Ubuntu / Mint (`requirements.txt` is from the sources): - `sudo apt-get update` - - `sudo apt-get install python3-tk python3-pip` - - `sudo pip3 install tzlocal pysqlite3` + - `sudo apt-get install python3-pip` + - `pip3 install -r requirements.txt` (on Ubuntu Server 22, you'll need `pysqlite3-binary` - see [this issue](https://github.com/Py-KMS-Organization/py-kms/issues/76)) ### Startup A Linux user with `ip addr` command can get his KMS IP (Windows users can try `ipconfig /all`). @@ -211,6 +193,9 @@ user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 1688 To stop `pykms_Server.py`, in the same bash window where code running, simply press `CTRL+C`. Alternatively, in a new bash window, use `kill ` command (you can type `ps aux` first and have the process ) or `killall `. +### Web-Interface +As you may have noticed, the Docker container contains a web-interface, replacing the old GUI. If you want to launch it manually, checkout this [issue discussion](https://github.com/Py-KMS-Organization/py-kms/issues/100#issuecomment-1710827824) to learn more. + ### Quick Guide The following are just some brief notes about parameters handling. For a more detailed description see [here](Usage.md). diff --git a/docs/Keys.md b/docs/Keys.md index fa9a258..1795fe3 100644 --- a/docs/Keys.md +++ b/docs/Keys.md @@ -3,387 +3,411 @@ These are keys, which can be used to activate a product with _py-kms_ (note this sometimes even reject it by itself (often due too many uses - in that case try to use an other one). ## Windows -*** + +### Windows Server 2022 + +| Product | GVLK | +| -------------------------------------------- | ------------------------------- | +| Windows Server 2022 Datacenter | `WX4NM-KYWYW-QJJR4-XV3QB-6VM33` | +| Windows Server 2022 Standard | `VDYBN-27WPP-V4HQT-9VMD4-VMK7H` | +| Windows Server 2022 Datacenter Azure Edition | `NTBV8-9K7Q8-V27C6-M2BTV-KHMXV` | ### Windows Server 2019 -| Product | GVLK | -| --- | --- | -| Windows Server 2019 Datacenter | `WMDGN-G9PQG-XVVXX-R3X43-63DFG` | -| Windows Server 2019 Standard | `N69G4-B89J2-4G8F4-WWYCC-J464C` | -| Windows Server 2019 Essentials | `WVDHN-86M7X-466P6-VHXV7-YY726` | -| Windows Server 2019 Azure Core | `FDNH6-VW9RW-BXPJ7-4XTYG-239TB` | +| Product | GVLK | +| ----------------------------------------------------------- | ------------------------------- | +| Windows Server 2019 Datacenter | `WMDGN-G9PQG-XVVXX-R3X43-63DFG` | +| Windows Server 2019 Standard | `N69G4-B89J2-4G8F4-WWYCC-J464C` | +| Windows Server 2019 Essentials | `WVDHN-86M7X-466P6-VHXV7-YY726` | +| Windows Server 2019 Azure Core | `FDNH6-VW9RW-BXPJ7-4XTYG-239TB` | | Windows Server 2019 Datacenter Semi-Annual Channel (v.1809) | `6NMRW-2C8FM-D24W7-TQWMY-CWH2D` | -| Windows Server 2019 Standard Semi-Annual Channel (v.1809) | `N2KJX-J94YW-TQVFB-DG9YT-724CC` | -| Windows Server 2019 ARM64 | `GRFBW-QNDC4-6QBHG-CCK3B-2PR88` | +| Windows Server 2019 Standard Semi-Annual Channel (v.1809) | `N2KJX-J94YW-TQVFB-DG9YT-724CC` | +| Windows Server 2019 ARM64 | `GRFBW-QNDC4-6QBHG-CCK3B-2PR88` | ### Windows Server 2016 -| Product | GVLK | -| --- | --- | -| Windows Server 2016 Standard Semi-Annual Channel (v.1803) | `PTXN8-JFHJM-4WC78-MPCBR-9W4KR` | -| Windows Server 2016 Datacenter Semi-Annual Channel (v.1803) | `2HXDN-KRXHB-GPYC7-YCKFJ-7FVDG` | -| Windows Server 2016 Datacenter Semi-Annual Channel (v.1709) | `6Y6KB-N82V8-D8CQV-23MJW-BWTG6` | -| Windows Server 2016 Standard Semi-Annual Channel (v.1709) | `DPCNP-XQFKJ-BJF7R-FRC8D-GF6G4` | -| Windows Server 2016 ARM64 | `K9FYF-G6NCK-73M32-XMVPY-F9DRR` | -| Windows Server 2016 Datacenter | `CB7KF-BWN84-R7R2Y-793K2-8XDDG` | -| Windows Server 2016 Standard | `WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY` | -| Windows Server 2016 Essentials | `JCKRF-N37P4-C2D82-9YXRT-4M63B` | -| Windows Server 2016 Cloud Storage | `QN4C6-GBJD2-FB422-GHWJK-GJG2R` | -| Windows Server 2016 Azure Core | `VP34G-4NPPG-79JTQ-864T4-R3MQX`
`WNCYY-GFBH2-M4WTT-XQ2FP-PG2K9` | - -### Windows 10 - -| Product | GVLK | -| --- | --- | -| Windows 10 Professional Workstation | `NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J` | -| Windows 10 Professional Workstation N | `9FNHH-K3HBT-3W4TD-6383H-6XYWF` | -| Windows 10 Enterprise G | `YYVX9-NTFWV-6MDM3-9PT4T-4M68B` | -| Windows 10 Enterprise G N | `44RPN-FTY23-9VTTB-MP9BX-T84FV` | -| Windows 10 Enterprise LTSC 2019 | `M7XTQ-FN8P6-TTKYV-9D4CC-J462D` | -| Windows 10 Enterprise LTSC 2019 N | `92NFX-8DJQP-P6BBQ-THF9C-7CG2H` | -| Windows 10 Remote Server | `7NBT4-WGBQX-MP4H7-QXFF8-YP3KX` | -| Windows 10 Enterprise for Remote Sessions | `CPWHC-NT2C7-VYW78-DHDB2-PG3GK` | -| Windows 10 S (Lean) | `NBTWJ-3DR69-3C4V8-C26MC-GQ9M6` | -| Windows 10 Professional | `W269N-WFGWX-YVC9B-4J6C9-T83GX` | -| Windows 10 Professional N | `MH37W-N47XK-V7XM9-C7227-GCQG9`
`HMNWJ-V69R6-B2CDC-8P7VT-2373K` | -| Windows 10 Professional Education | `6TP4R-GNPTD-KYYHQ-7B7DP-J447Y` | -| Windows 10 Professional Education N | `YVWGF-BXNMC-HTQYQ-CPQ99-66QFC` | -| Windows 10 Education | `NW6C2-QMPVW-D7KKK-3GKT6-VCFB2`
`F48BJ-8NX82-MRVY9-PF8BW-HMHY2` | -| Windows 10 Education N | `2WH4N-8QGBV-H22JP-CT43Q-MDWWJ`
`PPWGW-8NW9C-J77Q9-8WHB9-QV64W` | -| Windows 10 Enterprise | `NPPR9-FWDCX-D2C8J-H872K-2YT43`
`96YNV-9X4RP-2YYKB-RMQH4-6Q72D`
`TN6CM-KCVXP-VVP8X-YVCF7-R9BDH`
`3PMKQ-YNVGT-HFJGG-2F4FQ-9D6T7` | -| Windows 10 Enterprise N | `DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4`
`WGGHN-J84D6-QYCPR-T7PJ7-X766F` | -| Windows 10 Enterprise S | `H76BG-QBNM7-73XY9-V6W2T-684BJ` | -| Windows 10 Enterprise S N | `X4R4B-NV6WD-PKTVK-F98BH-4C2J8` | -| Windows 10 Enterprise 2015 LTSB | `WNMTR-4C88C-JK8YV-HQ7T2-76DF9` | -| Windows 10 Enterprise 2015 LTSB N | `2F77B-TNFGY-69QQF-B8YKP-D69TJ`
`RW7WN-FMT44-KRGBK-G44WK-QV7YK` | -| Windows 10 Enterprise 2016 LTSB | `DCPHK-NFMTC-H88MJ-PFHPY-QJ4BJ` | -| Windows 10 Enterprise 2016 LTSB N | `QFFDN-GRT3P-VKWWX-X7T3R-8B639` | -| Windows 10 Home
Windows 10 Core | `TX9XD-98N7V-6WMQ6-BX7FG-H8Q99`
`33QT6-RCNYF-DXB4F-DGP7B-7MHX9` | -| Windows 10 Home N
Windows 10 Core N | `3KHY7-WNT83-DGQKR-F7HPR-844BM`
`CP4KF-NG6TC-9K6QF-P6GTT-H8RBM` | -| Windows 10 Home Single Language
Windows 10 Core Single Language | `7HNRX-D7KGG-3K4RQ-4WPJ4-YTDFH`
`9HGRW-NH2CQ-XQHJD-YCRWB-6VJV7`
`4NX46-6DHCG-MR3PH-9FMCX-3RQ3G` | -| Windows 10 Home Country Specific
Windows 10 Core Country Specific | `PVMJN-6DFY6-9CCP6-7BKTT-D3WVR`
`JN9HR-MH7K4-DBPDD-TFTXF-Q9MMF` | +| Product | GVLK | +| ----------------------------------------------------------- | ------------------------------------------------------------------ | +| Windows Server 2016 Standard Semi-Annual Channel (v.1803) | `PTXN8-JFHJM-4WC78-MPCBR-9W4KR` | +| Windows Server 2016 Datacenter Semi-Annual Channel (v.1803) | `2HXDN-KRXHB-GPYC7-YCKFJ-7FVDG` | +| Windows Server 2016 Datacenter Semi-Annual Channel (v.1709) | `6Y6KB-N82V8-D8CQV-23MJW-BWTG6` | +| Windows Server 2016 Standard Semi-Annual Channel (v.1709) | `DPCNP-XQFKJ-BJF7R-FRC8D-GF6G4` | +| Windows Server 2016 ARM64 | `K9FYF-G6NCK-73M32-XMVPY-F9DRR` | +| Windows Server 2016 Datacenter | `CB7KF-BWN84-R7R2Y-793K2-8XDDG` | +| Windows Server 2016 Standard | `WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY` | +| Windows Server 2016 Essentials | `JCKRF-N37P4-C2D82-9YXRT-4M63B` | +| Windows Server 2016 Cloud Storage | `QN4C6-GBJD2-FB422-GHWJK-GJG2R` | +| Windows Server 2016 Azure Core | `VP34G-4NPPG-79JTQ-864T4-R3MQX`
`WNCYY-GFBH2-M4WTT-XQ2FP-PG2K9` | + +### Windows 10 & Windows 11 + +| Product | GVLK | +| ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| Windows 10/11 Professional Workstation | `NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J` | +| Windows 10/11 Professional Workstation N | `9FNHH-K3HBT-3W4TD-6383H-6XYWF` | +| Windows 10/11 Enterprise G | `YYVX9-NTFWV-6MDM3-9PT4T-4M68B` | +| Windows 10/11 Enterprise G N | `44RPN-FTY23-9VTTB-MP9BX-T84FV` | +| Windows 10 Enterprise LTSC 2019/2021 | `M7XTQ-FN8P6-TTKYV-9D4CC-J462D` | +| Windows 10 Enterprise LTSC 2019/2021 N | `92NFX-8DJQP-P6BBQ-THF9C-7CG2H` | +| Windows 10/11 Remote Server | `7NBT4-WGBQX-MP4H7-QXFF8-YP3KX` | +| Windows 10 Enterprise for Remote Sessions
Windows 10 Enterprise for virtual desktops
Windows 11 Enterprise multi-session | `CPWHC-NT2C7-VYW78-DHDB2-PG3GK` | +| Windows 10 S (Lean) | `NBTWJ-3DR69-3C4V8-C26MC-GQ9M6` | +| Windows 10/11 Professional | `W269N-WFGWX-YVC9B-4J6C9-T83GX` | +| Windows 10/11 Professional N | `MH37W-N47XK-V7XM9-C7227-GCQG9`
`HMNWJ-V69R6-B2CDC-8P7VT-2373K` | +| Windows 10/11 Professional Education | `6TP4R-GNPTD-KYYHQ-7B7DP-J447Y` | +| Windows 10/11 Professional Education N | `YVWGF-BXNMC-HTQYQ-CPQ99-66QFC` | +| Windows 10/11 Education | `NW6C2-QMPVW-D7KKK-3GKT6-VCFB2`
`F48BJ-8NX82-MRVY9-PF8BW-HMHY2` | +| Windows 10/11 Education N | `2WH4N-8QGBV-H22JP-CT43Q-MDWWJ`
`PPWGW-8NW9C-J77Q9-8WHB9-QV64W` | +| Windows 10/11 Enterprise | `NPPR9-FWDCX-D2C8J-H872K-2YT43`
`96YNV-9X4RP-2YYKB-RMQH4-6Q72D`
`TN6CM-KCVXP-VVP8X-YVCF7-R9BDH`
`3PMKQ-YNVGT-HFJGG-2F4FQ-9D6T7` | +| Windows 10/11 Enterprise N | `DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4`
`WGGHN-J84D6-QYCPR-T7PJ7-X766F` | +| Windows 10/11 Enterprise S | `H76BG-QBNM7-73XY9-V6W2T-684BJ` | +| Windows 10/11 Enterprise S N | `X4R4B-NV6WD-PKTVK-F98BH-4C2J8` | +| Windows 10 Enterprise 2015 LTSB | `WNMTR-4C88C-JK8YV-HQ7T2-76DF9` | +| Windows 10 Enterprise 2015 LTSB N | `2F77B-TNFGY-69QQF-B8YKP-D69TJ`
`RW7WN-FMT44-KRGBK-G44WK-QV7YK` | +| Windows 10 Enterprise 2016 LTSB | `DCPHK-NFMTC-H88MJ-PFHPY-QJ4BJ` | +| Windows 10 Enterprise 2016 LTSB N | `QFFDN-GRT3P-VKWWX-X7T3R-8B639` | +| Windows 10/11 Home
Windows 10/11 Core | `TX9XD-98N7V-6WMQ6-BX7FG-H8Q99`
`33QT6-RCNYF-DXB4F-DGP7B-7MHX9` | +| Windows 10/11 Home N
Windows 10/11 Core N | `3KHY7-WNT83-DGQKR-F7HPR-844BM`
`CP4KF-NG6TC-9K6QF-P6GTT-H8RBM` | +| Windows 10/11 Home Single Language
Windows 10 Core Single Language | `7HNRX-D7KGG-3K4RQ-4WPJ4-YTDFH`
`9HGRW-NH2CQ-XQHJD-YCRWB-6VJV7`
`4NX46-6DHCG-MR3PH-9FMCX-3RQ3G` | +| Windows 10/11 Home Country Specific
Windows 10 Core Country Specific | `PVMJN-6DFY6-9CCP6-7BKTT-D3WVR`
`JN9HR-MH7K4-DBPDD-TFTXF-Q9MMF` | ### Windows Server 2012 R2 -| Product | GVLK | -| --- | --- | -| Windows Server 2012 R2 Standard | `D2N9P-3P6X9-2R39C-7RTCD-MDVJX` | -| Windows Server 2012 R2 Datacenter | `W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9` | -| Windows Server 2012 R2 Essentials | `KNC87-3J2TX-XB4WP-VCPJV-M4FWM` | +| Product | GVLK | +| ------------------------------------ | ------------------------------- | +| Windows Server 2012 R2 Standard | `D2N9P-3P6X9-2R39C-7RTCD-MDVJX` | +| Windows Server 2012 R2 Datacenter | `W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9` | +| Windows Server 2012 R2 Essentials | `KNC87-3J2TX-XB4WP-VCPJV-M4FWM` | | Windows Server 2012 R2 Cloud Storage | `3NPTF-33KPT-GGBPR-YX76B-39KDD` | ### Windows 8.1 -| Product | GVLK | -| --- | --- | -| Windows 8.1 Professional | `GCRJD-8NW9H-F2CDX-CCM8D-9D6T9` | -| Windows 8.1 Professional N | `HMCNV-VVBFX-7HMBH-CTY9B-B4FXY` | -| Windows 8.1 Professional WMC | `789NJ-TQK6T-6XTH8-J39CJ-J8D3P` | -| Windows 8.1 Enterprise | `MHF9N-XY6XB-WVXMC-BTDCT-MKKG7`
`FHQNR-XYXYC-8PMHT-TV4PH-DRQ3H` | -| Windows 8.1 Enterprise N | `TT4HM-HN7YT-62K67-RGRQJ-JFFXW`
`NDRDJ-3YBP2-8WTKD-CK7VB-HT8KW` | -| Windows 8.1 Embedded Industry Automotive | `VHXM3-NR6FT-RY6RT-CK882-KW2CJ` | -| Windows 8.1 Embedded Industry Enterprise | `FNFKF-PWTVT-9RC8H-32HB2-JB34X` | -| Windows 8.1 Embedded Industry Professional | `NMMPB-38DD4-R2823-62W8D-VXKJB` | -| Windows 8.1 Core | `M9Q9P-WNJJT-6PXPY-DWX8H-6XWKK` | -| Windows 8.1 Core N | `7B9N3-D94CG-YTVHR-QBPX3-RJP64` | -| Windows 8.1 Core Single Language | `BB6NG-PQ82V-VRDPW-8XVD2-V8P66` | -| Windows 8.1 Core Country Specific | `NCTT7-2RGK8-WMHRF-RY7YQ-JTXG3` | -| Windows 8.1 Core ARM | `XYTND-K6QKT-K2MRH-66RTM-43JKP` | -| Windows 8.1 Core Connected | `3PY8R-QHNP9-W7XQD-G6DPH-3J2C9` | -| Windows 8.1 Core Connected N | `Q6HTR-N24GM-PMJFP-69CD8-2GXKR` | -| Windows 8.1 Core Connected Country Specific | `R962J-37N87-9VVK2-WJ74P-XTMHR` | -| Windows 8.1 Core Connected Single Language | `KF37N-VDV38-GRRTV-XH8X6-6F3BB` | -| Windows 8.1 Professional Student | `MX3RK-9HNGX-K3QKC-6PJ3F-W8D7B` | -| Windows 8.1 Professional Student N | `TNFGH-2R6PB-8XM3K-QYHX2-J4296` | +| Product | GVLK | +| ------------------------------------------- | ------------------------------------------------------------------ | +| Windows 8.1 Professional | `GCRJD-8NW9H-F2CDX-CCM8D-9D6T9` | +| Windows 8.1 Professional N | `HMCNV-VVBFX-7HMBH-CTY9B-B4FXY` | +| Windows 8.1 Professional WMC | `789NJ-TQK6T-6XTH8-J39CJ-J8D3P` | +| Windows 8.1 Enterprise | `MHF9N-XY6XB-WVXMC-BTDCT-MKKG7`
`FHQNR-XYXYC-8PMHT-TV4PH-DRQ3H` | +| Windows 8.1 Enterprise N | `TT4HM-HN7YT-62K67-RGRQJ-JFFXW`
`NDRDJ-3YBP2-8WTKD-CK7VB-HT8KW` | +| Windows 8.1 Embedded Industry Automotive | `VHXM3-NR6FT-RY6RT-CK882-KW2CJ` | +| Windows 8.1 Embedded Industry Enterprise | `FNFKF-PWTVT-9RC8H-32HB2-JB34X` | +| Windows 8.1 Embedded Industry Professional | `NMMPB-38DD4-R2823-62W8D-VXKJB` | +| Windows 8.1 Core | `M9Q9P-WNJJT-6PXPY-DWX8H-6XWKK` | +| Windows 8.1 Core N | `7B9N3-D94CG-YTVHR-QBPX3-RJP64` | +| Windows 8.1 Core Single Language | `BB6NG-PQ82V-VRDPW-8XVD2-V8P66` | +| Windows 8.1 Core Country Specific | `NCTT7-2RGK8-WMHRF-RY7YQ-JTXG3` | +| Windows 8.1 Core ARM | `XYTND-K6QKT-K2MRH-66RTM-43JKP` | +| Windows 8.1 Core Connected | `3PY8R-QHNP9-W7XQD-G6DPH-3J2C9` | +| Windows 8.1 Core Connected N | `Q6HTR-N24GM-PMJFP-69CD8-2GXKR` | +| Windows 8.1 Core Connected Country Specific | `R962J-37N87-9VVK2-WJ74P-XTMHR` | +| Windows 8.1 Core Connected Single Language | `KF37N-VDV38-GRRTV-XH8X6-6F3BB` | +| Windows 8.1 Professional Student | `MX3RK-9HNGX-K3QKC-6PJ3F-W8D7B` | +| Windows 8.1 Professional Student N | `TNFGH-2R6PB-8XM3K-QYHX2-J4296` | ### Windows Server 2012 -| Product | GVLK | -| --- | --- | -| Windows Server 2012
Windows 8 Core | `BN3D2-R7TKB-3YPBD-8DRP2-27GG4` | -| Windows Server 2012 N
Windows 8 Core N | `8N2M2-HWPGY-7PGT9-HGDD8-GVGGY` | -| Windows Server 2012 Single Language
Windows 8 Core Single Language | `2WN2H-YGCQR-KFX6K-CD6TF-84YXQ` | +| Product | GVLK | +| ----------------------------------------------------------------------- | ------------------------------- | +| Windows Server 2012
Windows 8 Core | `BN3D2-R7TKB-3YPBD-8DRP2-27GG4` | +| Windows Server 2012 N
Windows 8 Core N | `8N2M2-HWPGY-7PGT9-HGDD8-GVGGY` | +| Windows Server 2012 Single Language
Windows 8 Core Single Language | `2WN2H-YGCQR-KFX6K-CD6TF-84YXQ` | | Windows Server 2012 Country Specific
Windows 8 Core Country Specific | `4K36P-JN4VD-GDC6V-KDT89-DYFKP` | -| Windows Server 2012 Standard | `XC9B7-NBPP2-83J2H-RHMBY-92BT4` | -| Windows Server 2012 MultiPoint Standard | `HM7DN-YVMH3-46JC3-XYTG7-CYQJJ` | -| Windows Server 2012 MultiPoint Premium | `XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G` | -| Windows Server 2012 Datacenter | `48HP8-DN98B-MYWDG-T2DCC-8W83P` | +| Windows Server 2012 Standard | `XC9B7-NBPP2-83J2H-RHMBY-92BT4` | +| Windows Server 2012 MultiPoint Standard | `HM7DN-YVMH3-46JC3-XYTG7-CYQJJ` | +| Windows Server 2012 MultiPoint Premium | `XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G` | +| Windows Server 2012 Datacenter | `48HP8-DN98B-MYWDG-T2DCC-8W83P` | ### Windows 8 -| Product | GVLK | -| --- | --- | -| Windows 8 Professional | `NG4HW-VH26C-733KW-K6F98-J8CK4` | -| Windows 8 Professional N | `XCVCF-2NXM9-723PB-MHCB7-2RYQQ` | -| Windows 8 Professional WMC | `GNBB8-YVD74-QJHX6-27H4K-8QHDG`
`NQ3PX-BBY8Y-RRHMM-TBHFW-PJ866` | -| Windows 8 Enterprise | `32JNW-9KQ84-P47T8-D8GGY-CWCK7`
`8M9BN-YB7W9-YV3VJ-7WMGG-MKH3V` | -| Windows 8 Enterprise N | `JMNMF-RHW7P-DMY6X-RF3DR-X2BQT`
`NCVKH-RB9D4-R86X8-GB8WG-4M2K6` | -| Windows 8 Embedded Industry Professional | `JVPDN-TBWJW-PD94V-QYKJ2-KWYQM`
`RYXVT-BNQG7-VD29F-DBMRY-HT73M` | -| Windows 8 Embedded Industry Enterprise | `NKB3R-R2F8T-3XCDP-7Q2KW-XWYQ2` | -| Windows 8 Core
Windows Server 2012 | `BN3D2-R7TKB-3YPBD-8DRP2-27GG4` | -| Windows 8 Core N
Windows Server 2012 N | `8N2M2-HWPGY-7PGT9-HGDD8-GVGGY` | -| Windows 8 Core Single Language
Windows Server 2012 Single Language | `2WN2H-YGCQR-KFX6K-CD6TF-84YXQ` | -| Windows 8 Core Country Specific
Windows Server 2012 Country Specific | `4K36P-JN4VD-GDC6V-KDT89-DYFKP` | -| Windows 8 Core ARM | `DXHJF-N9KQX-MFPVR-GHGQK-Y7RKV` | +| Product | GVLK | +| ----------------------------------------------------------------------- | ------------------------------------------------------------------ | +| Windows 8 Professional | `NG4HW-VH26C-733KW-K6F98-J8CK4` | +| Windows 8 Professional N | `XCVCF-2NXM9-723PB-MHCB7-2RYQQ` | +| Windows 8 Professional WMC | `GNBB8-YVD74-QJHX6-27H4K-8QHDG`
`NQ3PX-BBY8Y-RRHMM-TBHFW-PJ866` | +| Windows 8 Enterprise | `32JNW-9KQ84-P47T8-D8GGY-CWCK7`
`8M9BN-YB7W9-YV3VJ-7WMGG-MKH3V` | +| Windows 8 Enterprise N | `JMNMF-RHW7P-DMY6X-RF3DR-X2BQT`
`NCVKH-RB9D4-R86X8-GB8WG-4M2K6` | +| Windows 8 Embedded Industry Professional | `JVPDN-TBWJW-PD94V-QYKJ2-KWYQM`
`RYXVT-BNQG7-VD29F-DBMRY-HT73M` | +| Windows 8 Embedded Industry Enterprise | `NKB3R-R2F8T-3XCDP-7Q2KW-XWYQ2` | +| Windows 8 Core
Windows Server 2012 | `BN3D2-R7TKB-3YPBD-8DRP2-27GG4` | +| Windows 8 Core N
Windows Server 2012 N | `8N2M2-HWPGY-7PGT9-HGDD8-GVGGY` | +| Windows 8 Core Single Language
Windows Server 2012 Single Language | `2WN2H-YGCQR-KFX6K-CD6TF-84YXQ` | +| Windows 8 Core Country Specific
Windows Server 2012 Country Specific | `4K36P-JN4VD-GDC6V-KDT89-DYFKP` | +| Windows 8 Core ARM | `DXHJF-N9KQX-MFPVR-GHGQK-Y7RKV` | ### Windows Server 2008 R2 -| Product | GVLK | -| --- | --- | -| Windows MultiPoint Server 2010 | `736RG-XDKJK-V34PF-BHK87-J6X3K` | -| Windows Server 2008 R2 Web | `6TPJF-RBVHG-WBW2R-86QPH-6RTM4` | -| Windows Server 2008 R2 HPC edition | `TT8MH-CG224-D3D7Q-498W2-9QCTX` | -| Windows Server 2008 R2 Standard | `YC6KT-GKW9T-YTKYR-T4X34-R7VHC` | -| Windows Server 2008 R2 Enterprise | `489J6-VHDMP-X63PK-3K798-CPX3Y` | -| Windows Server 2008 R2 Datacenter | `74YFP-3QFB3-KQT8W-PMXWJ-7M648` | -| Windows Server 2008 R2 for Itanium-based Systems | `GT63C-RJFQ3-4GMB6-BRFB9-CB83V` | +| Product | GVLK | +| ------------------------------------------------ | ------------------------------- | +| Windows MultiPoint Server 2010 | `736RG-XDKJK-V34PF-BHK87-J6X3K` | +| Windows Server 2008 R2 Web | `6TPJF-RBVHG-WBW2R-86QPH-6RTM4` | +| Windows Server 2008 R2 HPC edition | `TT8MH-CG224-D3D7Q-498W2-9QCTX` | +| Windows Server 2008 R2 Standard | `YC6KT-GKW9T-YTKYR-T4X34-R7VHC` | +| Windows Server 2008 R2 Enterprise | `489J6-VHDMP-X63PK-3K798-CPX3Y` | +| Windows Server 2008 R2 Datacenter | `74YFP-3QFB3-KQT8W-PMXWJ-7M648` | +| Windows Server 2008 R2 for Itanium-based Systems | `GT63C-RJFQ3-4GMB6-BRFB9-CB83V` | ### Windows 7 -| Product | GVLK | -| --- | --- | -| Windows 7 Professional | `FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4`
`MYKDJ-XV4CV-M2D3P-KDVY4-MPTW8` | -| Windows 7 Professional N | `MRPKT-YTG23-K7D7T-X2JMM-QY7MG` | -| Windows 7 Professional E | `W82YF-2Q76Y-63HXB-FGJG9-GF7QX` | -| Windows 7 Enterprise | `33PXH-7Y6KF-2VJC9-XBBR8-HVTHH` | -| Windows 7 Enterprise N | `YDRBP-3D83W-TY26F-D46B2-XCKRJ` | -| Windows 7 Enterprise E | `C29WB-22CC8-VJ326-GHFJW-H9DH4` | -| Windows 7 Embedded POSReady | `YBYF6-BHCR3-JPKRB-CDW7B-F9BK4` | -| Windows 7 Embedded ThinPC | `73KQT-CD9G6-K7TQG-66MRP-CQ22C` | -| Windows 7 Embedded Standard | `XGY72-BRBBT-FF8MH-2GG8H-W7KCW` | +| Product | GVLK | +| --------------------------- | ------------------------------------------------------------------ | +| Windows 7 Professional | `FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4`
`MYKDJ-XV4CV-M2D3P-KDVY4-MPTW8` | +| Windows 7 Professional N | `MRPKT-YTG23-K7D7T-X2JMM-QY7MG` | +| Windows 7 Professional E | `W82YF-2Q76Y-63HXB-FGJG9-GF7QX` | +| Windows 7 Enterprise | `33PXH-7Y6KF-2VJC9-XBBR8-HVTHH` | +| Windows 7 Enterprise N | `YDRBP-3D83W-TY26F-D46B2-XCKRJ` | +| Windows 7 Enterprise E | `C29WB-22CC8-VJ326-GHFJW-H9DH4` | +| Windows 7 Embedded POSReady | `YBYF6-BHCR3-JPKRB-CDW7B-F9BK4` | +| Windows 7 Embedded ThinPC | `73KQT-CD9G6-K7TQG-66MRP-CQ22C` | +| Windows 7 Embedded Standard | `XGY72-BRBBT-FF8MH-2GG8H-W7KCW` | ### Windows Server 2008 -| Product | GVLK | -| --- | --- | -| Windows Server 2008 Web | `WYR28-R7TFJ-3X2YQ-YCY4H-M249D` | -| Windows Server 2008 Standard | `TM24T-X9RMF-VWXK6-X8JC9-BFGM2` | -| Windows Server 2008 Standard without Hyper-V | `W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ` | -| Windows Server 2008 Enterprise | `YQGMW-MPWTJ-34KDK-48M3W-X4Q6V` | -| Windows Server 2008 Enterprise without Hyper-V | `39BXF-X8Q23-P2WWT-38T2F-G3FPG` | +| Product | GVLK | +| -------------------------------------------------- | ------------------------------- | +| Windows Server 2008 Web | `WYR28-R7TFJ-3X2YQ-YCY4H-M249D` | +| Windows Server 2008 Standard | `TM24T-X9RMF-VWXK6-X8JC9-BFGM2` | +| Windows Server 2008 Standard without Hyper-V | `W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ` | +| Windows Server 2008 Enterprise | `YQGMW-MPWTJ-34KDK-48M3W-X4Q6V` | +| Windows Server 2008 Enterprise without Hyper-V | `39BXF-X8Q23-P2WWT-38T2F-G3FPG` | | Windows Server 2008 HPC edition (Computer Cluster) | `RCTX3-KWVHP-BR6TB-RB6DM-6X7HP` | -| Windows Server 2008 Datacenter | `7M67G-PC374-GR742-YH8V4-TCBY3` | -| Windows Server 2008 Datacenter without Hyper-V | `22XQ2-VRXRG-P8D42-K34TD-G3QQC` | -| Windows Server 2008 for Itanium-Based Systems | `4DWFP-JF3DJ-B7DTH-78FJB-PDRHK` | +| Windows Server 2008 Datacenter | `7M67G-PC374-GR742-YH8V4-TCBY3` | +| Windows Server 2008 Datacenter without Hyper-V | `22XQ2-VRXRG-P8D42-K34TD-G3QQC` | +| Windows Server 2008 for Itanium-Based Systems | `4DWFP-JF3DJ-B7DTH-78FJB-PDRHK` | ### Windows Vista -| Product | GVLK | -| --- | --- | -| Windows Vista Business | `YFKBB-PQJJV-G996G-VWGXY-2V3X8` | -| Windows Vista Business N | `HMBQG-8H2RH-C77VX-27R82-VMQBT` | -| Windows Vista Enterprise | `VKK3X-68KWM-X2YGT-QR4M6-4BWMV` | +| Product | GVLK | +| -------------------------- | ------------------------------- | +| Windows Vista Business | `YFKBB-PQJJV-G996G-VWGXY-2V3X8` | +| Windows Vista Business N | `HMBQG-8H2RH-C77VX-27R82-VMQBT` | +| Windows Vista Enterprise | `VKK3X-68KWM-X2YGT-QR4M6-4BWMV` | | Windows Vista Enterprise N | `VTC42-BM838-43QHV-84HX6-XJXKV` | ### Windows Previews -| Product | GVLK | -| --- | --- | -| Windows Server 2019 Datacenter [Preview] | `6XBNX-4JQGW-QX6QG-74P76-72V67` | -| Windows Server 2019 Standard [Preview] | `MFY9F-XBN2F-TYFMP-CCV49-RMYVH` | -| Windows 10 Home / Core [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Home / Core Country Specific [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Home / Core N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Home / Core Single Language [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Home / Core [Technical Preview] | `?????-?????-?????-?????-?????` | -| Windows 10 Education [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Education N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Enterprise [Preview] | `QNMXX-GCD3W-TCCT4-872RV-G6P4J` | -| Windows 10 Enterprise 2015 LTSB [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Enterprise 2015 LTSB N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Enterprise N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Professional N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows 10 Professional [Preview] | `XQHPH-N4D9W-M8P96-DPDFP-TMVPY` | -| Windows 10 Professional WMC [Pre-Release] | `NKPM6-TCVPT-3HRFX-Q4H9B-QJ34Y`
`328NF-RTPQT-84J4Q-V44B8-78R2B` | -| Windows 10 IoT Core [Pre-Release] | `7NX88-X6YM3-9Q3YT-CCGBF-KBVQF`
`NHY4C-KCMKV-V9K9M-7R43T-KTP64` | -| Windows 10 Core Connected [Pre-Release] | `DJMYQ-WN6HG-YJ2YX-82JDB-CWFCW`
`QBWKP-NFVG3-CYGTT-724CF-FCYPW` | -| Windows 10 Core Connected N [Pre-Release] | `JQNT7-W63G4-WX4QX-RD9M9-6CPKM`
`TKDDW-N77V2-WXKMG-QY6WQ-WQJXM` | -| Windows 10 Core Connected Single Language [Pre-Release] | `QQMNF-GPVQ6-BFXGG-GWRCX-7XKT7`
`RQ2MN-RKR94-P86YQ-TM76X-P3667` | -| Windows 10 Core Connected Country Specific [Pre-Release] | `FTNXM-J4RGP-MYQCV-RVM8R-TVH24`
`TNPJK-GCKPR-4WX4C-HCJHT-HFRC4` | -| Windows 10 Professional S [Pre-Release] | `3NF4D-GF9GY-63VKH-QRC3V-7QW8P`
`NFDD9-FX3VM-DYCKP-B8HT8-D9M2C` | -| Windows 10 Professional S N [Pre-Release] | `KNDJ3-GVHWT-3TV4V-36K8Y-PR4PF`
`8Q36Y-N2F39-HRMHT-4XW33-TCQR4` | -| Windows 10 Professional Student [Pre-Release] | `YNXW3-HV3VB-Y83VG-KPBXM-6VH3Q`
`N6X24-448X6-HYV8Y-8XQ3V-DRRDQ` | -| Windows 10 Professional Student N [Pre-Release] | `8G9XJ-GN6PJ-GW787-MVV7G-GMR99`
`XHGFB-WNK7Q-BG8VG-BG2KQ-KKWX9` | -| Windows 10 PPIPro [Pre-Release (build 15063)] | `?????-?????-?????-?????-?????` | -| Windows 8 Core / Server 2012 [RC] | `?????-?????-?????-?????-?????` | -| Windows 8 Core / Server 2012 Country Specific [RC] | `?????-?????-?????-?????-?????` | -| Windows 8 Core / Server 2012 N [RC] | `?????-?????-?????-?????-?????` | -| Windows 8 Core / Server 2012 Single Language [RC] | `?????-?????-?????-?????-?????` | -| Windows 8 Core ARM64 [RC] | `?????-?????-?????-?????-?????` | -| Windows 8 Embedded Industry Professional [Beta] | `?????-?????-?????-?????-?????` | -| Windows 8 Embedded Industry Enterprise [Beta] | `?????-?????-?????-?????-?????` | -| Windows 8.1 Enterprise [Preview] | `2MP7K-98NK8-WPVF3-Q2WDG-VMD98` | -| Windows 8.1 Professional (Blue) [Preview] | `MTWNQ-CKDHJ-3HXW9-Q2PFX-WB2HQ` | -| Windows 8 Professional WMC [RC] | `MY4N9-TGH34-4X4VY-8FG2T-RRDPV` | -| Windows 8.x [Preview] | `MPWP3-DXNP9-BRD79-W8WFP-3YFJ6` | -| Windows 8.x ARM64 [Preview] | `?????-?????-?????-?????-?????` | -| Windows Next Core Connected [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Core Connected N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Core Connected Country Specific [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Core Connected Single Language [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Professional Student [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Professional Student N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Embedded Industry Professional [Beta] | `XY4TQ-CXNVJ-YCT73-HH6R7-R897X` | -| Windows Next Embedded Industry Enterprise [Beta] | `XCNC9-BPK3C-KCCMD-FTDTC-KWY4G`
`WN3XP-M9YFD-JRJ84-4J9FB-QJY4G` | -| Windows Next Embedded Industry Automotive [Beta] | `GN2X2-KXTK6-P92FR-VBB9G-PDJFP`
`434XB-NH62H-JG7RG-P3KMD-XHHJC` | -| Windows Server Next MultiPoint Standard [Preview] | `?????-?????-?????-?????-?????` | -| Windows Server Next MultiPoint Premium [Preview] | `?????-?????-?????-?????-?????` | -| Windows Server Next Enterprise [Preview] | `?????-?????-?????-?????-?????` | -| Windows Server Next Standard [Preview] | `?????-?????-?????-?????-?????` | -| Windows Server Next Web [Preview] | `?????-?????-?????-?????-?????` | -| Windows Server Next HPC Edition [Preview] | `?????-?????-?????-?????-?????` | -| Windows Server Next HI [Preview] | `7VX4N-3VDHQ-VYGHB-JXJVP-9QB26` | -| Enterprise ProdKey3 Win 9984 DLA/Bypass NQR Test | `?????-?????-?????-?????-?????` | -| Windows Server 2012 R2 Essentials [Preview] | `?????-?????-?????-?????-?????` | -| Windows Server 2016 Datacenter [Preview] | `VRDD2-NVGDP-K7QG8-69BR4-TVFHB` | -| Windows Vista Business [Preview 1] | `XQYF4-QVCMY-YXQRD-9QPV8-3YP9V` | -| Windows Vista Business [Preview 2] | `YVT36-YVCP2-J97GQ-7T22R-RWV8P` | -| Windows Vista Business N [Preview] | `HGBJ9-RWD6M-6HDGW-6T2XD-JQ66F` | -| Windows Vista Enterprise [Preview 1] | `3JHG3-Y66GP-B7F3K-JFVX2-VBH7K` | -| Windows Vista Enterprise [Beta-2 build 5384] | `MF9PG-RQK7R-26BPJ-TWFYK-RHXCM` | -| Windows Vista Enterprise N [Preview] | `?????-?????-?????-?????-?????` | -| Windows Longhorn Web [Preview] | `MDRCM-4WKCW-J93FF-J9Q48-M6KBB` | -| Windows Longhorn HPC Edition [Preview] | `?????-?????-?????-?????-?????` | -| Windows Longhorn Standard [Preview] | `Q37JX-P3HHB-GKRH2-PDBKG-GGXPW` | -| Windows Longhorn Enterprise [Preview] | `7KYMQ-R788Q-4RF69-KTWKM-92PFJ` | -| Windows Longhorn Datacenter [Preview] | `HR8VD-7DHG2-48378-M9D73-28F4T` | -| Windows Longhorn for Itanium Systems [Preview] | `CWV9H-PHGPW-V93WV-QBQV9-8V336` | -| Windows 7 Business [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Business N [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Enterprise [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Enterprise N [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Server Web [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Server Standard [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Server Standard without Hyper-V [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Server Enterprise [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Server Enterprise without Hyper-V [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Server Datacenter [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Server Datacenter without Hyper-V [Preview] | `?????-?????-?????-?????-?????` | -| Windows 7 Server for Itanium Systems [Preview] | `?????-?????-?????-?????-?????` | -| Windows Next Education [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Education N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Professional [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Professional N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Enterprise N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Enterprise [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Enterprise S [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Enterprise S N [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Professional S [Pre-Release] | `?????-?????-?????-?????-?????` | -| Windows Next Professional S N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Product | GVLK | +| ---------------------------------------------------------- | ------------------------------------------------------------------ | +| Windows Server 2019 Datacenter [Preview] | `6XBNX-4JQGW-QX6QG-74P76-72V67` | +| Windows Server 2019 Standard [Preview] | `MFY9F-XBN2F-TYFMP-CCV49-RMYVH` | +| Windows 10 Home / Core [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Home / Core Country Specific [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Home / Core N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Home / Core Single Language [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Home / Core [Technical Preview] | `?????-?????-?????-?????-?????` | +| Windows 10 Education [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Education N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Enterprise [Preview] | `QNMXX-GCD3W-TCCT4-872RV-G6P4J` | +| Windows 10 Enterprise 2015 LTSB [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Enterprise 2015 LTSB N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Enterprise N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Professional N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows 10 Professional [Preview] | `XQHPH-N4D9W-M8P96-DPDFP-TMVPY` | +| Windows 10 Professional WMC [Pre-Release] | `NKPM6-TCVPT-3HRFX-Q4H9B-QJ34Y`
`328NF-RTPQT-84J4Q-V44B8-78R2B` | +| Windows 10 IoT Core [Pre-Release] | `7NX88-X6YM3-9Q3YT-CCGBF-KBVQF`
`NHY4C-KCMKV-V9K9M-7R43T-KTP64` | +| Windows 10 Core Connected [Pre-Release] | `DJMYQ-WN6HG-YJ2YX-82JDB-CWFCW`
`QBWKP-NFVG3-CYGTT-724CF-FCYPW` | +| Windows 10 Core Connected N [Pre-Release] | `JQNT7-W63G4-WX4QX-RD9M9-6CPKM`
`TKDDW-N77V2-WXKMG-QY6WQ-WQJXM` | +| Windows 10 Core Connected Single Language [Pre-Release] | `QQMNF-GPVQ6-BFXGG-GWRCX-7XKT7`
`RQ2MN-RKR94-P86YQ-TM76X-P3667` | +| Windows 10 Core Connected Country Specific [Pre-Release] | `FTNXM-J4RGP-MYQCV-RVM8R-TVH24`
`TNPJK-GCKPR-4WX4C-HCJHT-HFRC4` | +| Windows 10 Professional S [Pre-Release] | `3NF4D-GF9GY-63VKH-QRC3V-7QW8P`
`NFDD9-FX3VM-DYCKP-B8HT8-D9M2C` | +| Windows 10 Professional S N [Pre-Release] | `KNDJ3-GVHWT-3TV4V-36K8Y-PR4PF`
`8Q36Y-N2F39-HRMHT-4XW33-TCQR4` | +| Windows 10 Professional Student [Pre-Release] | `YNXW3-HV3VB-Y83VG-KPBXM-6VH3Q`
`N6X24-448X6-HYV8Y-8XQ3V-DRRDQ` | +| Windows 10 Professional Student N [Pre-Release] | `8G9XJ-GN6PJ-GW787-MVV7G-GMR99`
`XHGFB-WNK7Q-BG8VG-BG2KQ-KKWX9` | +| Windows 10 PPIPro [Pre-Release (build 15063)] | `?????-?????-?????-?????-?????` | +| Windows 8 Core / Server 2012 [RC] | `?????-?????-?????-?????-?????` | +| Windows 8 Core / Server 2012 Country Specific [RC] | `?????-?????-?????-?????-?????` | +| Windows 8 Core / Server 2012 N [RC] | `?????-?????-?????-?????-?????` | +| Windows 8 Core / Server 2012 Single Language [RC] | `?????-?????-?????-?????-?????` | +| Windows 8 Core ARM64 [RC] | `?????-?????-?????-?????-?????` | +| Windows 8 Embedded Industry Professional [Beta] | `?????-?????-?????-?????-?????` | +| Windows 8 Embedded Industry Enterprise [Beta] | `?????-?????-?????-?????-?????` | +| Windows 8.1 Enterprise [Preview] | `2MP7K-98NK8-WPVF3-Q2WDG-VMD98` | +| Windows 8.1 Professional (Blue) [Preview] | `MTWNQ-CKDHJ-3HXW9-Q2PFX-WB2HQ` | +| Windows 8 Professional WMC [RC] | `MY4N9-TGH34-4X4VY-8FG2T-RRDPV` | +| Windows 8.x [Preview] | `MPWP3-DXNP9-BRD79-W8WFP-3YFJ6` | +| Windows 8.x ARM64 [Preview] | `?????-?????-?????-?????-?????` | +| Windows Next Core Connected [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Core Connected N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Core Connected Country Specific [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Core Connected Single Language [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Professional Student [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Professional Student N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Embedded Industry Professional [Beta] | `XY4TQ-CXNVJ-YCT73-HH6R7-R897X` | +| Windows Next Embedded Industry Enterprise [Beta] | `XCNC9-BPK3C-KCCMD-FTDTC-KWY4G`
`WN3XP-M9YFD-JRJ84-4J9FB-QJY4G` | +| Windows Next Embedded Industry Automotive [Beta] | `GN2X2-KXTK6-P92FR-VBB9G-PDJFP`
`434XB-NH62H-JG7RG-P3KMD-XHHJC` | +| Windows Server Next MultiPoint Standard [Preview] | `?????-?????-?????-?????-?????` | +| Windows Server Next MultiPoint Premium [Preview] | `?????-?????-?????-?????-?????` | +| Windows Server Next Enterprise [Preview] | `?????-?????-?????-?????-?????` | +| Windows Server Next Standard [Preview] | `?????-?????-?????-?????-?????` | +| Windows Server Next Web [Preview] | `?????-?????-?????-?????-?????` | +| Windows Server Next HPC Edition [Preview] | `?????-?????-?????-?????-?????` | +| Windows Server Next HI [Preview] | `7VX4N-3VDHQ-VYGHB-JXJVP-9QB26` | +| Enterprise ProdKey3 Win 9984 DLA/Bypass NQR Test | `?????-?????-?????-?????-?????` | +| Windows Server 2012 R2 Essentials [Preview] | `?????-?????-?????-?????-?????` | +| Windows Server 2016 Datacenter [Preview] | `VRDD2-NVGDP-K7QG8-69BR4-TVFHB` | +| Windows Vista Business [Preview 1] | `XQYF4-QVCMY-YXQRD-9QPV8-3YP9V` | +| Windows Vista Business [Preview 2] | `YVT36-YVCP2-J97GQ-7T22R-RWV8P` | +| Windows Vista Business N [Preview] | `HGBJ9-RWD6M-6HDGW-6T2XD-JQ66F` | +| Windows Vista Enterprise [Preview 1] | `3JHG3-Y66GP-B7F3K-JFVX2-VBH7K` | +| Windows Vista Enterprise [Beta-2 build 5384] | `MF9PG-RQK7R-26BPJ-TWFYK-RHXCM` | +| Windows Vista Enterprise N [Preview] | `?????-?????-?????-?????-?????` | +| Windows Longhorn Web [Preview] | `MDRCM-4WKCW-J93FF-J9Q48-M6KBB` | +| Windows Longhorn HPC Edition [Preview] | `?????-?????-?????-?????-?????` | +| Windows Longhorn Standard [Preview] | `Q37JX-P3HHB-GKRH2-PDBKG-GGXPW` | +| Windows Longhorn Enterprise [Preview] | `7KYMQ-R788Q-4RF69-KTWKM-92PFJ` | +| Windows Longhorn Datacenter [Preview] | `HR8VD-7DHG2-48378-M9D73-28F4T` | +| Windows Longhorn for Itanium Systems [Preview] | `CWV9H-PHGPW-V93WV-QBQV9-8V336` | +| Windows 7 Business [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Business N [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Enterprise [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Enterprise N [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Server Web [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Server Standard [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Server Standard without Hyper-V [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Server Enterprise [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Server Enterprise without Hyper-V [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Server Datacenter [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Server Datacenter without Hyper-V [Preview] | `?????-?????-?????-?????-?????` | +| Windows 7 Server for Itanium Systems [Preview] | `?????-?????-?????-?????-?????` | +| Windows Next Education [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Education N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Professional [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Professional N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Enterprise N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Enterprise [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Enterprise S [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Enterprise S N [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Professional S [Pre-Release] | `?????-?????-?????-?????-?????` | +| Windows Next Professional S N [Pre-Release] | `?????-?????-?????-?????-?????` | ## Office -*** + +### Office 2021 + +| Product | GVLK | +| ----------------------------------- | ------------------------------- | +| Office Access LTSC 2021 | `WM8YG-YNGDD-4JHDC-PG3F4-FC4T4` | +| Office Excel LTSC 2021 | `NWG3X-87C9K-TC7YY-BC2G7-G6RVC` | +| Office Outlook LTSC 2021 | `C9FM6-3N72F-HFJXB-TM3V9-T86R9` | +| Office Powerpoint LTSC 2021 | `TY7XF-NFRBR-KJ44C-G83KF-GX27K` | +| Office LTSC Professional Plus 2021 | `FXYTK-NJJ8C-GB6DW-3DYQT-6F7TH` | +| Office Project Pro 2021 | `FTNWT-C6WBT-8HMGF-K9PRX-QV9H8` | +| Office Project Standard 2021 | `J2JDC-NJCYY-9RGQ4-YXWMH-T3D4T` | +| Office Publisher LTSC 2021 | `2MW9D-N4BXM-9VBPG-Q7W6M-KFBGQ` | +| Office Skype for Business LTSC 2021 | `HWCXN-K3WBT-WJBKY-R8BD9-XK29P` | +| Office LTSC Standard 2021 | `KDX7X-BNVR8-TXXGX-4Q7Y8-78VT3` | +| Office Visio LTSC Pro 2021 | `KNH8D-FGHT4-T8RK3-CTDYJ-K2HT4` | +| Office Visio LTSC Standard 2021 | `MJVNY-BYWPY-CWV6J-2RKRT-4M8QG` | +| Office Word LTSC 2021 | `TN8H9-M34D3-Y64V9-TR72V-X79KV` | ### Office 2019 -| Product | GVLK | -| --- | --- | -| Professional Plus 2019 [C2R] | `VQ9DP-NVHPH-T9HJC-J9PDT-KTQRG` | -| Professional Plus 2019 | `NMMKJ-6RK4F-KMJVX-8D9MJ-6MWKP` | -| Standard 2019 | `6NWWJ-YQWMR-QKGCB-6TMB3-9D9HK` | +| Product | GVLK | +| ------------------------------- | ------------------------------- | +| Professional Plus 2019 [C2R] | `VQ9DP-NVHPH-T9HJC-J9PDT-KTQRG` | +| Professional Plus 2019 | `NMMKJ-6RK4F-KMJVX-8D9MJ-6MWKP` | +| Standard 2019 | `6NWWJ-YQWMR-QKGCB-6TMB3-9D9HK` | | Project Professional 2019 [C2R] | `XM2V9-DN9HH-QB449-XDGKC-W2RMW` | -| Project Professional 2019 | `B4NPR-3FKK7-T2MBV-FRQ4W-PKD2B` | -| Project Standard 2019 | `C4F7P-NCP8C-6CQPT-MQHV9-JXD2M` | -| Visio Professional 2019 [C2R] | `N2CG9-YD3YK-936X4-3WR82-Q3X4H` | -| Visio Professional 2019 | `9BGNQ-K37YR-RQHF2-38RQ3-7VCBB` | -| Visio Standard 2019 | `7TQNQ-K3YQQ-3PFH7-CCPPM-X4VQ2` | -| Access 2019 | `9N9PT-27V4Y-VJ2PD-YXFMF-YTFQT` | -| Excel 2019 | `TMJWT-YYNMB-3BKTF-644FC-RVXBD` | -| Outlook 2019 | `7HD7K-N4PVK-BHBCQ-YWQRW-XW4VK` | -| PowerPoint 2019 | `RRNCX-C64HY-W2MM7-MCH9G-TJHMQ` | -| Publisher 2019 | `G2KWX-3NW6P-PY93R-JXK2T-C9Y9V` | -| Skype for Business 2019 | `NCJ33-JHBBY-HTK98-MYCV8-HMKHJ` | -| Word 2019 | `PBX3G-NWMT6-Q7XBW-PYJGG-WXD33` | +| Project Professional 2019 | `B4NPR-3FKK7-T2MBV-FRQ4W-PKD2B` | +| Project Standard 2019 | `C4F7P-NCP8C-6CQPT-MQHV9-JXD2M` | +| Visio Professional 2019 [C2R] | `N2CG9-YD3YK-936X4-3WR82-Q3X4H` | +| Visio Professional 2019 | `9BGNQ-K37YR-RQHF2-38RQ3-7VCBB` | +| Visio Standard 2019 | `7TQNQ-K3YQQ-3PFH7-CCPPM-X4VQ2` | +| Access 2019 | `9N9PT-27V4Y-VJ2PD-YXFMF-YTFQT` | +| Excel 2019 | `TMJWT-YYNMB-3BKTF-644FC-RVXBD` | +| Outlook 2019 | `7HD7K-N4PVK-BHBCQ-YWQRW-XW4VK` | +| PowerPoint 2019 | `RRNCX-C64HY-W2MM7-MCH9G-TJHMQ` | +| Publisher 2019 | `G2KWX-3NW6P-PY93R-JXK2T-C9Y9V` | +| Skype for Business 2019 | `NCJ33-JHBBY-HTK98-MYCV8-HMKHJ` | +| Word 2019 | `PBX3G-NWMT6-Q7XBW-PYJGG-WXD33` | ### Office 2016 -| Product | GVLK | -| --- | --- | -| Professional Plus 2016 | `XQNVK-8JYDB-WJ9W3-YJ8YR-WFG99` | -| Standard 2016 | `JNRGM-WHDWX-FJJG3-K47QV-DRTFM` | -| Project Professional 2016 | `YG9NW-3K39V-2T3HJ-93F3Q-G83KT` | +| Product | GVLK | +| ------------------------------- | ------------------------------- | +| Professional Plus 2016 | `XQNVK-8JYDB-WJ9W3-YJ8YR-WFG99` | +| Standard 2016 | `JNRGM-WHDWX-FJJG3-K47QV-DRTFM` | +| Project Professional 2016 | `YG9NW-3K39V-2T3HJ-93F3Q-G83KT` | | Project Professional 2016 [C2R] | `WGT24-HCNMF-FQ7XH-6M8K7-DRTW9` | -| Project Standard 2016 | `GNFHQ-F6YQM-KQDGJ-327XX-KQBVC` | -| Project Standard 2016 [C2R] | `D8NRQ-JTYM3-7J2DX-646CT-6836M` | -| Visio Professional 2016 | `PD3PC-RHNGV-FXJ29-8JK7D-RJRJK` | -| Visio Professional 2016 [C2R] | `69WXN-MBYV6-22PQG-3WGHK-RM6XC` | -| Visio Standard 2016 | `7WHWN-4T7MP-G96JF-G33KR-W8GF4` | -| Visio Standard 2016 [C2R] | `NY48V-PPYYH-3F4PX-XJRKJ-W4423` | -| Access 2016 | `GNH9Y-D2J4T-FJHGG-QRVH7-QPFDW` | -| Excel 2016 | `9C2PK-NWTVB-JMPW8-BFT28-7FTBF` | -| Mondo 2016 | `HFTND-W9MK4-8B7MJ-B6C4G-XQBR2` | -| Mondo Retail 2016 | `DMTCJ-KNRKX-26982-JYCKT-P7KB6` | -| OneNote 2016 | `DR92N-9HTF2-97XKM-XW2WJ-XW3J6` | -| Outlook 2016 | `R69KK-NTPKF-7M3Q4-QYBHW-6MT9B` | -| PowerPoint 2016 | `J7MQP-HNJ4Y-WJ7YM-PFYGF-BY6C6` | -| Publisher 2016 | `F47MM-N3XJP-TQXJ9-BP99D-8K837` | -| Skype for Business 2016 | `869NQ-FJ69K-466HW-QYCP2-DDBV6` | -| Word 2016 | `WXY84-JN2Q9-RBCCQ-3Q3J3-3PFJ6` | +| Project Standard 2016 | `GNFHQ-F6YQM-KQDGJ-327XX-KQBVC` | +| Project Standard 2016 [C2R] | `D8NRQ-JTYM3-7J2DX-646CT-6836M` | +| Visio Professional 2016 | `PD3PC-RHNGV-FXJ29-8JK7D-RJRJK` | +| Visio Professional 2016 [C2R] | `69WXN-MBYV6-22PQG-3WGHK-RM6XC` | +| Visio Standard 2016 | `7WHWN-4T7MP-G96JF-G33KR-W8GF4` | +| Visio Standard 2016 [C2R] | `NY48V-PPYYH-3F4PX-XJRKJ-W4423` | +| Access 2016 | `GNH9Y-D2J4T-FJHGG-QRVH7-QPFDW` | +| Excel 2016 | `9C2PK-NWTVB-JMPW8-BFT28-7FTBF` | +| Mondo 2016 | `HFTND-W9MK4-8B7MJ-B6C4G-XQBR2` | +| Mondo Retail 2016 | `DMTCJ-KNRKX-26982-JYCKT-P7KB6` | +| OneNote 2016 | `DR92N-9HTF2-97XKM-XW2WJ-XW3J6` | +| Outlook 2016 | `R69KK-NTPKF-7M3Q4-QYBHW-6MT9B` | +| PowerPoint 2016 | `J7MQP-HNJ4Y-WJ7YM-PFYGF-BY6C6` | +| Publisher 2016 | `F47MM-N3XJP-TQXJ9-BP99D-8K837` | +| Skype for Business 2016 | `869NQ-FJ69K-466HW-QYCP2-DDBV6` | +| Word 2016 | `WXY84-JN2Q9-RBCCQ-3Q3J3-3PFJ6` | ### Office 2013 -| Product | GVLK | -| --- | --- | -| Professional Plus 2013 [Preview] | `PGD67-JN23K-JGVWV-KTHP4-GXR9G` | -| Professional Plus 2013 | `YC7DK-G2NP3-2QQC3-J6H88-GVGXT` | -| Standard 2013 | `KBKQT-2NMXY-JJWGP-M62JB-92CD4` | -| Project Professional 2013 [Preview] | `NFKVM-DVG7F-TYWYR-3RPHY-F872K` | -| Project Professional 2013 | `FN8TT-7WMH6-2D4X9-M337T-2342K` | -| Project Standard 2013 [Preview] | `N89QF-GGB8J-BKD28-C4V28-W4XTK` | -| Project Standard 2013 | `6NTH3-CW976-3G3Y2-JK3TX-8QHTT` | -| Visio Professional 2013 [Preview] | `B3C7Q-D6NH2-2VRFW-HHWDG-FVQB6` | -| Visio Professional 2013 | `C2FG9-N6J68-H8BTJ-BW3QX-RM3B3` | -| Visio Standard 2013 [Preview] | `9MKNF-J9XQ6-JV4XB-FJQPY-43F43` | -| Visio Standard 2013 | `J484Y-4NKBF-W2HMG-DBMJC-PGWR7` | -| Access 2013 [Preview] | `DJBH8-RGN7Q-836KD-DMP3M-DM9MF` | -| Access 2013 | `NG2JY-H4JBT-HQXYP-78QH9-4JM2D` | -| Excel 2013 [Preview] | `Q3BNP-3WXDT-GG8HF-24KMW-HMDBK` | -| Excel 2013 | `VGPNG-Y7HQW-9RHP7-TKPV3-BG7GB` | -| OneNote 2013 [Preview] | `VYNYX-8GPBC-7FQMD-D6B7B-7MDFD` | -| OneNote 2013 | `TGN6P-8MMBC-37P2F-XHXXK-P34VW` | -| Outlook 2013 [Preview] | `X2KNB-FRRG2-WXDPH-739DM-DM9RH` | -| Outlook 2013 | `QPN8Q-BJBTJ-334K3-93TGY-2PMBT` | -| PowerPoint 2013 [Preview] | `B8CT8-BTNFQ-XQXBK-BFWV8-HMDFQ` | -| PowerPoint 2013 | `4NT99-8RJFH-Q2VDH-KYG2C-4RD4F` | -| Publisher 2013 [Preview] | `NB67P-J8XP4-XDK9B-V73VH-M4CKR` | -| Publisher 2013 | `PN2WF-29XG2-T9HJ7-JQPJR-FCXK4` | -| InfoPath 2013 (Preview) | `7KPJJ-N8TT7-CK3KR-QTV98-YPVXQ` | -| InfoPath 2013 | `DKT8B-N7VXH-D963P-Q4PHY-F8894` | -| Lync 2013 [Preview] | `XNVD3-RYC7T-7R6BT-WX6CF-8BYH7` | -| Lync 2013 | `2MG3G-3BNTT-3MFW9-KDQW3-TCK7R` | -| Word 2013 [Preview] | `JBGD4-3JNG7-JWWGV-CR6TP-DC62Q` | -| Word 2013 | `6Q7VD-NX8JD-WJ2VH-88V73-4GBJ7` | -| Mondo 2013 [Preview] | `GCGCN-6FJRM-TR9Q3-BGMWJ-78KQV` | -| Mondo 2013 | `42QTK-RN8M7-J3C4G-BBGYM-88CYV` | -| Mondo 2013 Retail | `?????-?????-?????-?????-?????` | -| SharePoint Workspace (Groove) 2013 [Preview] | `WVCGG-NK4FG-7XKXM-BD4WF-3C624` | -| SharePoint Workspace (Groove) 2013 | `H7R7V-WPNXQ-WCYYC-76BGV-VT7GH` | +| Product | GVLK | +| ----------------------------------------------------- | ------------------------------- | +| Professional Plus 2013 [Preview] | `PGD67-JN23K-JGVWV-KTHP4-GXR9G` | +| Professional Plus 2013 | `YC7DK-G2NP3-2QQC3-J6H88-GVGXT` | +| Standard 2013 | `KBKQT-2NMXY-JJWGP-M62JB-92CD4` | +| Project Professional 2013 [Preview] | `NFKVM-DVG7F-TYWYR-3RPHY-F872K` | +| Project Professional 2013 | `FN8TT-7WMH6-2D4X9-M337T-2342K` | +| Project Standard 2013 [Preview] | `N89QF-GGB8J-BKD28-C4V28-W4XTK` | +| Project Standard 2013 | `6NTH3-CW976-3G3Y2-JK3TX-8QHTT` | +| Visio Professional 2013 [Preview] | `B3C7Q-D6NH2-2VRFW-HHWDG-FVQB6` | +| Visio Professional 2013 | `C2FG9-N6J68-H8BTJ-BW3QX-RM3B3` | +| Visio Standard 2013 [Preview] | `9MKNF-J9XQ6-JV4XB-FJQPY-43F43` | +| Visio Standard 2013 | `J484Y-4NKBF-W2HMG-DBMJC-PGWR7` | +| Access 2013 [Preview] | `DJBH8-RGN7Q-836KD-DMP3M-DM9MF` | +| Access 2013 | `NG2JY-H4JBT-HQXYP-78QH9-4JM2D` | +| Excel 2013 [Preview] | `Q3BNP-3WXDT-GG8HF-24KMW-HMDBK` | +| Excel 2013 | `VGPNG-Y7HQW-9RHP7-TKPV3-BG7GB` | +| OneNote 2013 [Preview] | `VYNYX-8GPBC-7FQMD-D6B7B-7MDFD` | +| OneNote 2013 | `TGN6P-8MMBC-37P2F-XHXXK-P34VW` | +| Outlook 2013 [Preview] | `X2KNB-FRRG2-WXDPH-739DM-DM9RH` | +| Outlook 2013 | `QPN8Q-BJBTJ-334K3-93TGY-2PMBT` | +| PowerPoint 2013 [Preview] | `B8CT8-BTNFQ-XQXBK-BFWV8-HMDFQ` | +| PowerPoint 2013 | `4NT99-8RJFH-Q2VDH-KYG2C-4RD4F` | +| Publisher 2013 [Preview] | `NB67P-J8XP4-XDK9B-V73VH-M4CKR` | +| Publisher 2013 | `PN2WF-29XG2-T9HJ7-JQPJR-FCXK4` | +| InfoPath 2013 (Preview) | `7KPJJ-N8TT7-CK3KR-QTV98-YPVXQ` | +| InfoPath 2013 | `DKT8B-N7VXH-D963P-Q4PHY-F8894` | +| Lync 2013 [Preview] | `XNVD3-RYC7T-7R6BT-WX6CF-8BYH7` | +| Lync 2013 | `2MG3G-3BNTT-3MFW9-KDQW3-TCK7R` | +| Word 2013 [Preview] | `JBGD4-3JNG7-JWWGV-CR6TP-DC62Q` | +| Word 2013 | `6Q7VD-NX8JD-WJ2VH-88V73-4GBJ7` | +| Mondo 2013 [Preview] | `GCGCN-6FJRM-TR9Q3-BGMWJ-78KQV` | +| Mondo 2013 | `42QTK-RN8M7-J3C4G-BBGYM-88CYV` | +| Mondo 2013 Retail | `?????-?????-?????-?????-?????` | +| SharePoint Workspace (Groove) 2013 [Preview] | `WVCGG-NK4FG-7XKXM-BD4WF-3C624` | +| SharePoint Workspace (Groove) 2013 | `H7R7V-WPNXQ-WCYYC-76BGV-VT7GH` | | SharePoint Designer (Frontpage) 2013 Retail [Preview] | `?????-?????-?????-?????-?????` | -| SharePoint Designer (Frontpage) 2013 Retail | `GYJRG-NMYMF-VGBM4-T3QD4-842DW` | +| SharePoint Designer (Frontpage) 2013 Retail | `GYJRG-NMYMF-VGBM4-T3QD4-842DW` | ### Office 2010 -| Product | GVLK | -| --- | --- | -| Professional Plus 2010 | `VYBBJ-TRJPB-QFQRF-QFT4D-H3GVB` | -| Standard 2010 | `V7QKV-4XVVR-XYV4D-F7DFM-8R6BM` | -| Project Professional 2010 | `YGX6F-PGV49-PGW3J-9BTGG-VHKC6` | -| Project Standard 2010 | `4HP3K-88W3F-W2K3D-6677X-F9PGB` | -| Visio Professional 2010 | `7MCW8-VRQVK-G677T-PDJCM-Q8TCP` | -| Visio Standard 2010 | `767HD-QGMWX-8QTDB-9G3R2-KHFGJ` | -| Visio Premium 2010 | `D9DWC-HPYVV-JGF4P-BTWQB-WX8BJ` | -| Access 2010 | `V7Y44-9T38C-R2VJK-666HK-T7DDX` | -| Excel 2010 | `H62QG-HXVKF-PP4HP-66KMR-CW9BM` | -| OneNote 2010 | `Q4Y4M-RHWJM-PY37F-MTKWH-D3XHX` | -| Outlook 2010 | `7YDC2-CWM8M-RRTJC-8MDVC-X3DWQ` | -| PowerPoint 2010 | `RC8FX-88JRY-3PF7C-X8P67-P4VTT` | -| Publisher 2010 | `BFK7F-9MYHM-V68C7-DRQ66-83YTP` | -| InfoPath 2010 | `K96W8-67RPQ-62T9Y-J8FQJ-BT37T` | -| SharePoint Workspace (Groove) 2010 | `QYYW6-QP4CB-MBV6G-HYMCJ-4T3J4` | -| Word 2010 | `HVHB3-C6FV7-KQX9W-YQG79-CRY7T` | -| Small Business Basics 2010 | `D6QFG-VBYP2-XQHM7-J97RH-VVRCK` | -| Starter 2010 Retail | `VXHHB-W7HBD-7M342-RJ7P8-CHBD6` | +| Product | GVLK | +| ------------------------------------------- | ------------------------------- | +| Professional Plus 2010 | `VYBBJ-TRJPB-QFQRF-QFT4D-H3GVB` | +| Standard 2010 | `V7QKV-4XVVR-XYV4D-F7DFM-8R6BM` | +| Project Professional 2010 | `YGX6F-PGV49-PGW3J-9BTGG-VHKC6` | +| Project Standard 2010 | `4HP3K-88W3F-W2K3D-6677X-F9PGB` | +| Visio Professional 2010 | `7MCW8-VRQVK-G677T-PDJCM-Q8TCP` | +| Visio Standard 2010 | `767HD-QGMWX-8QTDB-9G3R2-KHFGJ` | +| Visio Premium 2010 | `D9DWC-HPYVV-JGF4P-BTWQB-WX8BJ` | +| Access 2010 | `V7Y44-9T38C-R2VJK-666HK-T7DDX` | +| Excel 2010 | `H62QG-HXVKF-PP4HP-66KMR-CW9BM` | +| OneNote 2010 | `Q4Y4M-RHWJM-PY37F-MTKWH-D3XHX` | +| Outlook 2010 | `7YDC2-CWM8M-RRTJC-8MDVC-X3DWQ` | +| PowerPoint 2010 | `RC8FX-88JRY-3PF7C-X8P67-P4VTT` | +| Publisher 2010 | `BFK7F-9MYHM-V68C7-DRQ66-83YTP` | +| InfoPath 2010 | `K96W8-67RPQ-62T9Y-J8FQJ-BT37T` | +| SharePoint Workspace (Groove) 2010 | `QYYW6-QP4CB-MBV6G-HYMCJ-4T3J4` | +| Word 2010 | `HVHB3-C6FV7-KQX9W-YQG79-CRY7T` | +| Small Business Basics 2010 | `D6QFG-VBYP2-XQHM7-J97RH-VVRCK` | +| Starter 2010 Retail | `VXHHB-W7HBD-7M342-RJ7P8-CHBD6` | | SharePoint Designer (Frontpage) 2010 Retail | `H48K6-FB4Y6-P83GH-9J7XG-HDKKX` | -| Office Mondo 1 2010 | `YBJTT-JG6MD-V9Q7P-DBKXJ-38W9R` | -| Office Mondo 2 2010 | `7TC2V-WXF6P-TD7RT-BQRXR-B8K32` | +| Office Mondo 1 2010 | `YBJTT-JG6MD-V9Q7P-DBKXJ-38W9R` | +| Office Mondo 2 2010 | `7TC2V-WXF6P-TD7RT-BQRXR-B8K32` | diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index 0cc4805..3d5efae 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -6,6 +6,7 @@ If you not follow this, do not expect that we can or want to help you! * Are you activating a legit Windows copy checked with `sha256`, `md5` or is it maybe a warez torrent version ? * Did you tried a clean installation (format all) ? You skipped entering any key during installation, turning off internet connection, first activating and then updating Windows (and eventually later upgrading) ? +* Are you activating Windows or Office on a different machine (physical or virtual) where py-kms runs? * Have you installed all latest packages ? Especially before upgrading ? Are you upgrading using the "Update Assistant"/"Media Creation" tool to switch from Windows 7 / 8 / 8.1 to 10 (for me has always worked) ? * If isn't a clean install, so far as you have kept activated your Windows copy ? Have you used some other activator (maybe not trusted) that injects or changes .dll files and therefore may have corrupted something ? * Have you forgot to reactivate at least once before 180 (45 or 30, depending on your version) days ? diff --git a/docs/Usage.md b/docs/Usage.md index 7e27ba9..9635565 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -1,13 +1,13 @@ # Usage ## Start Parameters -*** +(pykms-server-py)= ### pykms_Server.py Follows a list of usable parameters: ip -> Instructs py-kms to listen on _IPADDRESS_ (can be an hostname too). If this option is not specified, _IPADDRESS_ 0.0.0.0 is used. +> Instructs py-kms to listen on _IPADDRESS_ (can be an hostname too). If this option is not specified, _IPADDRESS_ `::` is used. port > Define TCP _PORT_ the KMS service is listening on. Default is 1688. @@ -17,7 +17,7 @@ Follows a list of usable parameters: Use _EPID_ as Windows _EPID_. If no _EPID_ is specified, a random one will be generated. -l or --lcid -> Do not randomize the locale ID part of the _EPID_ and use _LCID_ instead. +> Specify the _LCID_ part of the _EPID_. If an _EPID_ is manually specified, this setting is ignored. Default is 1033 (English - US). The Language Code Identifier (_LCID_) describes localizable information in Windows. This structure is used to identify specific languages for the purpose of customizing software for particular languages and cultures. For example, it can specify the way dates, @@ -26,10 +26,9 @@ The _LCID_ must be specified as a decimal number (example: 1049 for "Russian - R By default py-kms generates a valid locale ID but this may lead to a value which is unlikely to occur in your country. You may want to select the locale ID of your country instead. See [here](https://msdn.microsoft.com/en-us/library/cc233982.aspx) for a list of valid _LCIDs_. -If an _EPID_ is manually specified, this setting is ignored. Default is a fixed _LCID_ of 1033 (English - US). -w or --hwid -> Use specified _HWID_ for all products. +> Use specified _HWID_ for all products. Use `-w RANDOM` to generate a random HWID. Default is random. Hardware Identification is a security measure used by Microsoft upon the activation of the Windows operating system. As part of the Product Activation system, a unique HWID number is generated when the operating system is first installed. The _HWID_ identifies the hardware components that the system @@ -39,8 +38,7 @@ to make sure that the operating system is still running on the same device. If the two _HWID_ numbers differ too much then the operating system will shut down until Microsoft reactivates the product. The theory behind _HWID_ is to ensure that the operating system is not being used on any device other than the one for which it was purchased and registered. -HWID must be an 16-character string of hex characters that are interpreted as a series of 8 bytes (big endian). -Default is _364F463A8863D35F_. To auto generate the _HWID_, type `-w RANDOM`. +HWID must be an 16-character string of hex characters that are interpreted as a series of 8 bytes (big endian). -c or --client-count > Use this flag to specify the current _CLIENTCOUNT_. Default is None. Remember that a number >=25 is @@ -55,7 +53,6 @@ e.g. because it could not reach the server. The default is 120 minutes (2 hours) -s or --sqlite [] > Use this option to store request information from unique clients in an SQLite database. Deactivated by default. -If enabled the default database file is _pykms_database.db_. You can also provide a specific location. -t0 or --timeout-idle > Maximum inactivity time (in seconds) after which the connection with the client is closed. @@ -77,7 +74,7 @@ user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py -V INFO ``` creates _pykms_logserver.log_ with these initial messages: ``` -Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 0.0.0.0 on port 1688. +Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at :: on port 1688. Mon, 12 Jun 2017 22:09:00 INFO HWID: 364F463A8863D35F ``` @@ -85,28 +82,28 @@ Mon, 12 Jun 2017 22:09:00 INFO HWID: 364F463A8863D35F > Creates a _LOGFILE.log_ logging file. The default is named _pykms_logserver.log_. example: ``` -user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 8080 -F ~/path/to/folder/py-kms/newlogfile.log -V INFO -w RANDOM +user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 1688 -F ~/path/to/folder/py-kms/newlogfile.log -V INFO -w RANDOM ``` creates _newlogfile.log_ with these initial messages: ``` -Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 192.168.1.102 on port 8080. +Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 192.168.1.102 on port 1688. Mon, 12 Jun 2017 22:09:00 INFO HWID: 58C4F4E53AE14224 ``` You can also enable other suboptions of `-F` doing what is reported in the following table: -| command | pretty msg | logging msg | logfile | -| --- | --- | --- | --- | -| `-F ` | ON | OFF | ON | -| `-F STDOUT` | OFF | ON | OFF | -| `-F FILESTDOUT ` | OFF | ON | ON | -| `-F STDOUTOFF ` | OFF | OFF | ON | -| `-F FILEOFF` | ON | OFF | OFF | +| command | pretty msg | logging msg | logfile | +| ------------------------- | ---------- | ----------- | ------- | +| `-F ` | ON | OFF | ON | +| `-F STDOUT` | OFF | ON | OFF | +| `-F FILESTDOUT ` | OFF | ON | ON | +| `-F STDOUTOFF ` | OFF | OFF | ON | +| `-F FILEOFF` | ON | OFF | OFF | -S or --logsize > Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default. -##### subparser `connect` +#### subparser `connect` -n or --listen <'IP,PORT'> > Use this option to add multiple listening ip address - port couples. Note the format with the comma between the ip address and the port number. You can use this option more than once. @@ -119,22 +116,22 @@ If placed just after `connect` refers to the main address and all additive coupl > Use this option not to allow binding / listening to the same ip address - port couple specified with `-n`. If placed just after `connect` refers to the main address and all additive couples without `-u` option. Reusing port is activated by default (except when running inside the Windows Sandbox and the current user is `WDAGUtilityAccount`). - -d or --dual -> Use this option to allow listening to an IPv6 address also accepting connections via IPv4. -If used it refers to all addresses (main and additional). Deactivated by default. + -d or --dual +> Allows listening to an IPv6 address while also accepting connections via IPv4. If used, it refers to all addresses (main and additional). Activated by default. Pass in "false" or "true" to disable or enable. -examples (with fictitious addresses and ports): +Examples (with fictitious addresses and ports): -| command | address (main) | backlog (main) | reuse port (main) | address (listen) | backlog (listen) | reuse port (listen) | dualstack (main / listen) | -| --- | --- | --- | --- | --- | --- | --- | --- | -| `python3 pykms_Server.py connect -b 12` | ('0.0.0.0', 1688) | 12 | True | [] | [] | [] | False | -| `python3 pykms_Server.py :: connect -b 12 -u -d` | ('::', 1688) | 12 | False | [] | [] | [] | True | -| `python3 pykms_Server.py connect -n 1.1.1.1,1699 -b 10` | ('0.0.0.0', 1688) | 5 | True | [('1.1.1.1', 1699)] | [10] | [True] | False | -| `python3 pykms_Server.py :: 1655 connect -n 2001:db8:0:200::7,1699 -d -b 10 -n 2.2.2.2,1677 -u` | ('::', 1655) | 5 | True | [('2001:db8:0:200::7', 1699), ('2.2.2.2', 1677)] | [10, 5] | [True, False] | True | -| `python3 pykms_Server.py connect -b 12 -u -n 1.1.1.1,1699 -b 10 -n 2.2.2.2,1677 -b 15` | ('0.0.0.0', 1688) | 12 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [10, 15] | [False, False] | False | -| `python3 pykms_Server.py connect -b 12 -n 1.1.1.1,1699 -u -n 2.2.2.2,1677` | ('0.0.0.0', 1688) | 12 | True | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [12, 12] | [False, True] | False | -| `python3 pykms_Server.py connect -d -u -b 8 -n 1.1.1.1,1699 -n 2.2.2.2,1677 -b 12` | ('0.0.0.0', 1688) | 8 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [8, 12] | [False, False] | True | -| `python3 pykms_Server.py connect -b 11 -u -n ::,1699 -n 2.2.2.2,1677` | ('0.0.0.0', 1688) | 11 | False | [('::', 1699), ('2.2.2.2', 1677)] | [11, 11] | [False, False] | False | +| command | address (main) | backlog (main) | reuse port (main) | address (listen) | backlog (listen) | reuse port (listen) | dualstack (main / listen) | +| ---------------------------------------------------------------------------------------------------- | -------------- | -------------- | ----------------- | ------------------------------------------------ | ---------------- | ------------------- | ------------------------- | +| `python3 pykms_Server.py connect -b 12` | ('::', 1688) | 12 | True | [] | [] | [] | True | +| `python3 pykms_Server.py :: connect -b 12 -u -d yes` | ('::', 1688) | 12 | False | [] | [] | [] | True | +| `python3 pykms_Server.py :: connect -b 12 -u -d false` | ('::', 1688) | 12 | False | [] | [] | [] | False | +| `python3 pykms_Server.py connect -n 1.1.1.1,1699 -b 10` | ('::', 1688) | 5 | True | [('1.1.1.1', 1699)] | [10] | [True] | True | +| `python3 pykms_Server.py :: 1655 connect -n 2001:db8:0:200::7,1699 -d true -b 10 -n 2.2.2.2,1677 -u` | ('::', 1655) | 5 | True | [('2001:db8:0:200::7', 1699), ('2.2.2.2', 1677)] | [10, 5] | [True, False] | True | +| `python3 pykms_Server.py connect -b 12 -u -n 1.1.1.1,1699 -b 10 -n 2.2.2.2,1677 -b 15` | ('::', 1688) | 12 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [10, 15] | [False, False] | True | +| `python3 pykms_Server.py connect -b 12 -n 1.1.1.1,1699 -u -n 2.2.2.2,1677` | ('::', 1688) | 12 | True | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [12, 12] | [False, True] | True | +| `python3 pykms_Server.py connect -d 0 -u -b 8 -n 1.1.1.1,1699 -n 2.2.2.2,1677 -b 12` | ('::', 1688) | 8 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [8, 12] | [False, False] | False | +| `python3 pykms_Server.py connect -b 11 -u -n ::,1699 -n 2.2.2.2,1677` | ('::', 1688) | 11 | False | [('::', 1699), ('2.2.2.2', 1677)] | [11, 11] | [False, False] | True | ### pykms_Client.py If _py-kms_ server doesn't works correctly, you can test it with the KMS client `pykms_Client.py`, running on the same machine where you started `pykms_Server.py`. @@ -145,6 +142,12 @@ user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py -V DEBUG user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG ``` +If you wish to get KMS server from DNS server: (ie perform a DNS resolution on _vlmcs._tcp.domain.tld, if ever there are several answers, only the first one is selected.). Althought that mode is supposed to be specific to devices connect to an Active Directory domain, setting a fully qualified name and a workgroup may help to use that automatic KMS discovery feature. +``` +user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG -F STDOUT -D contoso.com +user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG -F STDOUT -D contoso.com +``` + Or if you want better specify: ``` user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 1688 -V DEBUG @@ -194,12 +197,13 @@ You can enable same _pykms_Server.py_ suboptions of `-F`. -S or --logsize > Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default. +(docker-environment)= ## Docker Environment -This are the currently used `ENV` statements from the Dockerfile(s). For further references what exactly the parameters mean, please see the start parameters for the [server](Usage.html#pykms-server-py). +This are the currently used `ENV` statements from the Dockerfile(s). For further references what exactly the parameters mean, please see the start parameters for the [server](#pykms-server-py). ``` # IP-address -# The IP address to listen on. The default is "0.0.0.0" (all interfaces). -ENV IP 0.0.0.0 +# The IP address to listen on. The default is "::" (all interfaces). +ENV IP :: # TCP-port # The network port to listen on. The default is "1688". @@ -226,15 +230,11 @@ ENV ACTIVATION_INTERVAL 120 # Use this flag to specify the renewal interval (in minutes). Default is 10080 minutes (7 days). ENV RENEWAL_INTERVAL 10080 -# Use SQLITE -# Use this flag to store request information from unique clients in an SQLite database. -ENV SQLITE false - # hwid # Use this flag to specify a HWID. # The HWID must be an 16-character string of hex characters. -# The default is "364F463A8863D35F" or type "RANDOM" to auto generate the HWID. -ENV HWID 364F463A8863D35F +# The default is "RANDOM" to auto-generate the HWID or type a specific value. +ENV HWID RANDOM # log level ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG") # Use this flag to set a Loglevel. The default is "ERROR". @@ -253,7 +253,6 @@ ENV LOGSIZE "" The product asks for a key during installation, so it needs you to enter the GVLK. Then the user can set connection parameters, while KMS server must already be running on server machine. Finally with specific commands, activation occurs automatically and can be extended later every time for another 180 (or 30 or 45) days. ### Windows -*** The `//nologo` option of `cscript` was used only to hide the startup logo. ![win1](img/win1.png) @@ -271,7 +270,6 @@ The `//nologo` option of `cscript` was used only to hide the startup logo. 6. View license informations (optional). ### Office -*** Note that you’ll have to install a volume license (VL) version of Office. Office versions downloaded from MSDN and / or Technet are non-VL. ![off1](img/off1.png) diff --git a/docs/conf.py b/docs/conf.py index 6ec910f..943ebe8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,29 +18,23 @@ # 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. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['recommonmark', 'sphinx_markdown_tables'] +extensions = ['myst_parser', 'sphinx_markdown_tables', 'sphinx_rtd_theme'] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = { - '.rst': 'restructuredtext', - '.md': 'markdown', -} +# templates_path = ['_templates'] # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' @@ -54,19 +48,19 @@ # built documents. # # The short X.Y version. -version = '1.0' +# version = '1.0' # The full version, including alpha/beta/rc tags. -release = '1.0' +# release = '1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -74,109 +68,109 @@ # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- 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 = 'default' +html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # 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'] +# html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'py-kms' @@ -185,43 +179,43 @@ # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'py-kms.tex', u'py-kms Documentation', - u'SystemRage', 'manual'), + ('index', 'py-kms.tex', u'py-kms Documentation', + u'SystemRage', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- @@ -234,7 +228,7 @@ ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -243,19 +237,19 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'py-kms', u'py-kms Documentation', - u'SystemRage', 'py-kms', 'KMS Server Emulator written in Python', - 'Miscellaneous'), + ('index', 'py-kms', u'py-kms Documentation', + u'SystemRage', 'py-kms', 'KMS Server Emulator written in Python', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/docs/img/webinterface.png b/docs/img/webinterface.png new file mode 100644 index 0000000..8a0b36b Binary files /dev/null and b/docs/img/webinterface.png differ diff --git a/docs/requirements.txt b/docs/requirements.txt index ba6fda4..e2becb5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,44 +1,5 @@ -alabaster==0.7.12 -appdirs==1.4.4 -Babel==2.8.0 -CacheControl==0.12.6 -certifi==2020.4.5.1 -chardet==3.0.4 -colorama==0.4.3 -commonmark==0.9.1 -contextlib2==0.6.0 -distlib==0.3.0 -distro==1.5.0 -docutils==0.16 -html5lib==1.0.1 -idna==2.9 -imagesize==1.2.0 -Jinja2==2.11.2 -lockfile==0.12.2 -Markdown==3.2.2 -MarkupSafe==1.1.1 -msgpack==1.0.0 -ordered-set==4.0.1 -packaging==20.4 -pep517==0.8.2 -progress==1.5 -Pygments==2.6.1 -pyparsing==2.4.7 -pytoml==0.1.21 -pytz==2020.1 -recommonmark==0.6.0 -requests==2.23.0 -retrying==1.3.3 -six==1.15.0 -snowballstemmer==2.0.0 -Sphinx==3.1.2 -sphinx-markdown-tables==0.0.15 -sphinxcontrib-applehelp==1.0.2 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==1.0.3 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.4 -toml==0.10.1 -urllib3==1.25.9 -webencodings==0.5.1 +Sphinx~=7.2.6 +sphinx-rtd-theme~=2.0.0 +readthedocs-sphinx-search~=0.3.2 +sphinx-markdown-tables~=0.0.17 +myst-parser~=2.0.0 diff --git a/py-kms/Etrigan.py b/py-kms/Etrigan.py deleted file mode 100644 index 0bb2498..0000000 --- a/py-kms/Etrigan.py +++ /dev/null @@ -1,609 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import atexit -import errno -import os -import sys -import time -import signal -import logging -import argparse -from collections import Sequence - -__version__ = "0.1" -__license__ = "MIT License" -__author__ = u"Matteo ℱan " -__copyright__ = "© Copyright 2020" -__url__ = "https://github.com/SystemRage/Etrigan" -__description__ = "Etrigan: a python daemonizer that rocks." - - -class Etrigan(object): - """ - Daemonizer based on double-fork method - -------------------------------------- - Each option can be passed as a keyword argument or modified by assigning - to an attribute on the instance: - - jasonblood = Etrigan(pidfile, - argument_example_1 = foo, - argument_example_2 = bar) - - that is equivalent to: - - jasonblood = Etrigan(pidfile) - jasonblood.argument_example_1 = foo - jasonblood.argument_example_2 = bar - - Object constructor expects always `pidfile` argument. - `pidfile` - Path to the pidfile. - - The following other options are defined: - `stdin` - `stdout` - `stderr` - :Default: `os.devnull` - File objects used as the new file for the standard I/O streams - `sys.stdin`, `sys.stdout`, and `sys.stderr` respectively. - - `funcs_to_daemonize` - :Default: `[]` - Define a list of your custom functions - which will be executed after daemonization. - If None, you have to subclass Etrigan `run` method. - Note that these functions can return elements that will be - added to Etrigan object (`etrigan_add` list) so the other subsequent - ones can reuse them for further processing. - You only have to provide indexes of `etrigan_add` list, - (an int (example: 2) for single index or a string (example: '1:4') for slices) - as first returning element. - - `want_quit` - :Default: `False` - If `True`, runs Etrigan `quit_on_start` or `quit_on_stop` - lists of your custom functions at the end of `start` or `stop` operations. - These can return elements as `funcs_to_daemonize`. - - `logfile` - :Default: `None` - Path to the output log file. - - `loglevel` - :Default: `None` - Set the log level of logging messages. - - `mute` - :Default: `False` - Disable all stdout and stderr messages (before double forking). - - `pause_loop` - :Default: `None` - Seconds of pause between the calling, in an infinite loop, - of every function in `funcs_to_daemonize` list. - If `-1`, no pause between the calling, in an infinite loop, - of every function in `funcs_to_daemonize` list. - If `None`, only one run (no infinite loop) of functions in - `funcs_to_daemonize` list, without pause. - """ - - def __init__(self, pidfile, - stdin = os.devnull, stdout = os.devnull, stderr = os.devnull, - funcs_to_daemonize = [], want_quit = False, - logfile = None, loglevel = None, - mute = False, pause_loop = None): - - self.pidfile = pidfile - self.funcs_to_daemonize = funcs_to_daemonize - self.stdin = stdin - self.stdout = stdout - self.stderr = stderr - self.logfile = logfile - self.loglevel = loglevel - self.mute = mute - self.want_quit = want_quit - self.pause_loop = pause_loop - # internal only. - self.homedir = '/' - self.umask = 0o22 - self.etrigan_restart, self.etrigan_reload = (False for _ in range(2)) - self.etrigan_alive = True - self.etrigan_add = [] - self.etrigan_index = None - # seconds of pause between stop and start during the restart of the daemon. - self.pause_restart = 5 - # when terminate a process, seconds to wait until kill the process with signal. - # self.pause_kill = 3 - - # create logfile. - self.setup_files() - - def handle_terminate(self, signum, frame): - if os.path.exists(self.pidfile): - self.etrigan_alive = False - # eventually run quit (on stop) function/s. - if self.want_quit: - if not isinstance(self.quit_on_stop, (list, tuple)): - self.quit_on_stop = [self.quit_on_stop] - self.execute(self.quit_on_stop) - # then always run quit standard. - self.quit_standard() - else: - self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: can't find PIDFILE '%s'" %self.pidfile) - sys.exit(0) - - def handle_reload(self, signum, frame): - self.etrigan_reload = True - - def setup_files(self): - self.pidfile = os.path.abspath(self.pidfile) - - if self.logfile is not None: - self.logdaemon = logging.getLogger('logdaemon') - self.logdaemon.setLevel(self.loglevel) - - filehandler = logging.FileHandler(self.logfile) - filehandler.setLevel(self.loglevel) - formatter = logging.Formatter(fmt = '[%(asctime)s] [%(levelname)8s] --- %(message)s', - datefmt = '%Y-%m-%d %H:%M:%S') - filehandler.setFormatter(formatter) - self.logdaemon.addHandler(filehandler) - else: - nullhandler = logging.NullHandler() - self.logdaemon.addHandler(nullhandler) - - def emit_error(self, message, to_exit = True): - """ Print an error message to STDERR. """ - if not self.mute: - sys.stderr.write(message + '\n') - sys.stderr.flush() - if to_exit: - sys.exit(1) - - def emit_message(self, message, to_exit = False): - """ Print a message to STDOUT. """ - if not self.mute: - sys.stdout.write(message + '\n') - sys.stdout.flush() - if to_exit: - sys.exit(0) - - def view(self, logobj, emitobj, msg, **kwargs): - options = {'to_exit' : False, - 'silent' : False - } - options.update(kwargs) - - if logobj: - logobj(msg) - if emitobj: - if not options['silent']: - emitobj(msg, to_exit = options['to_exit']) - - def daemonize(self): - """ - Double-forks the process to daemonize the script. - see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177) - http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 - """ - self.view(self.logdaemon.debug, None, "Attempting to daemonize the process...") - - # First fork. - self.fork(msg = "First fork") - # Decouple from parent environment. - self.detach() - # Second fork. - self.fork(msg = "Second fork") - # Write the PID file. - self.create_pidfile() - self.view(self.logdaemon.info, self.emit_message, "The daemon process has started.") - # Redirect standard file descriptors. - sys.stdout.flush() - sys.stderr.flush() - self.attach('stdin', mode = 'r') - self.attach('stdout', mode = 'a+') - - try: - self.attach('stderr', mode = 'a+', buffering = 0) - except ValueError: - # Python 3 can't have unbuffered text I/O. - self.attach('stderr', mode = 'a+', buffering = 1) - - # Handle signals. - signal.signal(signal.SIGINT, self.handle_terminate) - signal.signal(signal.SIGTERM, self.handle_terminate) - signal.signal(signal.SIGHUP, self.handle_reload) - #signal.signal(signal.SIGKILL....) - - def fork(self, msg): - try: - pid = os.fork() - if pid > 0: - self.view(self.logdaemon.debug, None, msg + " success with PID %d." %pid) - # Exit from parent. - sys.exit(0) - except Exception as e: - msg += " failed: %s." %str(e) - self.view(self.logdaemon.error, self.emit_error, msg) - - def detach(self): - # cd to root for a guarenteed working dir. - try: - os.chdir(self.homedir) - except Exception as e: - msg = "Unable to change working directory: %s." %str(e) - self.view(self.logdaemon.error, self.emit_error, msg) - - # clear the session id to clear the controlling tty. - pid = os.setsid() - if pid == -1: - sys.exit(1) - - # set the umask so we have access to all files created by the daemon. - try: - os.umask(self.umask) - except Exception as e: - msg = "Unable to change file creation mask: %s." %str(e) - self.view(self.logdaemon.error, self.emit_error, msg) - - def attach(self, name, mode, buffering = -1): - with open(getattr(self, name), mode, buffering) as stream: - os.dup2(stream.fileno(), getattr(sys, name).fileno()) - - def checkfile(self, path, typearg, typefile): - filename = os.path.basename(path) - pathname = os.path.dirname(path) - if not os.path.isdir(pathname): - msg = "argument %s: invalid directory: '%s'. Exiting..." %(typearg, pathname) - self.view(self.logdaemon.error, self.emit_error, msg) - elif not filename.lower().endswith(typefile): - msg = "argument %s: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, filename) - self.view(self.logdaemon.error, self.emit_error, msg) - - def create_pidfile(self): - atexit.register(self.delete_pidfile) - pid = os.getpid() - try: - with open(self.pidfile, 'w+') as pf: - pf.write("%s\n" %pid) - self.view(self.logdaemon.debug, None, "PID %d written to '%s'." %(pid, self.pidfile)) - except Exception as e: - msg = "Unable to write PID to PIDFILE '%s': %s" %(self.pidfile, str(e)) - self.view(self.logdaemon.error, self.emit_error, msg) - - def delete_pidfile(self, pid): - # Remove the PID file. - try: - os.remove(self.pidfile) - self.view(self.logdaemon.debug, None, "Removing PIDFILE '%s' with PID %d." %(self.pidfile, pid)) - except Exception as e: - if e.errno != errno.ENOENT: - self.view(self.logdaemon.error, self.emit_error, str(e)) - - def get_pidfile(self): - # Get the PID from the PID file. - if self.pidfile is None: - return None - if not os.path.isfile(self.pidfile): - return None - - try: - with open(self.pidfile, 'r') as pf: - pid = int(pf.read().strip()) - self.view(self.logdaemon.debug, None, "Found PID %d in PIDFILE '%s'" %(pid, self.pidfile)) - except Exception as e: - self.view(self.logdaemon.warning, None, "Empty or broken PIDFILE") - pid = None - - def pid_exists(pid): - # psutil _psposix.py. - if pid == 0: - return True - try: - os.kill(pid, 0) - except OSError as e: - if e.errno == errno.ESRCH: - return False - elif e.errno == errno.EPERM: - return True - else: - self.view(self.logdaemon.error, self.emit_error, str(e)) - else: - return True - - if pid is not None and pid_exists(pid): - return pid - else: - # Remove the stale PID file. - self.delete_pidfile(pid) - return None - - def start(self): - """ Start the daemon. """ - self.view(self.logdaemon.info, self.emit_message, "Starting the daemon process...", silent = self.etrigan_restart) - - # Check for a PID file to see if the Daemon is already running. - pid = self.get_pidfile() - if pid is not None: - msg = "A previous daemon process with PIDFILE '%s' already exists. Daemon already running ?" %self.pidfile - self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False) - return - - # Daemonize the main process. - self.daemonize() - # Start a infinitive loop that periodically runs `funcs_to_daemonize`. - self.loop() - # eventualy run quit (on start) function/s. - if self.want_quit: - if not isinstance(self.quit_on_start, (list, tuple)): - self.quit_on_start = [self.quit_on_start] - self.execute(self.quit_on_start) - - def stop(self): - """ Stop the daemon. """ - self.view(None, self.emit_message, "Stopping the daemon process...", silent = self.etrigan_restart) - - self.logdaemon.disabled = True - pid = self.get_pidfile() - self.logdaemon.disabled = False - if not pid: - # Just to be sure. A ValueError might occur - # if the PIDFILE is empty but does actually exist. - if os.path.exists(self.pidfile): - self.delete_pidfile(pid) - - msg = "Can't find the daemon process with PIDFILE '%s'. Daemon not running ?" %self.pidfile - self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False) - return - - # Try to kill the daemon process. - try: - while True: - os.kill(pid, signal.SIGTERM) - time.sleep(0.1) - except Exception as e: - if (e.errno != errno.ESRCH): - self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: %s" %str(e)) - else: - self.view(None, self.emit_message, "The daemon process has ended correctly.", silent = self.etrigan_restart) - - def restart(self): - """ Restart the daemon. """ - self.view(self.logdaemon.info, self.emit_message, "Restarting the daemon process...") - self.etrigan_restart = True - self.stop() - if self.pause_restart: - time.sleep(self.pause_restart) - self.etrigan_alive = True - self.start() - - def reload(self): - pass - - def status(self): - """ Get status of the daemon. """ - self.view(self.logdaemon.info, self.emit_message, "Viewing the daemon process status...") - - if self.pidfile is None: - self.view(self.logdaemon.error, self.emit_error, "Cannot get the status of daemon without PIDFILE.") - - pid = self.get_pidfile() - if pid is None: - self.view(self.logdaemon.info, self.emit_message, "The daemon process is not running.", to_exit = True) - else: - try: - with open("/proc/%d/status" %pid, 'r') as pf: - pass - self.view(self.logdaemon.info, self.emit_message, "The daemon process is running.", to_exit = True) - except Exception as e: - msg = "There is not a process with the PIDFILE '%s': %s" %(self.pidfile, str(e)) - self.view(self.logdaemon.error, self.emit_error, msg) - - def flatten(self, alistoflists, ltypes = Sequence): - # https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists/2158532#2158532 - alistoflists = list(alistoflists) - while alistoflists: - while alistoflists and isinstance(alistoflists[0], ltypes): - alistoflists[0:1] = alistoflists[0] - if alistoflists: yield alistoflists.pop(0) - - def exclude(self, func): - from inspect import getargspec - args = getargspec(func) - if callable(func): - try: - args[0].pop(0) - except IndexError: - pass - return args - else: - self.view(self.logdaemon.error, self.emit_error, "Not a function.") - return - - def execute(self, some_functions): - returned = None - if isinstance(some_functions, (list, tuple)): - for func in some_functions: - l_req = len(self.exclude(func)[0]) - - if l_req == 0: - returned = func() - else: - l_add = len(self.etrigan_add) - if l_req > l_add: - self.view(self.logdaemon.error, self.emit_error, - "Can't evaluate function: given %s, required %s." %(l_add, l_req)) - return - else: - arguments = self.etrigan_add[self.etrigan_index] - l_args = (len(arguments) if isinstance(arguments, list) else 1) - if (l_args > l_req) or (l_args < l_req): - self.view(self.logdaemon.error, self.emit_error, - "Can't evaluate function: given %s, required %s." %(l_args, l_req)) - return - else: - if isinstance(arguments, list): - returned = func(*arguments) - else: - returned = func(arguments) - - if returned: - if isinstance(returned, (list, tuple)): - if isinstance(returned[0], int): - self.etrigan_index = returned[0] - else: - self.etrigan_index = slice(*map(int, returned[0].split(':'))) - if returned[1:] != []: - self.etrigan_add.append(returned[1:]) - self.etrigan_add = list(self.flatten(self.etrigan_add)) - else: - self.view(self.logdaemon.error, self.emit_error, "Function should return list or tuple.") - returned = None - else: - if some_functions is None: - self.run() - - def loop(self): - try: - if self.pause_loop is None: - # one-shot. - self.execute(self.funcs_to_daemonize) - else: - if self.pause_loop >= 0: - # infinite with pause. - time.sleep(self.pause_loop) - while self.etrigan_alive: - self.execute(self.funcs_to_daemonize) - time.sleep(self.pause_loop) - elif self.pause_loop == -1: - # infinite without pause. - while self.etrigan_alive: - self.execute(self.funcs_to_daemonize) - except Exception as e: - msg = "The daemon process start method failed: %s" %str(e) - self.view(self.logdaemon.error, self.emit_error, msg) - - def quit_standard(self): - self.view(self.logdaemon.info, None, "Stopping the daemon process...") - self.delete_pidfile(self.get_pidfile()) - self.view(self.logdaemon.info, None, "The daemon process has ended correctly.") - - def quit_on_start(self): - """ - Override this method when you subclass Daemon. - """ - self.quit_standard() - - def quit_on_stop(self): - """ - Override this method when you subclass Daemon. - """ - pass - - def run(self): - """ - Override this method when you subclass Daemon. - It will be called after the process has been - daemonized by start() or restart(). - """ - pass - -#----------------------------------------------------------------------------------------------------------------------------------------------------------- - -class JasonBlood(Etrigan): - def run(self): - jasonblood_func() - -def jasonblood_func(): - with open(os.path.join('.', 'etrigan_test.txt'), 'a') as file: - file.write("Yarva Demonicus Etrigan " + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) + '\n') - -def Etrigan_parser(parser = None): - if parser is None: - # create a new parser. - parser = argparse.ArgumentParser(description = __description__, epilog = __version__) - if not parser.add_help: - # create help argument. - parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit") - - # attach to an existent parser. - parser.add_argument("operation", action = "store", choices = ["start", "stop", "restart", "status", "reload"], - help = "Select an operation for daemon.", type = str) - parser.add_argument("--etrigan-pid", - action = "store", dest = "etriganpid", default = "/tmp/etrigan.pid", - help = "Choose a pidfile path. Default is \"/tmp/etrigan.pid\".", type = str) #'/var/run/etrigan.pid' - parser.add_argument("--etrigan-log", - action = "store", dest = "etriganlog", default = os.path.join('.', "etrigan.log"), - help = "Use this option to choose an output log file; for not logging don't select it. Default is \"etrigan.log\".", type = str) - parser.add_argument("--etrigan-lev", - action = "store", dest = "etriganlev", default = "DEBUG", - choices = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"], - help = "Use this option to set a log level. Default is \"DEBUG\".", type = str) - parser.add_argument("--etrigan-mute", - action = "store_const", dest = 'etriganmute', const = True, default = False, - help = "Disable all stdout and stderr messages.") - return parser - -class Etrigan_check(object): - def emit_opt_err(self, msg): - print(msg) - sys.exit(1) - - def checkfile(self, path, typearg, typefile): - filename, extension = os.path.splitext(path) - pathname = os.path.dirname(path) - if not os.path.isdir(pathname): - msg = "argument `%s`: invalid directory: '%s'. Exiting..." %(typearg, pathname) - self.emit_opt_err(msg) - elif not extension == typefile: - msg = "argument `%s`: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, extension) - self.emit_opt_err(msg) - - def checkfunction(self, funcs, booleans): - if not isinstance(funcs, (list, tuple)): - if funcs is not None: - msg = "argument `funcs_to_daemonize`: provide list, tuple or None" - self.emit_opt_err(msg) - - for elem in booleans: - if not type(elem) == bool: - msg = "argument `want_quit`: not a boolean." - self.emit_opt_err(msg) - -def Etrigan_job(type_oper, daemon_obj): - Etrigan_check().checkfunction(daemon_obj.funcs_to_daemonize, - [daemon_obj.want_quit]) - if type_oper == "start": - daemon_obj.start() - elif type_oper == "stop": - daemon_obj.stop() - elif type_oper == "restart": - daemon_obj.restart() - elif type_oper == "status": - daemon_obj.status() - elif type_oper == "reload": - daemon_obj.reload() - sys.exit(0) - -def main(): - # Parse arguments. - parser = Etrigan_parser() - args = vars(parser.parse_args()) - # Check arguments. - Etrigan_check().checkfile(args['etriganpid'], '--etrigan-pid', '.pid') - Etrigan_check().checkfile(args['etriganlog'], '--etrigan-log', '.log') - - # Setup daemon. - jasonblood_1 = Etrigan(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'], - mute = args['etriganmute'], - funcs_to_daemonize = [jasonblood_func], pause_loop = 5) - -## jasonblood_2 = JasonBlood(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'], -## mute = args['etriganmute'], -## funcs_to_daemonize = None, pause_loop = 5) - # Do job. - Etrigan_job(args['operation'], jasonblood_1) - -if __name__ == '__main__': - main() diff --git a/py-kms/KmsDataBase.xml b/py-kms/KmsDataBase.xml index cc454ca..e479ee0 100644 --- a/py-kms/KmsDataBase.xml +++ b/py-kms/KmsDataBase.xml @@ -103,6 +103,14 @@ + + + + + + + + @@ -530,6 +538,10 @@ + + + + @@ -560,6 +572,16 @@ + + + + + + + + + + @@ -573,16 +595,16 @@ - - + + - - - + + + @@ -590,44 +612,44 @@ - - + + - + - + - + - - + + - + - + - + - - + + - - - - - - + + + + + + - - + + @@ -984,6 +1006,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/py-kms/graphics/pykms_Arrow_Left.gif b/py-kms/graphics/pykms_Arrow_Left.gif deleted file mode 100644 index 731405d..0000000 Binary files a/py-kms/graphics/pykms_Arrow_Left.gif and /dev/null differ diff --git a/py-kms/graphics/pykms_Arrow_Right.gif b/py-kms/graphics/pykms_Arrow_Right.gif deleted file mode 100644 index 422583f..0000000 Binary files a/py-kms/graphics/pykms_Arrow_Right.gif and /dev/null differ diff --git a/py-kms/graphics/pykms_Keyhole_Left.gif b/py-kms/graphics/pykms_Keyhole_Left.gif deleted file mode 100644 index 5d2933c..0000000 Binary files a/py-kms/graphics/pykms_Keyhole_Left.gif and /dev/null differ diff --git a/py-kms/graphics/pykms_Keyhole_Right.gif b/py-kms/graphics/pykms_Keyhole_Right.gif deleted file mode 100644 index d7499bb..0000000 Binary files a/py-kms/graphics/pykms_Keyhole_Right.gif and /dev/null differ diff --git a/py-kms/graphics/pykms_Keys.gif b/py-kms/graphics/pykms_Keys.gif deleted file mode 100644 index 540eadc..0000000 Binary files a/py-kms/graphics/pykms_Keys.gif and /dev/null differ diff --git a/py-kms/pykms_Base.py b/py-kms/pykms_Base.py index d70bdfe..e0e9a6b 100644 --- a/py-kms/pykms_Base.py +++ b/py-kms/pykms_Base.py @@ -4,13 +4,12 @@ import logging import time import uuid -import socket from pykms_Structure import Structure from pykms_DB2Dict import kmsDB2Dict from pykms_PidGenerator import epidGenerator from pykms_Filetimes import filetime_to_dt -from pykms_Sql import sql_initialize, sql_update, sql_update_epid +from pykms_Sql import sql_update, sql_update_epid from pykms_Format import justify, byterize, enco, deco, pretty_printer #-------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -119,19 +118,24 @@ def serverLogic(self, kmsRequest): # Localize the request time, if module "tzlocal" is available. try: + from datetime import datetime from tzlocal import get_localzone from pytz.exceptions import UnknownTimeZoneError try: - tz = get_localzone() - local_dt = tz.localize(requestDatetime) + local_dt = datetime.fromisoformat(str(requestDatetime)).astimezone(get_localzone()) except UnknownTimeZoneError: pretty_printer(log_obj = loggersrv.warning, put_text = "{reverse}{yellow}{bold}Unknown time zone ! Request time not localized.{end}") local_dt = requestDatetime except ImportError: pretty_printer(log_obj = loggersrv.warning, - put_text = "{reverse}{yellow}{bold}Module 'tzlocal' not available ! Request time not localized.{end}") + put_text = "{reverse}{yellow}{bold}Module 'tzlocal' or 'pytz' not available ! Request time not localized.{end}") local_dt = requestDatetime + except Exception as e: + # Just in case something else goes wrong + loggersrv.warning('Okay, something went horribly wrong while localizing the request time (proceeding anyways): ' + str(e)) + local_dt = requestDatetime + pass # Activation threshold. # https://docs.microsoft.com/en-us/windows/deployment/volume-activation/activate-windows-10-clients-vamt @@ -161,6 +165,7 @@ def serverLogic(self, kmsRequest): # Get a name for SkuId, AppId. kmsdb = kmsDB2Dict() + appName, skuName = str(applicationId), str(skuId) appitems = kmsdb[2] for appitem in appitems: @@ -208,7 +213,6 @@ def serverLogic(self, kmsRequest): 'product' : infoDict["skuId"]}) # Create database. if self.srv_config['sqlite']: - sql_initialize(self.srv_config['sqlite']) sql_update(self.srv_config['sqlite'], infoDict) return self.createKmsResponse(kmsRequest, currentClientCount, appName) diff --git a/py-kms/pykms_Client.py b/py-kms/pykms_Client.py index bd6d3ec..d2fe1b5 100644 --- a/py-kms/pykms_Client.py +++ b/py-kms/pykms_Client.py @@ -13,7 +13,13 @@ import os import threading -import pykms_RpcBind, pykms_RpcRequest +import dns.message +import dns.rdataclass +import dns.rdatatype +import dns.query +import dns.resolver + +import pykms_RpcBind, pykms_RpcRequest from pykms_Filetimes import dt_to_filetime from pykms_Dcerpc import MSRPCHeader, MSRPCBindNak, MSRPCRequestHeader, MSRPCRespHeader from pykms_Base import kmsBase, UUID @@ -39,10 +45,9 @@ class client_thread(threading.Thread): def __init__(self, name): threading.Thread.__init__(self) self.name = name - self.with_gui = False def run(self): - clt_main(with_gui = self.with_gui) + clt_main() #--------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -50,7 +55,7 @@ def run(self): # 'help' string - 'default' value - 'dest' string. clt_options = { - 'ip' : {'help' : 'The IP address or hostname of the KMS server.', 'def' : "0.0.0.0", 'des' : "ip"}, + 'ip' : {'help' : 'The IP address or hostname of the KMS server.', 'def' : "::", 'des' : "ip"}, 'port' : {'help' : 'The port the KMS service is listening on. The default is \"1688\".', 'def' : 1688, 'des' : "port"}, 'mode' : {'help' : 'Use this flag to manually specify a Microsoft product for testing the server. The default is \"Windows81\"', 'def' : "Windows8.1", 'des' : "mode", @@ -72,6 +77,7 @@ def run(self): Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to create logfile.', 'def' : os.path.join('.', 'pykms_logclient.log'), 'des' : "logfile"}, 'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.', 'def' : 0, 'des': "logsize"}, + 'discovery' : {'help': 'ask the client to perform a _vlmcs._tcp.domain.tld DNS request to set KMS server.', 'def': None , 'des': 'discovery' }, } def client_options(): @@ -99,6 +105,8 @@ def client_options(): default = clt_options['lfile']['def'], help = clt_options['lfile']['help'], type = str) client_parser.add_argument("-S", "--logsize", dest = clt_options['lsize']['des'], action = "store", default = clt_options['lsize']['def'], help = clt_options['lsize']['help'], type = float) + client_parser.add_argument("-D", "--discovery", dest = clt_options['discovery']['des'], action = "store", + default = clt_options['discovery']['def'], help = clt_options['discovery']['help'], type = str) client_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit") @@ -156,18 +164,25 @@ def client_check(): def client_update(): kmsdb = kmsDB2Dict() + loggerclt.debug(f'Searching in kms database for machine "{clt_config["mode"]}"...') + appitems = kmsdb[2] for appitem in appitems: kmsitems = appitem['KmsItems'] for kmsitem in kmsitems: - name = re.sub('\(.*\)', '', kmsitem['DisplayName']).replace('2015', '').replace(' ', '') + name = re.sub('\(.*\)', '', kmsitem['DisplayName']) # Remove bracets + name = name.replace('2015', '') # Remove specific years + name = name.replace(' ', '') # Ignore whitespaces + name = name.replace('/11', '', 1) # Cut out Windows 11, as it is basically Windows 10 if name == clt_config['mode']: skuitems = kmsitem['SkuItems'] # Select 'Enterprise' for Windows or 'Professional Plus' for Office. for skuitem in skuitems: - if skuitem['DisplayName'].replace(' ','') == name + 'Enterprise' or \ - skuitem['DisplayName'].replace(' ','') == name[:6] + 'ProfessionalPlus' + name[6:]: - + sName = skuitem['DisplayName'] + sName = sName.replace(' ', '') # Ignore whitespaces + sName = sName.replace('/11', '', 1) # Cut out Windows 11, as it is basically Windows 10 + if sName == name + 'Enterprise' or \ + sName == name[:6] + 'ProfessionalPlus' + name[6:]: clt_config['KMSClientSkuID'] = skuitem['Id'] clt_config['RequiredClientCount'] = int(kmsitem['NCountPolicy']) clt_config['KMSProtocolMajorVersion'] = int(float(kmsitem['DefaultKmsProtocol'])) @@ -175,9 +190,25 @@ def client_update(): clt_config['KMSClientLicenseStatus'] = 2 clt_config['KMSClientAppID'] = appitem['Id'] clt_config['KMSClientKMSCountedID'] = kmsitem['Id'] - break + return + raise RuntimeError(f'Client failed to find machine configuration in kms database - make sure it contains an entry for "{clt_config["mode"]}"') def client_connect(): + + if clt_config['discovery'] is not None: + loggerclt.info(f'Using Domain: {clt_config["discovery"]}') + r= None + try: + r = dns.resolver.resolve('_vlmcs._tcp.' + clt_config['discovery'], dns.rdatatype.SRV) + for a in r: + loggerclt.debug(f'answer KMS server: {a.target} , port: {a.port}') + clt_config['ip'] = socket.gethostbyname(r[0].target.to_text()) + clt_config['port'] = r[0].port + except (dns.exception.Timeout, dns.resolver.NXDOMAIN) as e: + pretty_printer(log_obj = loggerclt.warning, + put_text = "{reverse}{red}{bold}Cannot resolve '%s'. Error: '%s'...{end}" %(clt_config['discovery'], + str(e))) + loggerclt.info("Connecting to %s on port %d" % (clt_config['ip'], clt_config['port'])) try: clt_sock = socket.create_connection((clt_config['ip'], clt_config['port']), timeout = clt_config['timeoutidle']) @@ -265,11 +296,10 @@ def client_create(clt_sock): pretty_printer(log_obj = loggerclt.warning, to_exit = True, where = "clt", put_text = "{reverse}{magenta}{bold}Something went wrong. Exiting...{end}") -def clt_main(with_gui = False): +def clt_main(): try: - if not with_gui: - # Parse options. - client_options() + # Parse options. + client_options() # Check options. client_check() @@ -361,4 +391,4 @@ def readKmsResponseV6(data): return message if __name__ == "__main__": - clt_main(with_gui = False) + clt_main() diff --git a/py-kms/pykms_Connect.py b/py-kms/pykms_Connect.py index cf8b3dd..69492bc 100644 --- a/py-kms/pykms_Connect.py +++ b/py-kms/pykms_Connect.py @@ -4,6 +4,9 @@ import socket import selectors import ipaddress +import logging +from pykms_Format import pretty_printer +loggersrv = logging.getLogger('logsrv') # https://github.com/python/cpython/blob/master/Lib/socket.py def has_dualstack_ipv6(): @@ -27,12 +30,13 @@ def create_server_sock(address, *, family = socket.AF_INET, backlog = None, reus *family* should be either AF_INET or AF_INET6. *backlog* is the queue size passed to socket.listen(). - *reuse_port* dictates whether to use the SO_REUSEPORT socket option. + *reuse_port* if True and the platform supports it, we will use the SO_REUSEPORT socket option. *dualstack_ipv6* if True and the platform supports it, it will create an AF_INET6 socket able to accept both IPv4 or IPv6 connections; when False it will explicitly disable this option on platforms that enable it by default (e.g. Linux). """ if reuse_port and not hasattr(socket._socket, "SO_REUSEPORT"): - raise ValueError("SO_REUSEPORT not supported on this platform") + pretty_printer(log_obj = loggersrv.warning, put_text = "{reverse}{yellow}{bold}SO_REUSEPORT not supported on this platform - ignoring socket option.{end}") + reuse_port = False if dualstack_ipv6: if not has_dualstack_ipv6(): diff --git a/py-kms/pykms_Format.py b/py-kms/pykms_Format.py index dd968c0..ee37363 100644 --- a/py-kms/pykms_Format.py +++ b/py-kms/pykms_Format.py @@ -274,9 +274,7 @@ def execute(self): ShellMessage.indx += 1 def print_logging_setup(self, logger, async_flag, formatter = logging.Formatter('%(name)s %(message)s')): - from pykms_GuiBase import gui_redirector - stream = gui_redirector(StringIO()) - handler = logging.StreamHandler(stream) + handler = logging.StreamHandler(StringIO()) handler.name = 'LogStream' handler.setLevel(logging.INFO) handler.setFormatter(formatter) @@ -293,9 +291,6 @@ def print_logging_setup(self, logger, async_flag, formatter = logging.Formatter( def print_logging(self, toprint): if (self.nshell and ((0 in self.nshell) or (2 in self.nshell and not ShellMessage.viewclt))) or ShellMessage.indx == 0: - from pykms_GuiBase import gui_redirector_setup, gui_redirector_clear - gui_redirector_setup() - gui_redirector_clear() self.print_logging_setup(ShellMessage.loggersrv_pty, ShellMessage.asyncmsgsrv) self.print_logging_setup(ShellMessage.loggerclt_pty, ShellMessage.asyncmsgclt) @@ -405,7 +400,6 @@ def pretty_printer(**kwargs): if None `put_text` must be defined for printing process. `to_exit ` --> if True system exit is called. `where` --> specifies if message is server-side or client-side - (useful for GUI redirect). """ # Set defaults for not defined options. options = {'log_obj' : None, diff --git a/py-kms/pykms_GuiBase.py b/py-kms/pykms_GuiBase.py deleted file mode 100644 index 30f5dec..0000000 --- a/py-kms/pykms_GuiBase.py +++ /dev/null @@ -1,948 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os -import sys -import threading -from time import sleep -import tkinter as tk -from tkinter import ttk -from tkinter import messagebox -from tkinter import filedialog -import tkinter.font as tkFont - -from pykms_Server import srv_options, srv_version, srv_config, server_terminate, serverqueue, serverthread -from pykms_GuiMisc import ToolTip, TextDoubleScroll, TextRedirect, ListboxOfRadiobuttons -from pykms_GuiMisc import custom_background, custom_pages -from pykms_Client import clt_options, clt_version, clt_config, client_thread - -gui_version = "py-kms_gui_v3.0" -__license__ = "MIT License" -__author__ = u"Matteo ℱan " -__copyright__ = "© Copyright 2020" -__url__ = "https://github.com/SystemRage/py-kms" -gui_description = "A GUI for py-kms." - -##--------------------------------------------------------------------------------------------------------------------------------------------------------- -def get_ip_address(): - if os.name == 'posix': - import subprocess - ip = subprocess.getoutput("hostname -I") - elif os.name == 'nt': - import socket - ip = socket.gethostbyname(socket.gethostname()) - else: - ip = 'Unknown' - return ip - -def gui_redirector(stream, redirect_to = TextRedirect.Pretty, redirect_conditio = True, stderr_side = "srv"): - global txsrv, txclt, txcol - if redirect_conditio: - if stream == 'stdout': - sys.stdout = redirect_to(txsrv, txclt, txcol) - elif stream == 'stderr': - sys.stderr = redirect_to(txsrv, txclt, txcol, stderr_side) - else: - stream = redirect_to(txsrv, txclt, txcol) - return stream - -def gui_redirector_setup(): - TextRedirect.Pretty.tag_num = 0 - TextRedirect.Pretty.newlinecut = [-1, -2, -4, -5] - -def gui_redirector_clear(): - global txsrv, oysrv - try: - if oysrv: - txsrv.configure(state = 'normal') - txsrv.delete('1.0', 'end') - txsrv.configure(state = 'disabled') - except: - # self.onlysrv not defined (menu not used) - pass - -##----------------------------------------------------------------------------------------------------------------------------------------------------------- - -class KmsGui(tk.Tk): - def __init__(self, *args, **kwargs): - tk.Tk.__init__(self, *args, **kwargs) - self.wraplength = 200 - serverthread.with_gui = True - self.validation_int = (self.register(self.validate_int), "%S") - self.validation_float = (self.register(self.validate_float), "%P") - - ## Define fonts and colors. - self.customfonts = {'btn' : tkFont.Font(family = 'Fixedsys', size = 11, weight = 'bold'), - 'oth' : tkFont.Font(family = 'Times', size = 9, weight = 'bold'), - 'opt' : tkFont.Font(family = 'Fixedsys', size = 9, weight = 'bold'), - 'lst' : tkFont.Font(family = 'Fixedsys', size = 8, weight = 'bold', slant = 'italic'), - 'msg' : tkFont.Font(family = 'Monospace', size = 6), # need a monospaced type (like courier, etc..). - } - - self.customcolors = { 'black' : '#000000', - 'white' : '#FFFFFF', - 'green' : '#00EE76', - 'yellow' : '#FFFF00', - 'magenta' : '#CD00CD', - 'orange' : '#FFA500', - 'red' : '#FF4500', - 'blue' : '#1E90FF', - 'cyan' : '#AFEEEE', - 'lavender': '#E6E6FA', - 'brown' : '#A52A2A', - } - - self.option_add('*TCombobox*Listbox.font', self.customfonts['lst']) - - self.gui_create() - - def invert(self, widgets = []): - for widget in widgets: - if widget['state'] == 'normal': - widget.configure(state = 'disabled') - elif widget['state'] == 'disabled': - widget.configure(state = 'normal') - - def gui_menu(self): - self.onlysrv, self.onlyclt = (False for _ in range(2)) - menubar = tk.Menu(self) - prefmenu = tk.Menu(menubar, tearoff = 0, font = ("Noto Sans Regular", 10), borderwidth = 3, relief = 'ridge') - menubar.add_cascade(label = 'Preferences', menu = prefmenu) - prefmenu.add_command(label = 'Enable server-side mode', command = lambda: self.pref_onlysrv(prefmenu)) - prefmenu.add_command(label = 'Enable client-side mode', command = lambda: self.pref_onlyclt(prefmenu)) - self.config(menu = menubar) - - def pref_onlysrv(self, menu): - global oysrv - - if self.onlyclt or serverthread.is_running_server: - return - self.onlysrv = not self.onlysrv - if self.onlysrv: - menu.entryconfigure(0, label = 'Disable server-side mode') - self.clt_on_show(force_remove = True) - else: - menu.entryconfigure(0, label = 'Enable server-side mode') - self.invert(widgets = [self.shbtnclt]) - oysrv = self.onlysrv - - def pref_onlyclt(self, menu): - if self.onlysrv or serverthread.is_running_server: - return - self.onlyclt = not self.onlyclt - if self.onlyclt: - menu.entryconfigure(1, label = 'Disable client-side mode') - if self.shbtnclt['text'] == 'SHOW\nCLIENT': - self.clt_on_show(force_view = True) - self.optsrvwin.grid_remove() - self.msgsrvwin.grid_remove() - gui_redirector('stderr', redirect_to = TextRedirect.Stderr, stderr_side = "clt") - else: - menu.entryconfigure(1, label = 'Enable client-side mode') - self.optsrvwin.grid() - self.msgsrvwin.grid() - gui_redirector('stderr', redirect_to = TextRedirect.Stderr) - - self.invert(widgets = [self.runbtnsrv, self.shbtnclt, self.runbtnclt]) - - def gui_create(self): - ## Create server gui - self.gui_srv() - ## Create client gui + other operations. - self.gui_complete() - ## Create menu. - self.gui_menu() - ## Create globals for printing process (redirect stdout). - global txsrv, txclt, txcol - txsrv = self.textboxsrv.get() - txclt = self.textboxclt.get() - txcol = self.customcolors - ## Redirect stderr. - gui_redirector('stderr', redirect_to = TextRedirect.Stderr) - - def gui_pages_show(self, pagename, side): - # https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter - # https://www.reddit.com/r/learnpython/comments/7xxtsy/trying_to_understand_tkinter_and_how_to_switch/ - pageside = self.pagewidgets[side] - tk.Misc.lift(pageside["PageWin"][pagename], aboveThis = None) - keylist = list(pageside["PageWin"].keys()) - - for elem in [pageside["BtnAni"], pageside["LblAni"]]: - if pagename == "PageStart": - elem["Left"].config(state = "disabled") - if len(keylist) == 2: - elem["Right"].config(state = "normal") - elif pagename == "PageEnd": - elem["Right"].config(state = "disabled") - if len(keylist) == 2: - elem["Left"].config(state = "normal") - else: - for where in ["Left", "Right"]: - elem[where].config(state = "normal") - - if pagename != "PageStart": - page_l = keylist[keylist.index(pagename) - 1] - pageside["BtnAni"]["Left"]['command'] = lambda pag=page_l, pos=side: self.gui_pages_show(pag, pos) - if pagename != "PageEnd": - page_r = keylist[keylist.index(pagename) + 1] - pageside["BtnAni"]["Right"]['command'] = lambda pag=page_r, pos=side: self.gui_pages_show(pag, pos) - - def gui_pages_buttons(self, parent, side): - btnwin = tk.Canvas(parent, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') - btnwin.grid(row = 14, column = 2, padx = 2, pady = 2, sticky = 'nsew') - btnwin.grid_columnconfigure(1, weight = 1) - self.pagewidgets[side]["BtnWin"] = btnwin - - for position in ["Left", "Right"]: - if position == "Left": - col = [0, 0, 1] - stick = 'e' - elif position == "Right": - col = [2, 1, 0] - stick = 'w' - - aniwin = tk.Canvas(btnwin, background = self.customcolors['white'], borderwidth = 0, relief = 'ridge') - aniwin.grid(row = 0, column = col[0], padx = 5, pady = 5, sticky = 'nsew') - self.pagewidgets[side]["AniWin"][position] = aniwin - - lblani = tk.Label(aniwin, width = 1, height = 1) - lblani.grid(row = 0, column = col[1], padx = 2, pady = 2, sticky = stick) - self.pagewidgets[side]["LblAni"][position] = lblani - - btnani = tk.Button(aniwin) - btnani.grid(row = 0, column = col[2], padx = 2, pady = 2, sticky = stick) - self.pagewidgets[side]["BtnAni"][position] = btnani - ## Customize buttons. - custom_pages(self, side) - - def gui_pages_create(self, parent, side, create = {}): - self.pagewidgets.update({side : {"PageWin" : create, - "BtnWin" : None, - "BtnAni" : {"Left" : None, - "Right" : None}, - "AniWin" : {"Left" : None, - "Right" : None}, - "LblAni" : {"Left" : None, - "Right" : None}, - } - }) - - for pagename in self.pagewidgets[side]["PageWin"].keys(): - page = tk.Canvas(parent, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') - self.pagewidgets[side]["PageWin"][pagename] = page - page.grid(row = 0, column = 2, padx = 2, pady = 2, sticky = "nsew") - page.grid_columnconfigure(1, weight = 1) - self.gui_pages_buttons(parent = parent, side = side) - self.gui_pages_show("PageStart", side = side) - - def gui_store(self, side, typewidgets): - stored = [] - for pagename in self.pagewidgets[side]["PageWin"].keys(): - for widget in self.pagewidgets[side]["PageWin"][pagename].winfo_children(): - if widget.winfo_class() in typewidgets: - stored.append(widget) - return stored - - def gui_srv(self): - ## Create main containers. ------------------------------------------------------------------------------------------------------------------ - self.masterwin = tk.Canvas(self, borderwidth = 3, relief = tk.RIDGE) - self.btnsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') - self.optsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') - self.msgsrvwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200) - - ## Layout main containers. - self.masterwin.grid(row = 0, column = 0, sticky = 'nsew') - self.btnsrvwin.grid(row = 0, column = 1, padx = 2, pady = 2, sticky = 'nw') - self.optsrvwin.grid(row = 0, column = 2, padx = 2, pady = 2, sticky = 'nsew') - self.optsrvwin.grid_rowconfigure(0, weight = 1) - self.optsrvwin.grid_columnconfigure(1, weight = 1) - - self.pagewidgets = {} - - ## Subpages of "optsrvwin". - self.gui_pages_create(parent = self.optsrvwin, side = "Srv", create = {"PageStart": None, - "PageEnd": None}) - - ## Continue to grid. - self.msgsrvwin.grid(row = 1, column = 2, padx = 1, pady = 1, sticky = 'nsew') - self.msgsrvwin.grid_propagate(False) - self.msgsrvwin.grid_columnconfigure(0, weight = 1) - self.msgsrvwin.grid_rowconfigure(0, weight = 1) - - ## Create widgets (btnsrvwin) --------------------------------------------------------------------------------------------------------------- - self.statesrv = tk.Label(self.btnsrvwin, text = 'Server\nState:\nStopped', font = self.customfonts['oth'], - foreground = self.customcolors['red']) - self.runbtnsrv = tk.Button(self.btnsrvwin, text = 'START\nSERVER', background = self.customcolors['green'], - foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'], - command = self.srv_on_start) - self.shbtnclt = tk.Button(self.btnsrvwin, text = 'SHOW\nCLIENT', background = self.customcolors['magenta'], - foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'], - command = self.clt_on_show) - self.defaubtnsrv = tk.Button(self.btnsrvwin, text = 'DEFAULTS', background = self.customcolors['brown'], - foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'], - command = self.on_defaults) - self.clearbtnsrv = tk.Button(self.btnsrvwin, text = 'CLEAR', background = self.customcolors['orange'], - foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'], - command = lambda: self.on_clear([txsrv, txclt])) - self.exitbtnsrv = tk.Button(self.btnsrvwin, text = 'EXIT', background = self.customcolors['black'], - foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'], - command = self.on_exit) - - ## Layout widgets (btnsrvwin) - self.statesrv.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew') - self.runbtnsrv.grid(row = 1, column = 0, padx = 2, pady = 2, sticky = 'ew') - self.shbtnclt.grid(row = 2, column = 0, padx = 2, pady = 2, sticky = 'ew') - self.defaubtnsrv.grid(row = 3, column = 0, padx = 2, pady = 2, sticky = 'ew') - self.clearbtnsrv.grid(row = 4, column = 0, padx = 2, pady = 2, sticky = 'ew') - self.exitbtnsrv.grid(row = 5, column = 0, padx = 2, pady = 2, sticky = 'ew') - - ## Create widgets (optsrvwin:Srv:PageWin:PageStart) ----------------------------------------------------------------------------------------- - # Version. - ver = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], - text = 'You are running server version: ' + srv_version, font = self.customfonts['oth'], - foreground = self.customcolors['red']) - # Ip Address. - srvipaddlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'IP Address: ', font = self.customfonts['opt']) - self.srvipadd = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'ip') - self.srvipadd.insert('end', srv_options['ip']['def']) - ToolTip(self.srvipadd, text = srv_options['ip']['help'], wraplength = self.wraplength) - myipadd = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Your IP address is: {}'.format(get_ip_address()), - font = self.customfonts['oth'], foreground = self.customcolors['red']) - # Port. - srvportlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Port: ', font = self.customfonts['opt']) - self.srvport = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'port', - validate = "key", validatecommand = self.validation_int) - self.srvport.insert('end', str(srv_options['port']['def'])) - ToolTip(self.srvport, text = srv_options['port']['help'], wraplength = self.wraplength) - # EPID. - epidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'EPID: ', font = self.customfonts['opt']) - self.epid = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'epid') - self.epid.insert('end', str(srv_options['epid']['def'])) - ToolTip(self.epid, text = srv_options['epid']['help'], wraplength = self.wraplength) - # LCID. - lcidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'LCID: ', font = self.customfonts['opt']) - self.lcid = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lcid', - validate = "key", validatecommand = self.validation_int) - self.lcid.insert('end', str(srv_options['lcid']['def'])) - ToolTip(self.lcid, text = srv_options['lcid']['help'], wraplength = self.wraplength) - # HWID. - hwidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'HWID: ', font = self.customfonts['opt']) - self.hwid = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = (str(srv_options['hwid']['def']), 'RANDOM'), - width = 17, height = 10, font = self.customfonts['lst'], name = 'hwid') - self.hwid.set(str(srv_options['hwid']['def'])) - ToolTip(self.hwid, text = srv_options['hwid']['help'], wraplength = self.wraplength) - # Client Count - countlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Client Count: ', font = self.customfonts['opt']) - self.count = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'count') - self.count.insert('end', str(srv_options['count']['def'])) - ToolTip(self.count, text = srv_options['count']['help'], wraplength = self.wraplength) - # Activation Interval. - activlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Activation Interval: ', font = self.customfonts['opt']) - self.activ = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'activation', - validate = "key", validatecommand = self.validation_int) - self.activ.insert('end', str(srv_options['activation']['def'])) - ToolTip(self.activ, text = srv_options['activation']['help'], wraplength = self.wraplength) - # Renewal Interval. - renewlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Renewal Interval: ', font = self.customfonts['opt']) - self.renew = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'renewal', - validate = "key", validatecommand = self.validation_int) - self.renew.insert('end', str(srv_options['renewal']['def'])) - ToolTip(self.renew, text = srv_options['renewal']['help'], wraplength = self.wraplength) - # Logfile. - srvfilelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logfile Path / Name: ', font = self.customfonts['opt']) - self.srvfile = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lfile') - self.srvfile.insert('end', srv_options['lfile']['def']) - self.srvfile.xview_moveto(1) - ToolTip(self.srvfile, text = srv_options['lfile']['help'], wraplength = self.wraplength) - srvfilebtnwin = tk.Button(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Browse', font = self.customfonts['opt'], - command = lambda: self.on_browse(self.srvfile, srv_options)) - # Loglevel. - srvlevellbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.customfonts['opt']) - self.srvlevel = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = tuple(srv_options['llevel']['choi']), - width = 10, height = 10, font = self.customfonts['lst'], state = "readonly", name = 'llevel') - self.srvlevel.set(srv_options['llevel']['def']) - ToolTip(self.srvlevel, text = srv_options['llevel']['help'], wraplength = self.wraplength) - # Logsize. - srvsizelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.customfonts['opt']) - self.srvsize = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lsize', - validate = "key", validatecommand = self.validation_float) - self.srvsize.insert('end', srv_options['lsize']['def']) - ToolTip(self.srvsize, text = srv_options['lsize']['help'], wraplength = self.wraplength) - # Asynchronous messages. - self.chkvalsrvasy = tk.BooleanVar() - self.chkvalsrvasy.set(srv_options['asyncmsg']['def']) - chksrvasy = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Async\nMsg', - font = self.customfonts['opt'], var = self.chkvalsrvasy, relief = 'groove', name = 'asyncmsg') - ToolTip(chksrvasy, text = srv_options['asyncmsg']['help'], wraplength = self.wraplength) - - # Listbox radiobuttons server. - self.chksrvfile = ListboxOfRadiobuttons(self.pagewidgets["Srv"]["PageWin"]["PageStart"], - ['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'], - self.customfonts['lst'], - changed = [(self.srvfile, srv_options['lfile']['def']), - (srvfilebtnwin, ''), - (self.srvsize, srv_options['lsize']['def']), - (self.srvlevel, srv_options['llevel']['def'])], - width = 10, height = 1, borderwidth = 2, relief = 'ridge') - - ## Layout widgets (optsrvwin:Srv:PageWin:PageStart) - ver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew') - srvipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') - self.srvipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew') - myipadd.grid(row = 2, column = 1, columnspan = 2, padx = 5, pady = 5, sticky = 'ew') - srvportlbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e') - self.srvport.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew') - epidlbl.grid(row = 4, column = 0, padx = 5, pady = 5, sticky = 'e') - self.epid.grid(row = 4, column = 1, padx = 5, pady = 5, sticky = 'ew') - lcidlbl.grid(row = 5, column = 0, padx = 5, pady = 5, sticky = 'e') - self.lcid.grid(row = 5, column = 1, padx = 5, pady = 5, sticky = 'ew') - hwidlbl.grid(row = 6, column = 0, padx = 5, pady = 5, sticky = 'e') - self.hwid.grid(row = 6, column = 1, padx = 5, pady = 5, sticky = 'ew') - countlbl.grid(row = 7, column = 0, padx = 5, pady = 5, sticky = 'e') - self.count.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew') - activlbl.grid(row = 8, column = 0, padx = 5, pady = 5, sticky = 'e') - self.activ.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew') - renewlbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e') - self.renew.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew') - srvfilelbl.grid(row = 10, column = 0, padx = 5, pady = 5, sticky = 'e') - self.srvfile.grid(row = 10, column = 1, padx = 5, pady = 5, sticky = 'ew') - srvfilebtnwin.grid(row = 10, column = 2, padx = 5, pady = 5, sticky = 'ew') - self.chksrvfile.grid(row = 11, column = 1, padx = 5, pady = 5, sticky = 'ew') - chksrvasy.grid(row = 11, column = 2, padx = 5, pady = 5, sticky = 'ew') - srvlevellbl.grid(row = 12, column = 0, padx = 5, pady = 5, sticky = 'e') - self.srvlevel.grid(row = 12, column = 1, padx = 5, pady = 5, sticky = 'ew') - srvsizelbl.grid(row = 13, column = 0, padx = 5, pady = 5, sticky = 'e') - self.srvsize.grid(row = 13, column = 1, padx = 5, pady = 5, sticky = 'ew') - - ## Create widgets (optsrvwin:Srv:PageWin:PageEnd)------------------------------------------------------------------------------------------- - # Timeout connection. - srvtimeout0lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.customfonts['opt']) - self.srvtimeout0 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time0') - self.srvtimeout0.insert('end', str(srv_options['time0']['def'])) - ToolTip(self.srvtimeout0, text = srv_options['time0']['help'], wraplength = self.wraplength) - # Timeout send/recv. - srvtimeout1lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout send-recv: ', font = self.customfonts['opt']) - self.srvtimeout1 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time1') - self.srvtimeout1.insert('end', str(srv_options['time1']['def'])) - ToolTip(self.srvtimeout1, text = srv_options['time1']['help'], wraplength = self.wraplength) - # Sqlite database. - self.chkvalsql = tk.BooleanVar() - self.chkvalsql.set(srv_options['sql']['def']) - self.chkfilesql = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'sql') - self.chkfilesql.insert('end', srv_options['sql']['file']) - self.chkfilesql.xview_moveto(1) - self.chkfilesql.configure(state = 'disabled') - - chksql = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Create Sqlite\nDatabase', - font = self.customfonts['opt'], var = self.chkvalsql, relief = 'groove', - command = lambda: self.sql_status()) - ToolTip(chksql, text = srv_options['sql']['help'], wraplength = self.wraplength) - - ## Layout widgets (optsrvwin:Srv:PageWin:PageEnd) - # a label for vertical aligning with PageStart - tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 0, - height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') - srvtimeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') - self.srvtimeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w') - srvtimeout1lbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e') - self.srvtimeout1.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w') - chksql.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e') - self.chkfilesql.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'w') - - # Store server-side widgets. - self.storewidgets_srv = self.gui_store(side = "Srv", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton']) - self.storewidgets_srv.append(self.chksrvfile) - - ## Create widgets and layout (msgsrvwin) --------------------------------------------------------------------------------------------------- - self.textboxsrv = TextDoubleScroll(self.msgsrvwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled', - relief = 'ridge', font = self.customfonts['msg']) - self.textboxsrv.put() - - def sql_status(self): - if self.chkvalsql.get(): - self.chkfilesql.configure(state = 'normal') - else: - self.chkfilesql.insert('end', srv_options['sql']['file']) - self.chkfilesql.xview_moveto(1) - self.chkfilesql.configure(state = 'disabled') - - def always_centered(self, geo, centered, refs): - x = (self.winfo_screenwidth() // 2) - (self.winfo_width() // 2) - y = (self.winfo_screenheight() // 2) - (self.winfo_height() // 2) - w, h, dx, dy = geo.split('+')[0].split('x') + geo.split('+')[1:] - - if w == refs[1]: - if centered: - self.geometry('+%d+%d' %(x, y)) - centered = False - elif w == refs[0]: - if not centered: - self.geometry('+%d+%d' %(x, y)) - centered = True - - if dx != str(x) or dy != str(y): - self.geometry('+%d+%d' %(x, 0)) - - self.after(200, self.always_centered, self.geometry(), centered, refs) - - def gui_complete(self): - ## Create client widgets (optcltwin, msgcltwin, btncltwin) - self.update_idletasks() # update Gui to get btnsrvwin values --> btncltwin. - minw, minh = self.winfo_width(), self.winfo_height() - self.iconify() - self.gui_clt() - maxw, minh = self.winfo_width(), self.winfo_height() - ## Main window custom background. - self.update_idletasks() # update Gui for custom background - self.iconify() - custom_background(self) - ## Main window other modifications. - self.eval('tk::PlaceWindow %s center' %self.winfo_pathname(self.winfo_id())) - self.wm_attributes("-topmost", True) - self.protocol("WM_DELETE_WINDOW", lambda: 0) - ## Disable maximize button. - self.resizable(False, False) - ## Centered window. - self.always_centered(self.geometry(), False, [minw, maxw]) - - def get_position(self, widget): - x, y = (widget.winfo_x(), widget.winfo_y()) - w, h = (widget.winfo_width(), widget.winfo_height()) - return x, y, w, h - - def gui_clt(self): - self.count_clear, self.keep_clear = (0, '0.0') - self.optcltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') - self.msgcltwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200) - self.btncltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') - - xb, yb, wb, hb = self.get_position(self.btnsrvwin) - self.btncltwin_X = xb - self.btncltwin_Y = yb + hb + 6 - self.btncltwin.place(x = self.btncltwin_X, y = self.btncltwin_Y, bordermode = 'outside', anchor = 'center') - - self.optcltwin.grid(row = 0, column = 4, padx = 2, pady = 2, sticky = 'nsew') - self.optcltwin.grid_rowconfigure(0, weight = 1) - self.optcltwin.grid_columnconfigure(1, weight = 1) - - ## Subpages of "optcltwin". - self.gui_pages_create(parent = self.optcltwin, side = "Clt", create = {"PageStart": None, - "PageEnd": None}) - - ## Continue to grid. - self.msgcltwin.grid(row = 1, column = 4, padx = 1, pady = 1, sticky = 'nsew') - self.msgcltwin.grid_propagate(False) - self.msgcltwin.grid_columnconfigure(0, weight = 1) - self.msgcltwin.grid_rowconfigure(0, weight = 1) - - ## Create widgets (btncltwin) ---------------------------------------------------------------------------------------------------------------- - self.runbtnclt = tk.Button(self.btncltwin, text = 'START\nCLIENT', background = self.customcolors['blue'], - foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'], - state = 'disabled', command = self.clt_on_start, width = 8, height = 2) - - ## Layout widgets (btncltwin) - self.runbtnclt.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew') - - ## Create widgets (optcltwin:Clt:PageWin:PageStart) ------------------------------------------------------------------------------------------ - # Version. - cltver = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'You are running client version: ' + clt_version, - font = self.customfonts['oth'], foreground = self.customcolors['red']) - # Ip Address. - cltipaddlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'IP Address: ', font = self.customfonts['opt']) - self.cltipadd = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'ip') - self.cltipadd.insert('end', clt_options['ip']['def']) - ToolTip(self.cltipadd, text = clt_options['ip']['help'], wraplength = self.wraplength) - # Port. - cltportlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Port: ', font = self.customfonts['opt']) - self.cltport = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'port', - validate = "key", validatecommand = self.validation_int) - self.cltport.insert('end', str(clt_options['port']['def'])) - ToolTip(self.cltport, text = clt_options['port']['help'], wraplength = self.wraplength) - # Mode. - cltmodelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Mode: ', font = self.customfonts['opt']) - self.cltmode = ttk.Combobox(self.pagewidgets["Clt"]["PageWin"]["PageStart"], values = tuple(clt_options['mode']['choi']), - width = 17, height = 10, font = self.customfonts['lst'], state = "readonly", name = 'mode') - self.cltmode.set(clt_options['mode']['def']) - ToolTip(self.cltmode, text = clt_options['mode']['help'], wraplength = self.wraplength) - # CMID. - cltcmidlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'CMID: ', font = self.customfonts['opt']) - self.cltcmid = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'cmid') - self.cltcmid.insert('end', str(clt_options['cmid']['def'])) - ToolTip(self.cltcmid, text = clt_options['cmid']['help'], wraplength = self.wraplength) - # Machine Name. - cltnamelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Machine Name: ', font = self.customfonts['opt']) - self.cltname = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'name') - self.cltname.insert('end', str(clt_options['name']['def'])) - ToolTip(self.cltname, text = clt_options['name']['help'], wraplength = self.wraplength) - # Logfile. - cltfilelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logfile Path / Name: ', font = self.customfonts['opt']) - self.cltfile = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lfile') - self.cltfile.insert('end', clt_options['lfile']['def']) - self.cltfile.xview_moveto(1) - ToolTip(self.cltfile, text = clt_options['lfile']['help'], wraplength = self.wraplength) - cltfilebtnwin = tk.Button(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Browse', font = self.customfonts['opt'], - command = lambda: self.on_browse(self.cltfile, clt_options)) - # Loglevel. - cltlevellbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.customfonts['opt']) - self.cltlevel = ttk.Combobox(self.pagewidgets["Clt"]["PageWin"]["PageStart"], values = tuple(clt_options['llevel']['choi']), - width = 10, height = 10, font = self.customfonts['lst'], state = "readonly", name = 'llevel') - self.cltlevel.set(clt_options['llevel']['def']) - ToolTip(self.cltlevel, text = clt_options['llevel']['help'], wraplength = self.wraplength) - # Logsize. - cltsizelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.customfonts['opt']) - self.cltsize = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lsize', - validate = "key", validatecommand = self.validation_float) - self.cltsize.insert('end', clt_options['lsize']['def']) - ToolTip(self.cltsize, text = clt_options['lsize']['help'], wraplength = self.wraplength) - # Asynchronous messages. - self.chkvalcltasy = tk.BooleanVar() - self.chkvalcltasy.set(clt_options['asyncmsg']['def']) - chkcltasy = tk.Checkbutton(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Async\nMsg', - font = self.customfonts['opt'], var = self.chkvalcltasy, relief = 'groove', name = 'asyncmsg') - ToolTip(chkcltasy, text = clt_options['asyncmsg']['help'], wraplength = self.wraplength) - - # Listbox radiobuttons client. - self.chkcltfile = ListboxOfRadiobuttons(self.pagewidgets["Clt"]["PageWin"]["PageStart"], - ['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'], - self.customfonts['lst'], - changed = [(self.cltfile, clt_options['lfile']['def']), - (cltfilebtnwin, ''), - (self.cltsize, clt_options['lsize']['def']), - (self.cltlevel, clt_options['llevel']['def'])], - width = 10, height = 1, borderwidth = 2, relief = 'ridge') - - ## Layout widgets (optcltwin:Clt:PageWin:PageStart) - cltver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew') - cltipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') - self.cltipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew') - cltportlbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e') - self.cltport.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'ew') - cltmodelbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e') - self.cltmode.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew') - cltcmidlbl.grid(row = 4, column = 0, padx = 5, pady = 5, sticky = 'e') - self.cltcmid.grid(row = 4, column = 1, padx = 5, pady = 5, sticky = 'ew') - cltnamelbl.grid(row = 5, column = 0, padx = 5, pady = 5, sticky = 'e') - self.cltname.grid(row = 5, column = 1, padx = 5, pady = 5, sticky = 'ew') - cltfilelbl.grid(row = 6, column = 0, padx = 5, pady = 5, sticky = 'e') - self.cltfile.grid(row = 6, column = 1, padx = 5, pady = 5, sticky = 'ew') - cltfilebtnwin.grid(row = 6, column = 2, padx = 5, pady = 5, sticky = 'ew') - self.chkcltfile.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew') - chkcltasy.grid(row = 7, column = 2, padx = 5, pady = 5, sticky = 'ew') - cltlevellbl.grid(row = 8, column = 0, padx = 5, pady = 5, sticky = 'e') - self.cltlevel.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew') - cltsizelbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e') - self.cltsize.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew') - - # ugly fix when client-side mode is activated. - templbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], - bg = self.customcolors['lavender']).grid(row = 10, column = 0, - padx = 35, pady = 54, sticky = 'e') - - ## Create widgets (optcltwin:Clt:PageWin:PageEnd) ------------------------------------------------------------------------------------------- - # Timeout connection. - clttimeout0lbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.customfonts['opt']) - self.clttimeout0 = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time0') - self.clttimeout0.insert('end', str(clt_options['time0']['def'])) - ToolTip(self.clttimeout0, text = clt_options['time0']['help'], wraplength = self.wraplength) - # Timeout send/recv. - clttimeout1lbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], text = 'Timeout send-recv: ', font = self.customfonts['opt']) - self.clttimeout1 = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time1') - self.clttimeout1.insert('end', str(clt_options['time1']['def'])) - ToolTip(self.clttimeout1, text = clt_options['time1']['help'], wraplength = self.wraplength) - - ## Layout widgets (optcltwin:Clt:PageWin:PageEnd) - # a label for vertical aligning with PageStart - tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 0, - height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') - clttimeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') - self.clttimeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w') - clttimeout1lbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e') - self.clttimeout1.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w') - - ## Store client-side widgets. - self.storewidgets_clt = self.gui_store(side = "Clt", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton']) - self.storewidgets_clt.append(self.chkcltfile) - - ## Create widgets and layout (msgcltwin) ----------------------------------------------------------------------------------------------------- - self.textboxclt = TextDoubleScroll(self.msgcltwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled', - relief = 'ridge', font = self.customfonts['msg']) - self.textboxclt.put() - - def prep_option(self, value): - try: - # is an INT - return int(value) - except (TypeError, ValueError): - try: - # is a FLOAT - return float(value) - except (TypeError, ValueError): - # is a STRING. - return value - - def prep_logfile(self, filepath, status): - # FILE (pretty on, log view off, logfile yes) - # FILEOFF (pretty on, log view off, no logfile) - # STDOUT (pretty off, log view on, no logfile) - # STDOUTOFF (pretty off, log view off, logfile yes) - # FILESTDOUT (pretty off, log view on, logfile yes) - - if status == 'FILE': - return filepath - elif status in ['FILESTDOUT', 'STDOUTOFF']: - return [status, filepath] - elif status in ['STDOUT', 'FILEOFF']: - return status - - def validate_int(self, value): - return value == "" or value.isdigit() - - def validate_float(self, value): - if value == "": - return True - try: - float(value) - return True - except ValueError: - return False - - def clt_on_show(self, force_remove = False, force_view = False): - if self.optcltwin.winfo_ismapped() or force_remove: - self.shbtnclt.configure(text = 'SHOW\nCLIENT', relief = 'raised') - self.optcltwin.grid_remove() - self.msgcltwin.grid_remove() - self.btncltwin.place_forget() - elif not self.optcltwin.winfo_ismapped() or force_view: - self.shbtnclt.configure(text = 'HIDE\nCLIENT', relief = 'sunken') - self.optcltwin.grid() - self.msgcltwin.grid() - self.btncltwin.place(x = self.btncltwin_X, y = self.btncltwin_Y, bordermode = 'inside', anchor = 'nw') - - def srv_on_start(self): - if self.runbtnsrv['text'] == 'START\nSERVER': - self.on_clear([txsrv, txclt]) - self.srv_actions_start() - # wait for switch. - while not serverthread.is_running_server: - pass - - self.srv_toggle_all(on_start = True) - # run thread for interrupting server when an error happens. - self.srv_eject_thread = threading.Thread(target = self.srv_eject, name = "Thread-SrvEjt") - self.srv_eject_thread.setDaemon(True) - self.srv_eject_thread.start() - - elif self.runbtnsrv['text'] == 'STOP\nSERVER': - serverthread.terminate_eject() - - def srv_eject(self): - while not serverthread.eject: - sleep(0.1) - self.srv_actions_stop() - - def srv_actions_start(self): - srv_config[srv_options['ip']['des']] = self.srvipadd.get() - srv_config[srv_options['port']['des']] = self.prep_option(self.srvport.get()) - srv_config[srv_options['epid']['des']] = self.epid.get() - srv_config[srv_options['lcid']['des']] = self.prep_option(self.lcid.get()) - srv_config[srv_options['hwid']['des']] = self.hwid.get() - srv_config[srv_options['count']['des']] = self.prep_option(self.count.get()) - srv_config[srv_options['activation']['des']] = self.prep_option(self.activ.get()) - srv_config[srv_options['renewal']['des']] = self.prep_option(self.renew.get()) - srv_config[srv_options['lfile']['des']] = self.prep_logfile(self.srvfile.get(), self.chksrvfile.state()) - srv_config[srv_options['asyncmsg']['des']] = self.chkvalsrvasy.get() - srv_config[srv_options['llevel']['des']] = self.srvlevel.get() - srv_config[srv_options['lsize']['des']] = self.prep_option(self.srvsize.get()) - - srv_config[srv_options['time0']['des']] = self.prep_option(self.srvtimeout0.get()) - srv_config[srv_options['time1']['des']] = self.prep_option(self.srvtimeout1.get()) - srv_config[srv_options['sql']['des']] = (self.chkfilesql.get() if self.chkvalsql.get() else self.chkvalsql.get()) - - ## Redirect stdout. - gui_redirector('stdout', redirect_to = TextRedirect.Log, - redirect_conditio = (srv_config[srv_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT'])) - serverqueue.put('start') - - def srv_actions_stop(self): - if serverthread.is_running_server: - if serverthread.server is not None: - server_terminate(serverthread, exit_server = True) - # wait for switch. - while serverthread.is_running_server: - pass - else: - serverthread.is_running_server = False - self.srv_toggle_all(on_start = False) - self.count_clear, self.keep_clear = (0, '0.0') - - def srv_toggle_all(self, on_start = True): - self.srv_toggle_state() - if on_start: - self.runbtnsrv.configure(text = 'STOP\nSERVER', background = self.customcolors['red'], - foreground = self.customcolors['white'], relief = 'sunken') - for widget in self.storewidgets_srv: - widget.configure(state = 'disabled') - self.runbtnclt.configure(state = 'normal') - else: - self.runbtnsrv.configure(text = 'START\nSERVER', background = self.customcolors['green'], - foreground = self.customcolors['white'], relief = 'raised') - for widget in self.storewidgets_srv: - widget.configure(state = 'normal') - if isinstance(widget, ListboxOfRadiobuttons): - widget.change() - self.runbtnclt.configure(state = 'disabled') - - def srv_toggle_state(self): - if serverthread.is_running_server: - txt, color = ('Server\nState:\nServing', self.customcolors['green']) - else: - txt, color = ('Server\nState:\nStopped', self.customcolors['red']) - - self.statesrv.configure(text = txt, foreground = color) - - def clt_on_start(self): - if self.onlyclt: - self.on_clear([txclt]) - else: - rng, add_newline = self.on_clear_setup() - self.on_clear([txsrv, txclt], clear_range = [rng, None], newline_list = [add_newline, False]) - - self.runbtnclt.configure(relief = 'sunken') - self.clt_actions_start() - # run thread for disabling interrupt server and client, when client running. - self.clt_eject_thread = threading.Thread(target = self.clt_eject, name = "Thread-CltEjt") - self.clt_eject_thread.setDaemon(True) - self.clt_eject_thread.start() - - for widget in self.storewidgets_clt + [self.runbtnsrv, self.runbtnclt, self.defaubtnsrv]: - widget.configure(state = 'disabled') - self.runbtnclt.configure(relief = 'raised') - - def clt_actions_start(self): - clt_config[clt_options['ip']['des']] = self.cltipadd.get() - clt_config[clt_options['port']['des']] = self.prep_option(self.cltport.get()) - clt_config[clt_options['mode']['des']] = self.cltmode.get() - clt_config[clt_options['cmid']['des']] = self.cltcmid.get() - clt_config[clt_options['name']['des']] = self.cltname.get() - clt_config[clt_options['lfile']['des']] = self.prep_logfile(self.cltfile.get(), self.chkcltfile.state()) - clt_config[clt_options['asyncmsg']['des']] = self.chkvalcltasy.get() - clt_config[clt_options['llevel']['des']] = self.cltlevel.get() - clt_config[clt_options['lsize']['des']] = self.prep_option(self.cltsize.get()) - - clt_config[clt_options['time0']['des']] = self.prep_option(self.clttimeout0.get()) - clt_config[clt_options['time1']['des']] = self.prep_option(self.clttimeout1.get()) - - ## Redirect stdout. - gui_redirector('stdout', redirect_to = TextRedirect.Log, - redirect_conditio = (clt_config[clt_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT'])) - - # run client (in a thread). - self.clientthread = client_thread(name = "Thread-Clt") - self.clientthread.setDaemon(True) - self.clientthread.with_gui = True - self.clientthread.start() - - def clt_eject(self): - while self.clientthread.is_alive(): - sleep(0.1) - - widgets = self.storewidgets_clt + [self.runbtnclt] + [self.defaubtnsrv] - if not self.onlyclt: - widgets += [self.runbtnsrv] - - for widget in widgets: - if isinstance(widget, ttk.Combobox): - widget.configure(state = 'readonly') - else: - widget.configure(state = 'normal') - if isinstance(widget, ListboxOfRadiobuttons): - widget.change() - - def on_browse(self, entrywidget, options): - path = filedialog.askdirectory() - if os.path.isdir(path): - entrywidget.delete('0', 'end') - entrywidget.insert('end', path + os.sep + os.path.basename(options['lfile']['def'])) - - def on_exit(self): - if serverthread.is_running_server: - if serverthread.server is not None: - server_terminate(serverthread, exit_server = True) - else: - serverthread.is_running_server = False - server_terminate(serverthread, exit_thread = True) - self.destroy() - - def on_clear_setup(self): - if any(opt in ['STDOUT', 'FILESTDOUT'] for opt in srv_config[srv_options['lfile']['des']]): - add_newline = True - if self.count_clear == 0: - self.keep_clear = txsrv.index('end-1c') - else: - add_newline = False - if self.count_clear == 0: - self.keep_clear = txsrv.index('end') - - rng = [self.keep_clear, 'end'] - self.count_clear += 1 - - return rng, add_newline - - def on_clear(self, widget_list, clear_range = None, newline_list = []): - if newline_list == []: - newline_list = len(widget_list) * [False] - - for num, couple in enumerate(zip(widget_list, newline_list)): - widget, add_n = couple - try: - ini, fin = clear_range[num] - except TypeError: - ini, fin = '1.0', 'end' - - widget.configure(state = 'normal') - widget.delete(ini, fin) - if add_n: - widget.insert('end', '\n') - widget.configure(state = 'disabled') - - def on_defaults(self): - - def put_defaults(widgets, chkasy, listofradio, options): - for widget in widgets: - wclass, wname = widget.winfo_class(), widget.winfo_name() - if wname == '!checkbutton': - continue - - opt = options[wname]['def'] - if wclass == 'Entry': - widget.delete(0, 'end') - if wname == 'sql': - self.chkvalsql.set(opt) - self.sql_status() - else: - widget.insert('end', (opt if isinstance(opt, str) else str(opt))) - elif wclass == 'Checkbutton': - if wname == 'asyncmsg': - chkasy.set(opt) - elif wclass == 'TCombobox': - widget.set(str(opt)) - - # ListboxOfRadiobuttons default. - listofradio.radiovar.set('FILE') - listofradio.textbox.yview_moveto(0) - listofradio.change() - - if self.runbtnsrv['text'] == 'START\nSERVER': - apply_default = zip(["Srv", "Clt"], - [self.chkvalsrvasy, self.chkvalcltasy], - [self.chksrvfile, self.chkcltfile], - [srv_options, clt_options]) - elif self.runbtnsrv['text'] == 'STOP\nSERVER': - apply_default = zip(*[("Clt",), - (self.chkvalcltasy,), - (self.chkcltfile,), - (clt_options,)]) - - for side, chkasy, listofradio, options in apply_default: - widgets = self.gui_store(side = side, typewidgets = ['Entry', 'TCombobox', 'Checkbutton']) - put_defaults(widgets, chkasy, listofradio, options) diff --git a/py-kms/pykms_GuiMisc.py b/py-kms/pykms_GuiMisc.py deleted file mode 100644 index 97b1cc1..0000000 --- a/py-kms/pykms_GuiMisc.py +++ /dev/null @@ -1,517 +0,0 @@ -#!/usr/bin/env python3 - -import os -import re -import sys -from collections import Counter -from time import sleep -import threading -import tkinter as tk -from tkinter import ttk -import tkinter.font as tkFont - -from pykms_Format import MsgMap, unshell_message, unformat_message - -#------------------------------------------------------------------------------------------------------------------------------------------------------------ - -# https://stackoverflow.com/questions/3221956/how-do-i-display-tooltips-in-tkinter -class ToolTip(object): - """ Create a tooltip for a given widget """ - def __init__(self, widget, bg = '#FFFFEA', pad = (5, 3, 5, 3), text = 'widget info', waittime = 400, wraplength = 250): - self.waittime = waittime # ms - self.wraplength = wraplength # pixels - self.widget = widget - self.text = text - self.widget.bind("", self.onEnter) - self.widget.bind("", self.onLeave) - self.widget.bind("", self.onLeave) - self.bg = bg - self.pad = pad - self.id = None - self.tw = None - - def onEnter(self, event = None): - self.schedule() - - def onLeave(self, event = None): - self.unschedule() - self.hide() - - def schedule(self): - self.unschedule() - self.id = self.widget.after(self.waittime, self.show) - - def unschedule(self): - id_ = self.id - self.id = None - if id_: - self.widget.after_cancel(id_) - - def show(self): - def tip_pos_calculator(widget, label, tip_delta = (10, 5), pad = (5, 3, 5, 3)): - w = widget - s_width, s_height = w.winfo_screenwidth(), w.winfo_screenheight() - width, height = (pad[0] + label.winfo_reqwidth() + pad[2], - pad[1] + label.winfo_reqheight() + pad[3]) - mouse_x, mouse_y = w.winfo_pointerxy() - x1, y1 = mouse_x + tip_delta[0], mouse_y + tip_delta[1] - x2, y2 = x1 + width, y1 + height - - x_delta = x2 - s_width - if x_delta < 0: - x_delta = 0 - y_delta = y2 - s_height - if y_delta < 0: - y_delta = 0 - - offscreen = (x_delta, y_delta) != (0, 0) - - if offscreen: - if x_delta: - x1 = mouse_x - tip_delta[0] - width - if y_delta: - y1 = mouse_y - tip_delta[1] - height - - offscreen_again = y1 < 0 # out on the top - - if offscreen_again: - # No further checks will be done. - - # TIP: - # A further mod might automagically augment the - # wraplength when the tooltip is too high to be - # kept inside the screen. - y1 = 0 - - return x1, y1 - - bg = self.bg - pad = self.pad - widget = self.widget - - # creates a toplevel window - self.tw = tk.Toplevel(widget) - - # leaves only the label and removes the app window - self.tw.wm_overrideredirect(True) - - win = tk.Frame(self.tw, background = bg, borderwidth = 0) - label = ttk.Label(win, text = self.text, justify = tk.LEFT, background = bg, relief = tk.SOLID, borderwidth = 0, - wraplength = self.wraplength) - label.grid(padx = (pad[0], pad[2]), pady = (pad[1], pad[3]), sticky=tk.NSEW) - win.grid() - - x, y = tip_pos_calculator(widget, label) - - self.tw.wm_geometry("+%d+%d" % (x, y)) - - def hide(self): - tw = self.tw - if tw: - tw.destroy() - self.tw = None - -##----------------------------------------------------------------------------------------------------------------------------------------------------------- - -class TextRedirect(object): - class Pretty(object): - grpmsg = unformat_message([MsgMap[1], MsgMap[7], MsgMap[12], MsgMap[20]]) - arrows = [ item[0] for item in grpmsg ] - clt_msg_nonewline = [ item[1] for item in grpmsg ] - arrows = list(set(arrows)) - lenarrow = len(arrows[0]) - srv_msg_nonewline = [ item[0] for item in unformat_message([MsgMap[2], MsgMap[5], MsgMap[13], MsgMap[18]]) ] - msg_align = [ msg[0].replace('\t', '').replace('\n', '') for msg in unformat_message([MsgMap[-2], MsgMap[-4]]) ] - - def __init__(self, srv_text_space, clt_text_space, customcolors): - self.srv_text_space = srv_text_space - self.clt_text_space = clt_text_space - self.customcolors = customcolors - - def textbox_write(self, tag, message, color, extras): - widget = self.textbox_choose(message) - self.w_maxpix, self.h_maxpix = widget.winfo_width(), widget.winfo_height() - self.xfont = tkFont.Font(font = widget['font']) - widget.configure(state = 'normal') - widget.insert('end', self.textbox_format(message), tag) - self.textbox_color(tag, widget, color, self.customcolors['black'], extras) - widget.after(100, widget.see('end')) - widget.configure(state = 'disabled') - - def textbox_choose(self, message): - if any(item.startswith('logsrv') for item in [message, self.str_to_print]): - self.srv_text_space.focus_set() - self.where = "srv" - return self.srv_text_space - elif any(item.startswith('logclt') for item in [message, self.str_to_print]): - self.clt_text_space.focus_set() - self.where = "clt" - return self.clt_text_space - - def textbox_color(self, tag, widget, forecolor = 'white', backcolor = 'black', extras = []): - for extra in extras: - if extra == 'bold': - self.xfont.configure(weight = "bold") - elif extra == 'italic': - self.xfont.configure(slant = "italic") - elif extra == 'underlined': - self.xfont.text_font.configure(underline = True) - elif extra == 'strike': - self.xfont.configure(overstrike = True) - elif extra == 'reverse': - forecolor, backcolor = backcolor, forecolor - - widget.tag_configure(tag, foreground = forecolor, background = backcolor, font = self.xfont) - widget.tag_add(tag, "insert linestart", "insert lineend") - - def textbox_newline(self, message): - if not message.endswith('\n'): - return message + '\n' - else: - return message - - def textbox_format(self, message): - # vertical align. - self.w_maxpix = self.w_maxpix - 5 # pixel reduction for distance from border. - w_fontpix, h_fontpix = (self.xfont.measure('0'), self.xfont.metrics('linespace')) - msg_unformat = message.replace('\t', '').replace('\n', '') - lenfixed_chars = int((self.w_maxpix / w_fontpix) - len(msg_unformat)) - - if message in self.srv_msg_nonewline + self.clt_msg_nonewline: - lung = lenfixed_chars - self.lenarrow - if message in self.clt_msg_nonewline: - message = self.textbox_newline(message) - else: - lung = lenfixed_chars - if (self.where == "srv") or (self.where == "clt" and message not in self.arrows): - message = self.textbox_newline(message) - # horizontal align. - if msg_unformat in self.msg_align: - msg_strip = message.lstrip('\n') - message = '\n' * (len(message) - len(msg_strip) + TextRedirect.Pretty.newlinecut[0]) + msg_strip - TextRedirect.Pretty.newlinecut.pop(0) - - count = Counter(message) - countab = (count['\t'] if count['\t'] != 0 else 1) - message = message.replace('\t' * countab, ' ' * lung) - return message - - def textbox_do(self): - msgs, TextRedirect.Pretty.tag_num = unshell_message(self.str_to_print, TextRedirect.Pretty.tag_num) - for tag in msgs: - self.textbox_write(tag, msgs[tag]['text'], self.customcolors[msgs[tag]['color']], msgs[tag]['extra']) - - def flush(self): - pass - - def write(self, string): - if string != '\n': - self.str_to_print = string - self.textbox_do() - - class Stderr(Pretty): - def __init__(self, srv_text_space, clt_text_space, customcolors, side): - self.srv_text_space = srv_text_space - self.clt_text_space = clt_text_space - self.customcolors = customcolors - self.side = side - self.tag_err = 'STDERR' - self.xfont = tkFont.Font(font = self.srv_text_space['font']) - - def textbox_choose(self, message): - if self.side == "srv": - return self.srv_text_space - elif self.side == "clt": - return self.clt_text_space - - def write(self, string): - widget = self.textbox_choose(string) - self.textbox_color(self.tag_err, widget, self.customcolors['red'], self.customcolors['black']) - self.srv_text_space.configure(state = 'normal') - self.srv_text_space.insert('end', string, self.tag_err) - self.srv_text_space.see('end') - self.srv_text_space.configure(state = 'disabled') - - class Log(Pretty): - def textbox_format(self, message): - if message.startswith('logsrv'): - message = message.replace('logsrv ', '') - if message.startswith('logclt'): - message = message.replace('logclt ', '') - return message + '\n' - -##----------------------------------------------------------------------------------------------------------------------------------------------------------- -class TextDoubleScroll(tk.Frame): - def __init__(self, master, **kwargs): - """ Initialize. - - horizontal scrollbar - - vertical scrollbar - - text widget - """ - tk.Frame.__init__(self, master) - self.master = master - - self.textbox = tk.Text(self.master, **kwargs) - self.sizegrip = ttk.Sizegrip(self.master) - self.hs = ttk.Scrollbar(self.master, orient = "horizontal", command = self.on_scrollbar_x) - self.vs = ttk.Scrollbar(self.master, orient = "vertical", command = self.on_scrollbar_y) - self.textbox.configure(yscrollcommand = self.on_textscroll, xscrollcommand = self.hs.set) - - def on_scrollbar_x(self, *args): - """ Horizontally scrolls text widget. """ - self.textbox.xview(*args) - - def on_scrollbar_y(self, *args): - """ Vertically scrolls text widget. """ - self.textbox.yview(*args) - - def on_textscroll(self, *args): - """ Moves the scrollbar and scrolls text widget when the mousewheel is moved on a text widget. """ - self.vs.set(*args) - self.on_scrollbar_y('moveto', args[0]) - - def put(self, **kwargs): - """ Grid the scrollbars and textbox correctly. """ - self.textbox.grid(row = 0, column = 0, padx = 3, pady = 3, sticky = "nsew") - self.vs.grid(row = 0, column = 1, sticky = "ns") - self.hs.grid(row = 1, column = 0, sticky = "we") - self.sizegrip.grid(row = 1, column = 1, sticky = "news") - - def get(self): - """ Return the "frame" useful to place inner controls. """ - return self.textbox - -##----------------------------------------------------------------------------------------------------------------------------------------------------------- -def custom_background(window): - # first level canvas. - allwidgets = window.grid_slaves(0,0)[0].grid_slaves() + window.grid_slaves(0,0)[0].place_slaves() - widgets_alphalow = [ widget for widget in allwidgets if widget.winfo_class() == 'Canvas'] - widgets_alphahigh = [] - # sub-level canvas. - for side in ["Srv", "Clt"]: - widgets_alphahigh.append(window.pagewidgets[side]["BtnWin"]) - for position in ["Left", "Right"]: - widgets_alphahigh.append(window.pagewidgets[side]["AniWin"][position]) - for pagename in window.pagewidgets[side]["PageWin"].keys(): - widgets_alphalow.append(window.pagewidgets[side]["PageWin"][pagename]) - - try: - from PIL import Image, ImageTk - - # Open Image. - img = Image.open(os.path.dirname(os.path.abspath( __file__ )) + "/graphics/pykms_Keys.gif") - img = img.convert('RGBA') - # Resize image. - img.resize((window.winfo_width(), window.winfo_height()), Image.ANTIALIAS) - # Put semi-transparent background chunks. - window.backcrops_alphalow, window.backcrops_alphahigh = ([] for _ in range(2)) - - def cutter(master, image, widgets, crops, alpha): - for widget in widgets: - x, y, w, h = master.get_position(widget) - cropped = image.crop((x, y, x + w, y + h)) - cropped.putalpha(alpha) - crops.append(ImageTk.PhotoImage(cropped)) - # Not in same loop to prevent reference garbage. - for crop, widget in zip(crops, widgets): - widget.create_image(1, 1, image = crop, anchor = 'nw') - - cutter(window, img, widgets_alphalow, window.backcrops_alphalow, 36) - cutter(window, img, widgets_alphahigh, window.backcrops_alphahigh, 96) - - # Put semi-transparent background overall. - img.putalpha(128) - window.backimg = ImageTk.PhotoImage(img) - window.masterwin.create_image(1, 1, image = window.backimg, anchor = 'nw') - - except ImportError: - for widget in widgets_alphalow + widgets_alphahigh: - widget.configure(background = window.customcolors['lavender']) - - # Hide client. - window.clt_on_show(force_remove = True) - # Show Gui. - window.deiconify() - -##----------------------------------------------------------------------------------------------------------------------------------------------------------- -class Animation(object): - def __init__(self, gifpath, master, widget, loop = False): - from PIL import Image, ImageTk, ImageSequence - - self.master = master - self.widget = widget - self.loop = loop - self.cancelid = None - self.flagstop = False - self.index = 0 - self.frames = [] - - img = Image.open(gifpath) - size = img.size - for frame in ImageSequence.Iterator(img): - static_img = ImageTk.PhotoImage(frame.convert('RGBA')) - try: - static_img.delay = int(frame.info['duration']) - except KeyError: - static_img.delay = 100 - self.frames.append(static_img) - - self.widget.configure(width = size[0], height = size[1]) - self.initialize() - - def initialize(self): - self.widget.configure(image = self.frames[0]) - self.widget.image = self.frames[0] - - def deanimate(self): - while not self.flagstop: - pass - self.flagstop = False - self.index = 0 - self.widget.configure(relief = "raised") - - def animate(self): - frame = self.frames[self.index] - self.widget.configure(image = frame, relief = "sunken") - self.index += 1 - self.cancelid = self.master.after(frame.delay, self.animate) - if self.index == len(self.frames): - if self.loop: - self.index = 0 - else: - self.stop() - - def start(self, event = None): - if str(self.widget['state']) != 'disabled': - if self.cancelid is None: - if not self.loop: - self.btnani_thread = threading.Thread(target = self.deanimate, name = "Thread-BtnAni") - self.btnani_thread.setDaemon(True) - self.btnani_thread.start() - self.cancelid = self.master.after(self.frames[0].delay, self.animate) - - def stop(self, event = None): - if self.cancelid: - self.master.after_cancel(self.cancelid) - self.cancelid = None - self.flagstop = True - self.initialize() - - -def custom_pages(window, side): - buttons = window.pagewidgets[side]["BtnAni"] - labels = window.pagewidgets[side]["LblAni"] - - for position in buttons.keys(): - buttons[position].config(anchor = "center", - font = window.customfonts['btn'], - background = window.customcolors['white'], - activebackground = window.customcolors['white'], - borderwidth = 2) - - try: - anibtn = Animation(os.path.dirname(os.path.abspath( __file__ )) + "/graphics/pykms_Keyhole_%s.gif" %position, - window, buttons[position], loop = False) - anilbl = Animation(os.path.dirname(os.path.abspath( __file__ )) + "/graphics/pykms_Arrow_%s.gif" %position, - window, labels[position], loop = True) - - def animationwait(master, button, btn_animation, lbl_animation): - while btn_animation.cancelid: - pass - sleep(1) - x, y = master.winfo_pointerxy() - if master.winfo_containing(x, y) == button: - lbl_animation.start() - - def animationcombo(master, button, btn_animation, lbl_animation): - wait_thread = threading.Thread(target = animationwait, - args = (master, button, btn_animation, lbl_animation), - name = "Thread-WaitAni") - wait_thread.setDaemon(True) - wait_thread.start() - lbl_animation.stop() - btn_animation.start() - - buttons[position].bind("", lambda event, anim1 = anibtn, anim2 = anilbl, - bt = buttons[position], win = window: - animationcombo(win, bt, anim1, anim2)) - buttons[position].bind("", anilbl.start) - buttons[position].bind("", anilbl.stop) - - except ImportError: - buttons[position].config(activebackground = window.customcolors['blue'], - foreground = window.customcolors['blue']) - labels[position].config(background = window.customcolors['lavender']) - - if position == "Left": - buttons[position].config(text = '<<') - elif position == "Right": - buttons[position].config(text = '>>') - -##----------------------------------------------------------------------------------------------------------------------------------------------------------- -class ListboxOfRadiobuttons(tk.Frame): - def __init__(self, master, radios, font, changed, **kwargs): - tk.Frame.__init__(self, master) - - self.master = master - self.radios = radios - self.font = font - self.changed = changed - - self.scrollv = tk.Scrollbar(self, orient = "vertical") - self.textbox = tk.Text(self, yscrollcommand = self.scrollv.set, **kwargs) - self.scrollv.config(command = self.textbox.yview) - # layout. - self.scrollv.pack(side = "right", fill = "y") - self.textbox.pack(side = "left", fill = "both", expand = True) - # create radiobuttons. - self.radiovar = tk.StringVar() - self.radiovar.set('FILE') - self.create() - - def create(self): - self.rdbtns = [] - for n, nameradio in enumerate(self.radios): - rdbtn = tk.Radiobutton(self, text = nameradio, value = nameradio, variable = self.radiovar, - font = self.font, indicatoron = 0, width = 15, - borderwidth = 3, selectcolor = 'yellow', command = self.change) - self.textbox.window_create("end", window = rdbtn) - # to force one checkbox per line - if n != len(self.radios) - 1: - self.textbox.insert("end", "\n") - self.rdbtns.append(rdbtn) - self.textbox.configure(state = "disabled") - - def change(self): - st = self.state() - for widget, default in self.changed: - wclass = widget.winfo_class() - if st in ['STDOUT', 'FILEOFF']: - if wclass == 'Entry': - widget.delete(0, 'end') - widget.configure(state = "disabled") - elif wclass == 'TCombobox': - if st == 'STDOUT': - widget.set(default) - widget.configure(state = "readonly") - elif st == 'FILEOFF': - widget.set('') - widget.configure(state = "disabled") - elif st in ['FILE', 'FILESTDOUT', 'STDOUTOFF']: - if wclass == 'Entry': - widget.configure(state = "normal") - widget.delete(0, 'end') - widget.insert('end', default) - widget.xview_moveto(1) - elif wclass == 'TCombobox': - widget.configure(state = "readonly") - widget.set(default) - elif wclass == 'Button': - widget.configure(state = "normal") - - def configure(self, state): - for rb in self.rdbtns: - rb.configure(state = state) - - def state(self): - return self.radiovar.get() diff --git a/py-kms/pykms_Misc.py b/py-kms/pykms_Misc.py index 5769fc9..c96f094 100644 --- a/py-kms/pykms_Misc.py +++ b/py-kms/pykms_Misc.py @@ -194,9 +194,6 @@ def logger_create(log_obj, config, mode = 'a'): frmt_name = '%(name)s ' from pykms_Server import serverthread - if serverthread.with_gui: - frmt_std = frmt_name + frmt_std - frmt_min = frmt_name + frmt_min def apply_formatter(levelnum, formats, handler, color = False): levelformdict = {} @@ -227,7 +224,7 @@ def apply_formatter(levelnum, formats, handler, color = False): log_obj.setLevel(config['loglevel']) #------------------------------------------------------------------------------------------------------------------------------------------------------------ -def check_dir(path, where, log_obj = None, argument = '-F/--logfile', typefile = '.log'): +def check_dir(path, where, log_obj = None, argument = '-F/--logfile'): filename = os.path.basename(path) pathname = os.path.dirname(path) extension = os.path.splitext(filename)[1] @@ -243,9 +240,6 @@ def check_dir(path, where, log_obj = None, argument = '-F/--logfile', typefile = pathname = filename pretty_printer(log_obj = log_obj, where = where, to_exit = True, put_text = msg_dir %(argument, pathname)) - elif not extension.lower() == typefile: - pretty_printer(log_obj = log_obj, where = where, to_exit = True, - put_text = msg_fil %(argument, typefile, extension)) def check_logfile(optionlog, defaultlog, where): if not isinstance(optionlog, list): @@ -524,7 +518,7 @@ def check_setup(config, options, logger, where): # Check logfile. config['logfile'] = check_logfile(config['logfile'], options['lfile']['def'], where = where) - # Check logsize (py-kms Gui). + # Check logsize if config['logsize'] == "": if any(opt in ['STDOUT', 'FILEOFF'] for opt in config['logfile']): # set a recognized size never used. @@ -533,7 +527,7 @@ def check_setup(config, options, logger, where): pretty_printer(put_text = "{reverse}{red}{bold}argument `-S/--logsize`: invalid with: '%s'. Exiting...{end}" %config['logsize'], where = where, to_exit = True) - # Check loglevel (py-kms Gui). + # Check loglevel if config['loglevel'] == "": # set a recognized level never used. config['loglevel'] = 'ERROR' diff --git a/py-kms/pykms_Server.py b/py-kms/pykms_Server.py index 301661b..9298c4a 100755 --- a/py-kms/pykms_Server.py +++ b/py-kms/pykms_Server.py @@ -9,23 +9,20 @@ import logging import os import threading -import pickle import socketserver import queue as Queue import selectors -from getpass import getuser -from tempfile import gettempdir from time import monotonic as time import pykms_RpcBind, pykms_RpcRequest from pykms_RpcBase import rpcBase from pykms_Dcerpc import MSRPCHeader -from pykms_Misc import check_setup, check_lcid, check_dir, check_other +from pykms_Misc import check_setup, check_lcid, check_other from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals, kms_parser_check_connect from pykms_Format import enco, deco, pretty_printer, justify -from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job from pykms_Connect import MultipleListener +from pykms_Sql import sql_initialize srv_version = "py-kms_2020-10-01" __license__ = "The Unlicense" @@ -136,7 +133,8 @@ def __init__(self, queue, name): self.name = name self.queue = queue self.server = None - self.is_running_server, self.with_gui, self.checked = [False for _ in range(3)] + self.is_running_server = False + self.checked = False self.is_running_thread = threading.Event() def terminate_serve(self): @@ -171,21 +169,25 @@ def run(self): self.server.pykms_serve() except (SystemExit, Exception) as e: self.eject = True - if not self.with_gui: - raise - else: - if isinstance(e, SystemExit): - continue - else: - raise + raise ##--------------------------------------------------------------------------------------------------------------------------------------------------------- loggersrv = logging.getLogger('logsrv') +def _str2bool(v): + if isinstance(v, bool): + return v + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise ValueError('Boolean value expected.') + # 'help' string - 'default' value - 'dest' string. srv_options = { - 'ip' : {'help' : 'The IP address (IPv4 or IPv6) to listen on. The default is \"0.0.0.0\" (all interfaces).', 'def' : "0.0.0.0", 'des' : "ip"}, + 'ip' : {'help' : 'The IP address (IPv4 or IPv6) to listen on. The default is \"::\" (all interfaces).', 'def' : "::", 'des' : "ip"}, 'port' : {'help' : 'The network port to listen on. The default is \"1688\".', 'def' : 1688, 'des' : "port"}, 'epid' : {'help' : 'Use this option to manually specify an ePID to use. If no ePID is specified, a random ePID will be auto generated.', 'def' : None, 'des' : "epid"}, @@ -197,19 +199,18 @@ def run(self): 'def' : 120, 'des': "activation"}, 'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).', 'def' : 1440 * 7, 'des' : "renewal"}, - 'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default. \ -If enabled the default .db file is \"pykms_database.db\". You can also provide a specific location.', 'def' : False, + 'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default.', 'def' : False, 'file': os.path.join('.', 'pykms_database.db'), 'des' : "sqlite"}, 'hwid' : {'help' : 'Use this option to specify a HWID. The HWID must be an 16-character string of hex characters. \ -The default is \"364F463A8863D35F\" or type \"RANDOM\" to auto generate the HWID.', - 'def' : "364F463A8863D35F", 'des' : "hwid"}, +Type \"RANDOM\" to auto-generate the HWID.', + 'def' : "RANDOM", 'des' : "hwid"}, 'time0' : {'help' : 'Maximum inactivity time (in seconds) after which the connection with the client is closed. If \"None\" (default) serve forever.', 'def' : None, 'des' : "timeoutidle"}, 'time1' : {'help' : 'Set the maximum time to wait for sending / receiving a request / response. Default is no timeout.', 'def' : None, 'des' : "timeoutsndrcv"}, 'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Deactivated by default.', 'def' : False, 'des' : "asyncmsg"}, - 'llevel' : {'help' : 'Use this option to set a log level. The default is \"ERROR\".', 'def' : "ERROR", 'des' : "loglevel", + 'llevel' : {'help' : 'Use this option to set a log level. The default is \"WARNING\".', 'def' : "WARNING", 'des' : "loglevel", 'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MININFO"]}, 'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logserver.log\". \ Type \"STDOUT\" to view log info on stdout. Type \"FILESTDOUT\" to combine previous actions. \ @@ -220,8 +221,8 @@ def run(self): 'backlog' : {'help' : 'Specifies the maximum length of the queue of pending connections. Default is \"5\".', 'def' : 5, 'des': "backlog"}, 'reuse' : {'help' : 'Do not allows binding / listening to the same address and port. Reusing port is activated by default.', 'def' : True, 'des': "reuse"}, - 'dual' : {'help' : 'Allows listening to an IPv6 address also accepting connections via IPv4. Deactivated by default.', - 'def' : False, 'des': "dual"} + 'dual' : {'help' : 'Allows listening to an IPv6 address while also accepting connections via IPv4. If used, it refers to all addresses (main and additional). Activated by default. Pass in "false" or "true" to disable or enable.', + 'def' : True, 'des': "dual"} } def server_options(): @@ -257,15 +258,6 @@ def server_options(): server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit") - ## Daemon (Etrigan) parsing. - daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False) - daemon_subparser = daemon_parser.add_subparsers(dest = "mode") - - etrigan_parser = daemon_subparser.add_parser("etrigan", add_help = False) - etrigan_parser.add_argument("-g", "--gui", action = "store_const", dest = 'gui', const = True, default = False, - help = "Enable py-kms GUI usage.") - etrigan_parser = Etrigan_parser(parser = etrigan_parser) - ## Connection parsing. connection_parser = KmsParser(description = "connect options", add_help = False) connection_subparser = connection_parser.add_subparsers(dest = "mode") @@ -277,7 +269,7 @@ def server_options(): help = srv_options['backlog']['help'], type = int) connect_parser.add_argument("-u", "--no-reuse", action = "append_const", dest = srv_options['reuse']['des'], const = False, default = [], help = srv_options['reuse']['help']) - connect_parser.add_argument("-d", "--dual", action = "store_true", dest = srv_options['dual']['des'], default = srv_options['dual']['def'], + connect_parser.add_argument("-d", "--dual", type = _str2bool, dest = srv_options['dual']['des'], default = srv_options['dual']['def'], help = srv_options['dual']['help']) try: @@ -285,16 +277,14 @@ def server_options(): # Run help. if any(arg in ["-h", "--help"] for arg in userarg): - KmsParserHelp().printer(parsers = [server_parser, (daemon_parser, etrigan_parser), - (connection_parser, connect_parser)]) + KmsParserHelp().printer(parsers = [server_parser, (connection_parser, connect_parser)]) # Get stored arguments. pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser) - etrigan_zeroarg, etrigan_onearg = kms_parser_get(etrigan_parser) connect_zeroarg, connect_onearg = kms_parser_get(connect_parser) - subdict = {'etrigan' : (etrigan_zeroarg, etrigan_onearg, daemon_parser.parse_args), - 'connect' : (connect_zeroarg, connect_onearg, connection_parser.parse_args) - } + subdict = { + 'connect' : (connect_zeroarg, connect_onearg, connection_parser.parse_args) + } subpars = list(subdict.keys()) pykmssrv_zeroarg += subpars # add subparsers @@ -310,14 +300,7 @@ def server_options(): if subindx: # Set `daemon options` and/or `connect options` for server dict config. # example cases: - # 1 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] etrigan daemon_positional [--daemon_optionals] \ - # connect [--connect_optionals] - # - # 2 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals] etrigan \ - # daemon_positional [--daemon_optionals] - # - # 3 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] etrigan daemon_positional [--daemon_optionals] - # 4 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals] + # 1 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals] first = subindx[0][0] # initial. kms_parser_check_optionals(userarg[0 : first], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms) @@ -339,7 +322,7 @@ def server_options(): else: # Update `pykms options` for server dict config. # example case: - # 5 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] + # 2 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] kms_parser_check_optionals(userarg, pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms) kms_parser_check_positionals(srv_config, server_parser.parse_args) @@ -348,63 +331,6 @@ def server_options(): except KmsParserException as e: pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True) -class Etrigan_Check(Etrigan_check): - def emit_opt_err(self, msg): - pretty_printer(put_text = "{reverse}{red}{bold}%s{end}" %msg, to_exit = True) - -class Etrigan(Etrigan): - def emit_message(self, message, to_exit = False): - if not self.mute: - pretty_printer(put_text = "{reverse}{green}{bold}%s{end}" %message) - if to_exit: - sys.exit(0) - - def emit_error(self, message, to_exit = True): - if not self.mute: - pretty_printer(put_text = "{reverse}{red}{bold}%s{end}" %message, to_exit = True) - -def server_daemon(): - if 'etrigan' in srv_config.values(): - path = os.path.join(gettempdir(), 'pykms_config.pickle') - - if srv_config['operation'] in ['stop', 'restart', 'status'] and len(sys.argv[1:]) > 2: - pretty_printer(put_text = "{reverse}{red}{bold}too much arguments with etrigan '%s'. Exiting...{end}" %srv_config['operation'], - to_exit = True) - - # Check file arguments. - Etrigan_Check().checkfile(srv_config['etriganpid'], '--etrigan-pid', '.pid') - Etrigan_Check().checkfile(srv_config['etriganlog'], '--etrigan-log', '.log') - - if srv_config['gui']: - pass - else: - if srv_config['operation'] == 'start': - with open(path, 'wb') as file: - pickle.dump(srv_config, file, protocol = pickle.HIGHEST_PROTOCOL) - elif srv_config['operation'] in ['stop', 'status', 'restart']: - with open(path, 'rb') as file: - old_srv_config = pickle.load(file) - old_srv_config = {x: old_srv_config[x] for x in old_srv_config if x not in ['operation']} - srv_config.update(old_srv_config) - - serverdaemon = Etrigan(srv_config['etriganpid'], - logfile = srv_config['etriganlog'], loglevel = srv_config['etriganlev'], - mute = srv_config['etriganmute'], pause_loop = None) - - if srv_config['operation'] in ['start', 'restart']: - serverdaemon.want_quit = True - if srv_config['gui']: - serverdaemon.funcs_to_daemonize = [server_with_gui] - else: - server_without_gui = ServerWithoutGui() - serverdaemon.funcs_to_daemonize = [server_without_gui.start, server_without_gui.join] - indx_for_clean = lambda: (0, ) - serverdaemon.quit_on_stop = [indx_for_clean, server_without_gui.clean] - elif srv_config['operation'] == 'stop': - os.remove(path) - - Etrigan_job(srv_config['operation'], serverdaemon) - def server_check(): # Setup and some checks. check_setup(srv_config, srv_options, loggersrv, where = "srv") @@ -446,25 +372,25 @@ def server_check(): # Check sqlite. if srv_config['sqlite']: - if isinstance(srv_config['sqlite'], str): - check_dir(srv_config['sqlite'], 'srv', log_obj = loggersrv.error, argument = '-s/--sqlite', typefile = '.db') - elif srv_config['sqlite'] is True: + if srv_config['sqlite'] is True: # Resolve bool to the default path srv_config['sqlite'] = srv_options['sql']['file'] + if os.path.isdir(srv_config['sqlite']): + pretty_printer(log_obj = loggersrv.warning, + put_text = "{reverse}{yellow}{bold}You specified a folder instead of a database file! This behavior is not officially supported anymore, please change your start parameters soon.{end}") + srv_config['sqlite'] = os.path.join(srv_config['sqlite'], 'pykms_database.db') try: import sqlite3 + sql_initialize(srv_config['sqlite']) except ImportError: pretty_printer(log_obj = loggersrv.warning, - put_text = "{reverse}{yellow}{bold}Module 'sqlite3' not installed, database support disabled.{end}") + put_text = "{reverse}{yellow}{bold}Module 'sqlite3' not installed, database support disabled.{end}") srv_config['sqlite'] = False # Check other specific server options. opts = [('clientcount', '-c/--client-count'), ('timeoutidle', '-t0/--timeout-idle'), ('timeoutsndrcv', '-t1/--timeout-sndrcv')] - if serverthread.with_gui: - opts += [('activation', '-a/--activation-interval'), - ('renewal', '-r/--renewal-interval')] check_other(srv_config, opts, loggersrv, where = 'srv') # Check further addresses / ports. @@ -495,14 +421,12 @@ def server_create(): all_address = [( srv_config['ip'], srv_config['port'], (srv_config['backlog_main'] if 'backlog_main' in srv_config else srv_options['backlog']['def']), - (srv_config['reuse_main'] if 'reuse_main' in srv_config else False if getuser() == 'WDAGUtilityAccount' \ - else srv_options['reuse']['def']) + (srv_config['reuse_main'] if 'reuse_main' in srv_config else srv_options['reuse']['def']) )] log_address = "TCP server listening at %s on port %d" %(srv_config['ip'], srv_config['port']) if 'listen' in srv_config: for l, b, r in zip(srv_config['listen'], srv_config['backlog'], srv_config['reuse']): - r = (False if getuser() == 'WDAGUtilityAccount' else r) all_address.append(l + (b,) + (r,)) log_address += justify("at %s on port %d" %(l[0], l[1]), indent = 56) @@ -546,32 +470,14 @@ def server_main_terminal(): server_check() serverthread.checked = True - if 'etrigan' not in srv_config.values(): - # (without GUI) and (without daemon). - # Run threaded server. - serverqueue.put('start') - # Wait to finish. - try: - while serverthread.is_alive(): - serverthread.join(timeout = 0.5) - except (KeyboardInterrupt, SystemExit): - server_terminate(serverthread, exit_server = True, exit_thread = True) - else: - # (with or without GUI) and (with daemon) - # Setup daemon (eventually). - server_daemon() - -def server_with_gui(): - import pykms_GuiBase - - root = pykms_GuiBase.KmsGui() - root.title(pykms_GuiBase.gui_description + ' (' + pykms_GuiBase.gui_version + ')') - root.mainloop() - -def server_main_no_terminal(): - # Run tkinter GUI. - # (with GUI) and (without daemon). - server_with_gui() + # Run threaded server. + serverqueue.put('start') + # Wait to finish. + try: + while serverthread.is_alive(): + serverthread.join(timeout = 0.5) + except (KeyboardInterrupt, SystemExit): + server_terminate(serverthread, exit_server = True, exit_thread = True) class kmsServerHandler(socketserver.BaseRequestHandler): def setup(self): @@ -585,7 +491,7 @@ def handle(self): try: self.data = self.request.recv(1024) if self.data == '' or not self.data: - pretty_printer(log_obj = loggersrv.warning, + pretty_printer(log_obj = loggersrv.debug, # use debug, as the healthcheck will spam this put_text = "{reverse}{yellow}{bold}No data received.{end}") break except socket.error as e: @@ -632,14 +538,8 @@ def finish(self): serverqueue = Queue.Queue(maxsize = 0) serverthread = server_thread(serverqueue, name = "Thread-Srv") -serverthread.setDaemon(True) +serverthread.daemon = True serverthread.start() if __name__ == "__main__": - if sys.stdout.isatty(): - server_main_terminal() - else: - try: - server_main_no_terminal() - except: - server_main_terminal() + server_main_terminal() diff --git a/py-kms/pykms_Sql.py b/py-kms/pykms_Sql.py index 1e8c47d..841c557 100644 --- a/py-kms/pykms_Sql.py +++ b/py-kms/pykms_Sql.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import datetime import os import logging @@ -18,21 +19,40 @@ def sql_initialize(dbName): if not os.path.isfile(dbName): # Initialize the database. + loggersrv.debug(f'Initializing database file "{dbName}"...') con = None try: con = sqlite3.connect(dbName) cur = con.cursor() - cur.execute("CREATE TABLE clients(clientMachineId TEXT, machineName TEXT, applicationId TEXT, skuId TEXT, \ -licenseStatus TEXT, lastRequestTime INTEGER, kmsEpid TEXT, requestCount INTEGER)") + cur.execute("CREATE TABLE clients(clientMachineId TEXT , machineName TEXT, applicationId TEXT, skuId TEXT, licenseStatus TEXT, lastRequestTime INTEGER, kmsEpid TEXT, requestCount INTEGER, PRIMARY KEY(clientMachineId, applicationId))") except sqlite3.Error as e: - pretty_printer(log_obj = loggersrv.error, to_exit = True, - put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e)) + pretty_printer(log_obj = loggersrv.error, to_exit = True, put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e)) finally: if con: con.commit() con.close() +def sql_get_all(dbName): + if not os.path.isfile(dbName): + return None + with sqlite3.connect(dbName) as con: + cur = con.cursor() + cur.execute("SELECT * FROM clients") + clients = [] + for row in cur.fetchall(): + clients.append({ + 'clientMachineId': row[0], + 'machineName': row[1], + 'applicationId': row[2], + 'skuId': row[3], + 'licenseStatus': row[4], + 'lastRequestTime': datetime.datetime.fromtimestamp(row[5]).isoformat(), + 'kmsEpid': row[6], + 'requestCount': row[7] + }) + return clients + def sql_update(dbName, infoDict): con = None try: diff --git a/py-kms/pykms_WebUI.py b/py-kms/pykms_WebUI.py new file mode 100644 index 0000000..43285a5 --- /dev/null +++ b/py-kms/pykms_WebUI.py @@ -0,0 +1,141 @@ +import os, uuid, datetime +from flask import Flask, render_template +from pykms_Sql import sql_get_all +from pykms_DB2Dict import kmsDB2Dict + +def _random_uuid(): + return str(uuid.uuid4()).replace('-', '_') + +_serve_count = 0 +def _increase_serve_count(): + global _serve_count + _serve_count += 1 + +def _get_serve_count(): + return _serve_count + +_kms_items = None +_kms_items_ignored = None +def _get_kms_items_cache(): + global _kms_items, _kms_items_ignored + if _kms_items is None: + _kms_items = {} + _kms_items_ignored = 0 + queue = [kmsDB2Dict()] + while len(queue): + item = queue.pop(0) + if isinstance(item, list): + for i in item: + queue.append(i) + elif isinstance(item, dict): + if 'KmsItems' in item: + queue.append(item['KmsItems']) + elif 'SkuItems' in item: + queue.append(item['SkuItems']) + elif 'Gvlk' in item: + if len(item['Gvlk']): + _kms_items[item['DisplayName']] = item['Gvlk'] + else: + _kms_items_ignored += 1 + #else: + # print(item) + else: + raise NotImplementedError(f'Unknown type: {type(item)}') + return _kms_items, _kms_items_ignored + +app = Flask('pykms_webui') +app.jinja_env.globals['start_time'] = datetime.datetime.now() +app.jinja_env.globals['get_serve_count'] = _get_serve_count +app.jinja_env.globals['random_uuid'] = _random_uuid +app.jinja_env.globals['version_info'] = None + +_version_info_path = os.environ.get('PYKMS_VERSION_PATH', '../VERSION') +if os.path.exists(_version_info_path): + with open(_version_info_path, 'r') as f: + app.jinja_env.globals['version_info'] = { + 'hash': f.readline().strip(), + 'branch': f.readline().strip() + } + +_dbEnvVarName = 'PYKMS_SQLITE_DB_PATH' +def _env_check(): + if _dbEnvVarName not in os.environ: + raise Exception(f'Environment variable is not set: {_dbEnvVarName}') + +@app.route('/') +def root(): + _increase_serve_count() + error = None + # Get the db name / path + dbPath = None + if _dbEnvVarName in os.environ: + dbPath = os.environ.get(_dbEnvVarName) + else: + error = f'Environment variable is not set: {_dbEnvVarName}' + # Fetch all clients from the database. + clients = None + try: + if dbPath: + clients = sql_get_all(dbPath) + except Exception as e: + error = f'Error while loading database: {e}' + countClients = len(clients) if clients else 0 + countClientsWindows = len([c for c in clients if c['applicationId'] == 'Windows']) if clients else 0 + countClientsOffice = countClients - countClientsWindows + return render_template( + 'clients.html', + path='/', + error=error, + clients=clients, + count_clients=countClients, + count_clients_windows=countClientsWindows, + count_clients_office=countClientsOffice, + count_projects=len(_get_kms_items_cache()[0]) + ), 200 if error is None else 500 + +@app.route('/readyz') +def readyz(): + try: + _env_check() + except Exception as e: + return f'Whooops! {e}', 503 + if (datetime.datetime.now() - app.jinja_env.globals['start_time']).seconds > 10: # Wait 10 seconds before accepting requests + return 'OK', 200 + else: + return 'Not ready', 503 + +@app.route('/livez') +def livez(): + try: + _env_check() + return 'OK', 200 # There are no checks for liveness, so we just return OK + except Exception as e: + return f'Whooops! {e}', 503 + +@app.route('/license') +def license(): + _increase_serve_count() + with open(os.environ.get('PYKMS_LICENSE_PATH', '../LICENSE'), 'r') as f: + return render_template( + 'license.html', + path='/license/', + license=f.read() + ) + +@app.route('/products') +def products(): + _increase_serve_count() + items, ignored = _get_kms_items_cache() + countProducts = len(items) + countProductsWindows = len([i for i in items if 'windows' in i.lower()]) + countProductsOffice = len([i for i in items if 'office' in i.lower()]) + return render_template( + 'products.html', + path='/products/', + products=items, + filtered=ignored, + count_products=countProducts, + count_products_windows=countProductsWindows, + count_products_office=countProductsOffice + ) + \ No newline at end of file diff --git a/py-kms/static/css/bulma.min.css b/py-kms/static/css/bulma.min.css new file mode 100644 index 0000000..86ad2ff --- /dev/null +++ b/py-kms/static/css/bulma.min.css @@ -0,0 +1 @@ +/*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */.button,.file-cta,.file-name,.input,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.select select,.textarea{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.5em - 1px);padding-left:calc(.75em - 1px);padding-right:calc(.75em - 1px);padding-top:calc(.5em - 1px);position:relative;vertical-align:top}.button:active,.button:focus,.file-cta:active,.file-cta:focus,.file-name:active,.file-name:focus,.input:active,.input:focus,.is-active.button,.is-active.file-cta,.is-active.file-name,.is-active.input,.is-active.pagination-ellipsis,.is-active.pagination-link,.is-active.pagination-next,.is-active.pagination-previous,.is-active.textarea,.is-focused.button,.is-focused.file-cta,.is-focused.file-name,.is-focused.input,.is-focused.pagination-ellipsis,.is-focused.pagination-link,.is-focused.pagination-next,.is-focused.pagination-previous,.is-focused.textarea,.pagination-ellipsis:active,.pagination-ellipsis:focus,.pagination-link:active,.pagination-link:focus,.pagination-next:active,.pagination-next:focus,.pagination-previous:active,.pagination-previous:focus,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{outline:0}.button[disabled],.file-cta[disabled],.file-name[disabled],.input[disabled],.pagination-ellipsis[disabled],.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .button,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .input,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-previous,fieldset[disabled] .select select,fieldset[disabled] .textarea{cursor:not-allowed}.breadcrumb,.button,.file,.is-unselectable,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.tabs{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid transparent;border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:.625em;margin-top:-.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:.625em}.block:not(:last-child),.box:not(:last-child),.breadcrumb:not(:last-child),.content:not(:last-child),.level:not(:last-child),.message:not(:last-child),.notification:not(:last-child),.pagination:not(:last-child),.progress:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.tabs:not(:last-child),.title:not(:last-child){margin-bottom:1.5rem}.delete,.modal-close{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:0;position:relative;vertical-align:top;width:20px}.delete::after,.delete::before,.modal-close::after,.modal-close::before{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.delete::before,.modal-close::before{height:2px;width:50%}.delete::after,.modal-close::after{height:50%;width:2px}.delete:focus,.delete:hover,.modal-close:focus,.modal-close:hover{background-color:rgba(10,10,10,.3)}.delete:active,.modal-close:active{background-color:rgba(10,10,10,.4)}.is-small.delete,.is-small.modal-close{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.delete,.is-medium.modal-close{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.delete,.is-large.modal-close{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.button.is-loading::after,.control.is-loading::after,.loader,.select.is-loading::after{-webkit-animation:spinAround .5s infinite linear;animation:spinAround .5s infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img,.is-overlay,.modal,.modal-background{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */blockquote,body,dd,dl,dt,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,html,iframe,legend,li,ol,p,pre,textarea,ul{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,::after,::before{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#4a4a4a;font-size:1em;font-weight:400;line-height:1.5}a{color:#485fc7;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#da1039;font-size:.875em;font-weight:400;padding:.25em .5em .25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#4a4a4a;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#363636}@-webkit-keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}@keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}.box{background-color:#fff;border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;display:block;padding:1.25rem}a.box:focus,a.box:hover{box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px #485fc7}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2),0 0 0 1px #485fc7}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-large,.button .icon.is-medium,.button .icon.is-small{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-.5em - 1px);margin-right:calc(-.5em - 1px)}.button.is-hovered,.button:hover{border-color:#b5b5b5;color:#363636}.button.is-focused,.button:focus{border-color:#485fc7;color:#363636}.button.is-focused:not(:active),.button:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.button.is-active,.button:active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#4a4a4a;text-decoration:underline}.button.is-text.is-focused,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text:hover{background-color:#f5f5f5;color:#363636}.button.is-text.is-active,.button.is-text:active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:0 0;border-color:transparent;color:#485fc7;text-decoration:none}.button.is-ghost.is-hovered,.button.is-ghost:hover{color:#485fc7;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white.is-hovered,.button.is-white:hover{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white.is-focused,.button.is-white:focus{border-color:transparent;color:#0a0a0a}.button.is-white.is-focused:not(:active),.button.is-white:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-white.is-active,.button.is-white:active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-hovered,.button.is-white.is-inverted:hover{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined.is-focused,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-outlined.is-loading.is-focused::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined.is-focused,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined:hover{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black.is-hovered,.button.is-black:hover{background-color:#040404;border-color:transparent;color:#fff}.button.is-black.is-focused,.button.is-black:focus{border-color:transparent;color:#fff}.button.is-black.is-focused:not(:active),.button.is-black:focus:not(:active){box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.button.is-black.is-active,.button.is-black:active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-hovered,.button.is-black.is-inverted:hover{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined.is-focused,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-outlined.is-loading.is-focused::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined.is-focused,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined:hover{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-hovered,.button.is-light:hover{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused,.button.is-light:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused:not(:active),.button.is-light:focus:not(:active){box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-light.is-active,.button.is-light:active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-hovered,.button.is-light.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined.is-focused,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined:hover{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-outlined.is-loading.is-focused::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-light.is-inverted.is-outlined.is-focused,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-dark{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark.is-hovered,.button.is-dark:hover{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark.is-focused,.button.is-dark:focus{border-color:transparent;color:#fff}.button.is-dark.is-focused:not(:active),.button.is-dark:focus:not(:active){box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-dark.is-active,.button.is-dark:active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:#363636;box-shadow:none}.button.is-dark.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-hovered,.button.is-dark.is-inverted:hover{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined.is-focused,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined:hover{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-outlined.is-loading.is-focused::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined.is-focused,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined:hover{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary{background-color:#00d1b2;border-color:transparent;color:#fff}.button.is-primary.is-hovered,.button.is-primary:hover{background-color:#00c4a7;border-color:transparent;color:#fff}.button.is-primary.is-focused,.button.is-primary:focus{border-color:transparent;color:#fff}.button.is-primary.is-focused:not(:active),.button.is-primary:focus:not(:active){box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.button.is-primary.is-active,.button.is-primary:active{background-color:#00b89c;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#00d1b2;border-color:#00d1b2;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-hovered,.button.is-primary.is-inverted:hover{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#00d1b2}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;color:#00d1b2}.button.is-primary.is-outlined.is-focused,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined:hover{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-outlined.is-loading.is-focused::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;box-shadow:none;color:#00d1b2}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined.is-focused,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined:hover{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light{background-color:#ebfffc;color:#00947e}.button.is-primary.is-light.is-hovered,.button.is-primary.is-light:hover{background-color:#defffa;border-color:transparent;color:#00947e}.button.is-primary.is-light.is-active,.button.is-primary.is-light:active{background-color:#d1fff8;border-color:transparent;color:#00947e}.button.is-link{background-color:#485fc7;border-color:transparent;color:#fff}.button.is-link.is-hovered,.button.is-link:hover{background-color:#3e56c4;border-color:transparent;color:#fff}.button.is-link.is-focused,.button.is-link:focus{border-color:transparent;color:#fff}.button.is-link.is-focused:not(:active),.button.is-link:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.button.is-link.is-active,.button.is-link:active{background-color:#3a51bb;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#485fc7;border-color:#485fc7;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#485fc7}.button.is-link.is-inverted.is-hovered,.button.is-link.is-inverted:hover{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#485fc7}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined{background-color:transparent;border-color:#485fc7;color:#485fc7}.button.is-link.is-outlined.is-focused,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined:hover{background-color:#485fc7;border-color:#485fc7;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #485fc7 #485fc7!important}.button.is-link.is-outlined.is-loading.is-focused::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#485fc7;box-shadow:none;color:#485fc7}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined.is-focused,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined:hover{background-color:#fff;color:#485fc7}.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #485fc7 #485fc7!important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eff1fa;color:#3850b7}.button.is-link.is-light.is-hovered,.button.is-link.is-light:hover{background-color:#e6e9f7;border-color:transparent;color:#3850b7}.button.is-link.is-light.is-active,.button.is-link.is-light:active{background-color:#dce0f4;border-color:transparent;color:#3850b7}.button.is-info{background-color:#3e8ed0;border-color:transparent;color:#fff}.button.is-info.is-hovered,.button.is-info:hover{background-color:#3488ce;border-color:transparent;color:#fff}.button.is-info.is-focused,.button.is-info:focus{border-color:transparent;color:#fff}.button.is-info.is-focused:not(:active),.button.is-info:focus:not(:active){box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.button.is-info.is-active,.button.is-info:active{background-color:#3082c5;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3e8ed0;border-color:#3e8ed0;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted.is-hovered,.button.is-info.is-inverted:hover{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3e8ed0}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;color:#3e8ed0}.button.is-info.is-outlined.is-focused,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined:hover{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3e8ed0 #3e8ed0!important}.button.is-info.is-outlined.is-loading.is-focused::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;box-shadow:none;color:#3e8ed0}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined.is-focused,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined:hover{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3e8ed0 #3e8ed0!important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eff5fb;color:#296fa8}.button.is-info.is-light.is-hovered,.button.is-info.is-light:hover{background-color:#e4eff9;border-color:transparent;color:#296fa8}.button.is-info.is-light.is-active,.button.is-info.is-light:active{background-color:#dae9f6;border-color:transparent;color:#296fa8}.button.is-success{background-color:#48c78e;border-color:transparent;color:#fff}.button.is-success.is-hovered,.button.is-success:hover{background-color:#3ec487;border-color:transparent;color:#fff}.button.is-success.is-focused,.button.is-success:focus{border-color:transparent;color:#fff}.button.is-success.is-focused:not(:active),.button.is-success:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.button.is-success.is-active,.button.is-success:active{background-color:#3abb81;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#48c78e;border-color:#48c78e;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#48c78e}.button.is-success.is-inverted.is-hovered,.button.is-success.is-inverted:hover{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#48c78e}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined{background-color:transparent;border-color:#48c78e;color:#48c78e}.button.is-success.is-outlined.is-focused,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined:hover{background-color:#48c78e;border-color:#48c78e;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #48c78e #48c78e!important}.button.is-success.is-outlined.is-loading.is-focused::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#48c78e;box-shadow:none;color:#48c78e}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined.is-focused,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined:hover{background-color:#fff;color:#48c78e}.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #48c78e #48c78e!important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effaf5;color:#257953}.button.is-success.is-light.is-hovered,.button.is-success.is-light:hover{background-color:#e6f7ef;border-color:transparent;color:#257953}.button.is-success.is-light.is-active,.button.is-success.is-light:active{background-color:#dcf4e9;border-color:transparent;color:#257953}.button.is-warning{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-hovered,.button.is-warning:hover{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused,.button.is-warning:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused:not(:active),.button.is-warning:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.button.is-warning.is-active,.button.is-warning:active{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffe08a;border-color:#ffe08a;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted.is-hovered,.button.is-warning.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffe08a}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;color:#ffe08a}.button.is-warning.is-outlined.is-focused,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined:hover{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffe08a #ffe08a!important}.button.is-warning.is-outlined.is-loading.is-focused::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;box-shadow:none;color:#ffe08a}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined.is-focused,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #ffe08a #ffe08a!important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-warning.is-light{background-color:#fffaeb;color:#946c00}.button.is-warning.is-light.is-hovered,.button.is-warning.is-light:hover{background-color:#fff6de;border-color:transparent;color:#946c00}.button.is-warning.is-light.is-active,.button.is-warning.is-light:active{background-color:#fff3d1;border-color:transparent;color:#946c00}.button.is-danger{background-color:#f14668;border-color:transparent;color:#fff}.button.is-danger.is-hovered,.button.is-danger:hover{background-color:#f03a5f;border-color:transparent;color:#fff}.button.is-danger.is-focused,.button.is-danger:focus{border-color:transparent;color:#fff}.button.is-danger.is-focused:not(:active),.button.is-danger:focus:not(:active){box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-danger.is-active,.button.is-danger:active{background-color:#ef2e55;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#f14668;border-color:#f14668;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-hovered,.button.is-danger.is-inverted:hover{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f14668}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;color:#f14668}.button.is-danger.is-outlined.is-focused,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined:hover{background-color:#f14668;border-color:#f14668;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-outlined.is-loading.is-focused::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;box-shadow:none;color:#f14668}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined.is-focused,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined:hover{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.button.is-danger.is-light.is-hovered,.button.is-danger.is-light:hover{background-color:#fde0e6;border-color:transparent;color:#cc0f35}.button.is-danger.is-light.is-active,.button.is-danger.is-light:active{background-color:#fcd4dc;border-color:transparent;color:#cc0f35}.button.is-small{font-size:.75rem}.button.is-small:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent!important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * .5));top:calc(50% - (1em * .5));position:absolute!important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:9999px;padding-left:calc(1em + .25em);padding-right:calc(1em + .25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button.is-hovered,.buttons.has-addons .button:hover{z-index:2}.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-focused,.buttons.has-addons .button.is-selected,.buttons.has-addons .button:active,.buttons.has-addons .button:focus{z-index:3}.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button.is-selected:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button:focus:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}@media screen and (max-width:768px){.button.is-responsive.is-small{font-size:.5625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.65625rem}.button.is-responsive.is-medium{font-size:.75rem}.button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width:769px) and (max-width:1023px){.button.is-responsive.is-small{font-size:.65625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.75rem}.button.is-responsive.is-medium{font-size:1rem}.button.is-responsive.is-large{font-size:1.25rem}}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none!important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width:1024px){.container{max-width:960px}}@media screen and (max-width:1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width:1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width:1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width:1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:.25em}.content blockquote:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content p:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child),.content ul:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sub,.content sup{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:9999px}.image.is-fullwidth{width:100%}.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img{height:100%;width:100%}.image.is-1by1,.image.is-square{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:0 0}.notification>.delete{right:.5rem;position:absolute;top:.5rem}.notification .content,.notification .subtitle,.notification .title{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.notification.is-dark{background-color:#363636;color:#fff}.notification.is-primary{background-color:#00d1b2;color:#fff}.notification.is-primary.is-light{background-color:#ebfffc;color:#00947e}.notification.is-link{background-color:#485fc7;color:#fff}.notification.is-link.is-light{background-color:#eff1fa;color:#3850b7}.notification.is-info{background-color:#3e8ed0;color:#fff}.notification.is-info.is-light{background-color:#eff5fb;color:#296fa8}.notification.is-success{background-color:#48c78e;color:#fff}.notification.is-success.is-light{background-color:#effaf5;color:#257953}.notification.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.notification.is-warning.is-light{background-color:#fffaeb;color:#946c00}.notification.is-danger{background-color:#f14668;color:#fff}.notification.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#4a4a4a}.progress::-moz-progress-bar{background-color:#4a4a4a}.progress::-ms-fill{background-color:#4a4a4a;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right,#fff 30%,#ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right,#0a0a0a 30%,#ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right,#f5f5f5 30%,#ededed 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right,#363636 30%,#ededed 30%)}.progress.is-primary::-webkit-progress-value{background-color:#00d1b2}.progress.is-primary::-moz-progress-bar{background-color:#00d1b2}.progress.is-primary::-ms-fill{background-color:#00d1b2}.progress.is-primary:indeterminate{background-image:linear-gradient(to right,#00d1b2 30%,#ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#485fc7}.progress.is-link::-moz-progress-bar{background-color:#485fc7}.progress.is-link::-ms-fill{background-color:#485fc7}.progress.is-link:indeterminate{background-image:linear-gradient(to right,#485fc7 30%,#ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3e8ed0}.progress.is-info::-moz-progress-bar{background-color:#3e8ed0}.progress.is-info::-ms-fill{background-color:#3e8ed0}.progress.is-info:indeterminate{background-image:linear-gradient(to right,#3e8ed0 30%,#ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#48c78e}.progress.is-success::-moz-progress-bar{background-color:#48c78e}.progress.is-success::-ms-fill{background-color:#48c78e}.progress.is-success:indeterminate{background-image:linear-gradient(to right,#48c78e 30%,#ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffe08a}.progress.is-warning::-moz-progress-bar{background-color:#ffe08a}.progress.is-warning::-ms-fill{background-color:#ffe08a}.progress.is-warning:indeterminate{background-image:linear-gradient(to right,#ffe08a 30%,#ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#f14668}.progress.is-danger::-moz-progress-bar{background-color:#f14668}.progress.is-danger::-ms-fill{background-color:#f14668}.progress.is-danger:indeterminate{background-image:linear-gradient(to right,#f14668 30%,#ededed 30%)}.progress:indeterminate{-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:moveIndeterminate;animation-name:moveIndeterminate;-webkit-animation-timing-function:linear;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right,#4a4a4a 30%,#ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@-webkit-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.table td.is-link,.table th.is-link{background-color:#485fc7;border-color:#485fc7;color:#fff}.table td.is-info,.table th.is-info{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.table td.is-success,.table th.is-success{background-color:#48c78e;border-color:#48c78e;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#f14668;border-color:#f14668;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#00d1b2;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#363636}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#00d1b2;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:transparent}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:transparent}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:transparent}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(2n){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(2n){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:.25rem;margin-left:.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#4a4a4a;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag:not(body).is-white{background-color:#fff;color:#0a0a0a}.tag:not(body).is-black{background-color:#0a0a0a;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.tag:not(body).is-dark{background-color:#363636;color:#fff}.tag:not(body).is-primary{background-color:#00d1b2;color:#fff}.tag:not(body).is-primary.is-light{background-color:#ebfffc;color:#00947e}.tag:not(body).is-link{background-color:#485fc7;color:#fff}.tag:not(body).is-link.is-light{background-color:#eff1fa;color:#3850b7}.tag:not(body).is-info{background-color:#3e8ed0;color:#fff}.tag:not(body).is-info.is-light{background-color:#eff5fb;color:#296fa8}.tag:not(body).is-success{background-color:#48c78e;color:#fff}.tag:not(body).is-success.is-light{background-color:#effaf5;color:#257953}.tag:not(body).is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.tag:not(body).is-warning.is-light{background-color:#fffaeb;color:#946c00}.tag:not(body).is-danger{background-color:#f14668;color:#fff}.tag:not(body).is-danger.is-light{background-color:#feecf0;color:#cc0f35}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::after,.tag:not(body).is-delete::before{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:focus,.tag:not(body).is-delete:hover{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:9999px}a.tag:hover{text-decoration:underline}.subtitle,.title{word-break:break-word}.subtitle em,.subtitle span,.title em,.title span{font-weight:inherit}.subtitle sub,.title sub{font-size:.75em}.subtitle sup,.title sup{font-size:.75em}.subtitle .tag,.title .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#4a4a4a;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.input,.select select,.textarea{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.input::-moz-placeholder,.select select::-moz-placeholder,.textarea::-moz-placeholder{color:rgba(54,54,54,.3)}.input::-webkit-input-placeholder,.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.input:-moz-placeholder,.select select:-moz-placeholder,.textarea:-moz-placeholder{color:rgba(54,54,54,.3)}.input:-ms-input-placeholder,.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:rgba(54,54,54,.3)}.input:hover,.is-hovered.input,.is-hovered.textarea,.select select.is-hovered,.select select:hover,.textarea:hover{border-color:#b5b5b5}.input:active,.input:focus,.is-active.input,.is-active.textarea,.is-focused.input,.is-focused.textarea,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{border-color:#485fc7;box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.input[disabled]::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder{color:rgba(122,122,122,.3)}.input,.textarea{box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);max-width:100%;width:100%}.input[readonly],.textarea[readonly]{box-shadow:none}.is-white.input,.is-white.textarea{border-color:#fff}.is-white.input:active,.is-white.input:focus,.is-white.is-active.input,.is-white.is-active.textarea,.is-white.is-focused.input,.is-white.is-focused.textarea,.is-white.textarea:active,.is-white.textarea:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.is-black.input,.is-black.textarea{border-color:#0a0a0a}.is-black.input:active,.is-black.input:focus,.is-black.is-active.input,.is-black.is-active.textarea,.is-black.is-focused.input,.is-black.is-focused.textarea,.is-black.textarea:active,.is-black.textarea:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.is-light.input,.is-light.textarea{border-color:#f5f5f5}.is-light.input:active,.is-light.input:focus,.is-light.is-active.input,.is-light.is-active.textarea,.is-light.is-focused.input,.is-light.is-focused.textarea,.is-light.textarea:active,.is-light.textarea:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.is-dark.input,.is-dark.textarea{border-color:#363636}.is-dark.input:active,.is-dark.input:focus,.is-dark.is-active.input,.is-dark.is-active.textarea,.is-dark.is-focused.input,.is-dark.is-focused.textarea,.is-dark.textarea:active,.is-dark.textarea:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.is-primary.input,.is-primary.textarea{border-color:#00d1b2}.is-primary.input:active,.is-primary.input:focus,.is-primary.is-active.input,.is-primary.is-active.textarea,.is-primary.is-focused.input,.is-primary.is-focused.textarea,.is-primary.textarea:active,.is-primary.textarea:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.is-link.input,.is-link.textarea{border-color:#485fc7}.is-link.input:active,.is-link.input:focus,.is-link.is-active.input,.is-link.is-active.textarea,.is-link.is-focused.input,.is-link.is-focused.textarea,.is-link.textarea:active,.is-link.textarea:focus{box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.is-info.input,.is-info.textarea{border-color:#3e8ed0}.is-info.input:active,.is-info.input:focus,.is-info.is-active.input,.is-info.is-active.textarea,.is-info.is-focused.input,.is-info.is-focused.textarea,.is-info.textarea:active,.is-info.textarea:focus{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.is-success.input,.is-success.textarea{border-color:#48c78e}.is-success.input:active,.is-success.input:focus,.is-success.is-active.input,.is-success.is-active.textarea,.is-success.is-focused.input,.is-success.is-focused.textarea,.is-success.textarea:active,.is-success.textarea:focus{box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.is-warning.input,.is-warning.textarea{border-color:#ffe08a}.is-warning.input:active,.is-warning.input:focus,.is-warning.is-active.input,.is-warning.is-active.textarea,.is-warning.is-focused.input,.is-warning.is-focused.textarea,.is-warning.textarea:active,.is-warning.textarea:focus{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.is-danger.input,.is-danger.textarea{border-color:#f14668}.is-danger.input:active,.is-danger.input:focus,.is-danger.is-active.input,.is-danger.is-active.textarea,.is-danger.is-focused.input,.is-danger.is-focused.textarea,.is-danger.textarea:active,.is-danger.textarea:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.is-small.input,.is-small.textarea{border-radius:2px;font-size:.75rem}.is-medium.input,.is-medium.textarea{font-size:1.25rem}.is-large.input,.is-large.textarea{font-size:1.5rem}.is-fullwidth.input,.is-fullwidth.textarea{display:block;width:100%}.is-inline.input,.is-inline.textarea{display:inline;width:auto}.input.is-rounded{border-radius:9999px;padding-left:calc(calc(.75em - 1px) + .375em);padding-right:calc(calc(.75em - 1px) + .375em)}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.checkbox,.radio{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.checkbox input,.radio input{cursor:pointer}.checkbox:hover,.radio:hover{color:#363636}.checkbox input[disabled],.checkbox[disabled],.radio input[disabled],.radio[disabled],fieldset[disabled] .checkbox,fieldset[disabled] .radio{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#485fc7;right:1.125em;z-index:4}.select.is-rounded select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:0}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select.is-hovered,.select.is-white select:hover{border-color:#f2f2f2}.select.is-white select.is-active,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select.is-hovered,.select.is-black select:hover{border-color:#000}.select.is-black select.is-active,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select.is-hovered,.select.is-light select:hover{border-color:#e8e8e8}.select.is-light select.is-active,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select.is-hovered,.select.is-dark select:hover{border-color:#292929}.select.is-dark select.is-active,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.select.is-primary:not(:hover)::after{border-color:#00d1b2}.select.is-primary select{border-color:#00d1b2}.select.is-primary select.is-hovered,.select.is-primary select:hover{border-color:#00b89c}.select.is-primary select.is-active,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.select.is-link:not(:hover)::after{border-color:#485fc7}.select.is-link select{border-color:#485fc7}.select.is-link select.is-hovered,.select.is-link select:hover{border-color:#3a51bb}.select.is-link select.is-active,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select:focus{box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.select.is-info:not(:hover)::after{border-color:#3e8ed0}.select.is-info select{border-color:#3e8ed0}.select.is-info select.is-hovered,.select.is-info select:hover{border-color:#3082c5}.select.is-info select.is-active,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select:focus{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.select.is-success:not(:hover)::after{border-color:#48c78e}.select.is-success select{border-color:#48c78e}.select.is-success select.is-hovered,.select.is-success select:hover{border-color:#3abb81}.select.is-success select.is-active,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select:focus{box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.select.is-warning:not(:hover)::after{border-color:#ffe08a}.select.is-warning select{border-color:#ffe08a}.select.is-warning select.is-hovered,.select.is-warning select:hover{border-color:#ffd970}.select.is-warning select.is-active,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select:focus{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.select.is-danger:not(:hover)::after{border-color:#f14668}.select.is-danger select{border-color:#f14668}.select.is-danger select.is-hovered,.select.is-danger select:hover{border-color:#ef2e55}.select.is-danger select.is-active,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a!important;opacity:.5}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white.is-hovered .file-cta,.file.is-white:hover .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white.is-focused .file-cta,.file.is-white:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.file.is-white.is-active .file-cta,.file.is-white:active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black.is-hovered .file-cta,.file.is-black:hover .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black.is-focused .file-cta,.file.is-black:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.file.is-black.is-active .file-cta,.file.is-black:active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-hovered .file-cta,.file.is-light:hover .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-focused .file-cta,.file.is-light:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:rgba(0,0,0,.7)}.file.is-light.is-active .file-cta,.file.is-light:active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark.is-hovered .file-cta,.file.is-dark:hover .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark.is-focused .file-cta,.file.is-dark:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#fff}.file.is-dark.is-active .file-cta,.file.is-dark:active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta{background-color:#00d1b2;border-color:transparent;color:#fff}.file.is-primary.is-hovered .file-cta,.file.is-primary:hover .file-cta{background-color:#00c4a7;border-color:transparent;color:#fff}.file.is-primary.is-focused .file-cta,.file.is-primary:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(0,209,178,.25);color:#fff}.file.is-primary.is-active .file-cta,.file.is-primary:active .file-cta{background-color:#00b89c;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#485fc7;border-color:transparent;color:#fff}.file.is-link.is-hovered .file-cta,.file.is-link:hover .file-cta{background-color:#3e56c4;border-color:transparent;color:#fff}.file.is-link.is-focused .file-cta,.file.is-link:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,95,199,.25);color:#fff}.file.is-link.is-active .file-cta,.file.is-link:active .file-cta{background-color:#3a51bb;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3e8ed0;border-color:transparent;color:#fff}.file.is-info.is-hovered .file-cta,.file.is-info:hover .file-cta{background-color:#3488ce;border-color:transparent;color:#fff}.file.is-info.is-focused .file-cta,.file.is-info:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(62,142,208,.25);color:#fff}.file.is-info.is-active .file-cta,.file.is-info:active .file-cta{background-color:#3082c5;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#48c78e;border-color:transparent;color:#fff}.file.is-success.is-hovered .file-cta,.file.is-success:hover .file-cta{background-color:#3ec487;border-color:transparent;color:#fff}.file.is-success.is-focused .file-cta,.file.is-success:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,199,142,.25);color:#fff}.file.is-success.is-active .file-cta,.file.is-success:active .file-cta{background-color:#3abb81;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-hovered .file-cta,.file.is-warning:hover .file-cta{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-focused .file-cta,.file.is-warning:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,224,138,.25);color:rgba(0,0,0,.7)}.file.is-warning.is-active .file-cta,.file.is-warning:active .file-cta{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#f14668;border-color:transparent;color:#fff}.file.is-danger.is-hovered .file-cta,.file.is-danger:hover .file-cta{background-color:#f03a5f;border-color:transparent;color:#fff}.file.is-danger.is-focused .file-cta,.file.is-danger:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(241,70,104,.25);color:#fff}.file.is-danger.is-active .file-cta,.file.is-danger:active .file-cta{background-color:#ef2e55;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:0;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#4a4a4a}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#00d1b2}.help.is-link{color:#485fc7}.help.is-info{color:#3e8ed0}.help.is-success{color:#48c78e}.help.is-warning{color:#ffe08a}.help.is-danger{color:#f14668}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover{z-index:2}.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]):focus{z-index:3}.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width:769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width:768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width:769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width:769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#4a4a4a}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute!important;right:.625em;top:.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#485fc7;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ol,.breadcrumb ul{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;max-width:100%;position:relative}.card-content:first-child,.card-footer:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-content:last-child,.card-footer:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:transparent;align-items:stretch;box-shadow:0 .125em .25em rgba(10,10,10,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:transparent;padding:1.5rem}.card-footer{background-color:transparent;border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#4a4a4a;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#485fc7;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width:769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .subtitle,.level-item .title{margin-bottom:0}@media screen and (max-width:768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width:769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width:768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width:769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width:769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width:768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#4a4a4a;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#485fc7;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body{border-color:#363636}.message.is-primary{background-color:#ebfffc}.message.is-primary .message-header{background-color:#00d1b2;color:#fff}.message.is-primary .message-body{border-color:#00d1b2;color:#00947e}.message.is-link{background-color:#eff1fa}.message.is-link .message-header{background-color:#485fc7;color:#fff}.message.is-link .message-body{border-color:#485fc7;color:#3850b7}.message.is-info{background-color:#eff5fb}.message.is-info .message-header{background-color:#3e8ed0;color:#fff}.message.is-info .message-body{border-color:#3e8ed0;color:#296fa8}.message.is-success{background-color:#effaf5}.message.is-success .message-header{background-color:#48c78e;color:#fff}.message.is-success .message-body{border-color:#48c78e;color:#257953}.message.is-warning{background-color:#fffaeb}.message.is-warning .message-header{background-color:#ffe08a;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffe08a;color:#946c00}.message.is-danger{background-color:#feecf0}.message.is-danger .message-header{background-color:#f14668;color:#fff}.message.is-danger .message-body{border-color:#f14668;color:#cc0f35}.message-header{align-items:center;background-color:#4a4a4a;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#4a4a4a;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,.86)}.modal-card,.modal-content{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width:769px){.modal-card,.modal-content{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:0 0;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-foot,.modal-card-head{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link,.navbar.is-white .navbar-brand>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width:1024px){.navbar.is-white .navbar-end .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-start>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link::after,.navbar.is-white .navbar-start .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand .navbar-link,.navbar.is-black .navbar-brand>.navbar-item{color:#fff}.navbar.is-black .navbar-brand .navbar-link.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-black .navbar-end .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-start>.navbar-item{color:#fff}.navbar.is-black .navbar-end .navbar-link.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-end .navbar-link::after,.navbar.is-black .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link,.navbar.is-light .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-light .navbar-end .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link::after,.navbar.is-light .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,.7)}}.navbar.is-dark{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand .navbar-link,.navbar.is-dark .navbar-brand>.navbar-item{color:#fff}.navbar.is-dark .navbar-brand .navbar-link.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-dark .navbar-end .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-start>.navbar-item{color:#fff}.navbar.is-dark .navbar-end .navbar-link.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-end .navbar-link::after,.navbar.is-dark .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary{background-color:#00d1b2;color:#fff}.navbar.is-primary .navbar-brand .navbar-link,.navbar.is-primary .navbar-brand>.navbar-item{color:#fff}.navbar.is-primary .navbar-brand .navbar-link.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-primary .navbar-end .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-start>.navbar-item{color:#fff}.navbar.is-primary .navbar-end .navbar-link.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-end .navbar-link::after,.navbar.is-primary .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#00d1b2;color:#fff}}.navbar.is-link{background-color:#485fc7;color:#fff}.navbar.is-link .navbar-brand .navbar-link,.navbar.is-link .navbar-brand>.navbar-item{color:#fff}.navbar.is-link .navbar-brand .navbar-link.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-link .navbar-end .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-start>.navbar-item{color:#fff}.navbar.is-link .navbar-end .navbar-link.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-end .navbar-link::after,.navbar.is-link .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#485fc7;color:#fff}}.navbar.is-info{background-color:#3e8ed0;color:#fff}.navbar.is-info .navbar-brand .navbar-link,.navbar.is-info .navbar-brand>.navbar-item{color:#fff}.navbar.is-info .navbar-brand .navbar-link.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-info .navbar-end .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-start>.navbar-item{color:#fff}.navbar.is-info .navbar-end .navbar-link.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-end .navbar-link::after,.navbar.is-info .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3e8ed0;color:#fff}}.navbar.is-success{background-color:#48c78e;color:#fff}.navbar.is-success .navbar-brand .navbar-link,.navbar.is-success .navbar-brand>.navbar-item{color:#fff}.navbar.is-success .navbar-brand .navbar-link.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-success .navbar-end .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-start>.navbar-item{color:#fff}.navbar.is-success .navbar-end .navbar-link.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-end .navbar-link::after,.navbar.is-success .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#48c78e;color:#fff}}.navbar.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link,.navbar.is-warning .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-warning .navbar-end .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link::after,.navbar.is-warning .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffe08a;color:rgba(0,0,0,.7)}}.navbar.is-danger{background-color:#f14668;color:#fff}.navbar.is-danger .navbar-brand .navbar-link,.navbar.is-danger .navbar-brand>.navbar-item{color:#fff}.navbar.is-danger .navbar-brand .navbar-link.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-danger .navbar-end .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-start>.navbar-item{color:#fff}.navbar.is-danger .navbar-end .navbar-link.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-end .navbar-link::after,.navbar.is-danger .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f14668;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}body.has-navbar-fixed-top,html.has-navbar-fixed-top{padding-top:3.25rem}body.has-navbar-fixed-bottom,html.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#4a4a4a;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color,opacity,transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:first-child{top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,.05)}.navbar-burger.is-active span:first-child{transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#4a4a4a;display:block;line-height:1.5;padding:.5rem .75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-.25rem;margin-right:-.25rem}.navbar-link,a.navbar-item{cursor:pointer}.navbar-link.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,a.navbar-item.is-active,a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover{background-color:#fafafa;color:#485fc7}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#485fc7}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#485fc7;border-bottom-style:solid;border-bottom-width:3px;color:#485fc7;padding-bottom:calc(.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#485fc7;margin-top:-.375em;right:1.125em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:.5rem 0}@media screen and (max-width:1023px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}body.has-navbar-fixed-top-touch,html.has-navbar-fixed-top-touch{padding-top:3.25rem}body.has-navbar-fixed-bottom-touch,html.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width:1024px){.navbar,.navbar-end,.navbar-menu,.navbar-start{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-end,.navbar.is-spaced .navbar-start{align-items:center}.navbar.is-spaced .navbar-link,.navbar.is-spaced a.navbar-item{border-radius:4px}.navbar.is-transparent .navbar-link.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover{background-color:transparent!important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent!important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#485fc7}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(.25em,-.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#485fc7}.navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-dropdown{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.container>.navbar .navbar-brand,.navbar>.container .navbar-brand{margin-left:-.75rem}.container>.navbar .navbar-menu,.navbar>.container .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-desktop{top:0}body.has-navbar-fixed-top-desktop,html.has-navbar-fixed-top-desktop{padding-top:3.25rem}body.has-navbar-fixed-bottom-desktop,html.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}body.has-spaced-navbar-fixed-top,html.has-spaced-navbar-fixed-top{padding-top:5.25rem}body.has-spaced-navbar-fixed-bottom,html.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}.navbar-link.is-active,a.navbar-item.is-active{color:#0a0a0a}.navbar-link.is-active:not(:focus):not(:hover),a.navbar-item.is-active:not(:focus):not(:hover){background-color:transparent}.navbar-item.has-dropdown.is-active .navbar-link,.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-next,.pagination.is-rounded .pagination-previous{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-link,.pagination-next,.pagination-previous{border-color:#dbdbdb;color:#363636;min-width:2.5em}.pagination-link:hover,.pagination-next:hover,.pagination-previous:hover{border-color:#b5b5b5;color:#363636}.pagination-link:focus,.pagination-next:focus,.pagination-previous:focus{border-color:#485fc7}.pagination-link:active,.pagination-next:active,.pagination-previous:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2)}.pagination-link.is-disabled,.pagination-link[disabled],.pagination-next.is-disabled,.pagination-next[disabled],.pagination-previous.is-disabled,.pagination-previous[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-next,.pagination-previous{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#485fc7;border-color:#485fc7;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width:768px){.pagination{flex-wrap:wrap}.pagination-next,.pagination-previous{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width:769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading{background-color:#00d1b2;color:#fff}.panel.is-primary .panel-tabs a.is-active{border-bottom-color:#00d1b2}.panel.is-primary .panel-block.is-active .panel-icon{color:#00d1b2}.panel.is-link .panel-heading{background-color:#485fc7;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#485fc7}.panel.is-link .panel-block.is-active .panel-icon{color:#485fc7}.panel.is-info .panel-heading{background-color:#3e8ed0;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3e8ed0}.panel.is-info .panel-block.is-active .panel-icon{color:#3e8ed0}.panel.is-success .panel-heading{background-color:#48c78e;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#48c78e}.panel.is-success .panel-block.is-active .panel-icon{color:#48c78e}.panel.is-warning .panel-heading{background-color:#ffe08a;color:rgba(0,0,0,.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffe08a}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffe08a}.panel.is-danger .panel-heading{background-color:#f14668;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f14668}.panel.is-danger .panel-block.is-active .panel-icon{color:#f14668}.panel-block:not(:last-child),.panel-tabs:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#363636;font-size:1.25em;font-weight:700;line-height:1.25;padding:.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#4a4a4a}.panel-list a:hover{color:#485fc7}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#485fc7;color:#363636}.panel-block.is-active .panel-icon{color:#485fc7}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#4a4a4a;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#485fc7;color:#485fc7}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent!important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#485fc7;border-color:#485fc7;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0}.columns.is-mobile>.column.is-1{flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width:768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0}.column.is-1-mobile{flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width:769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width:1023px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0}.column.is-1-touch{flex:none;width:8.33333%}.column.is-offset-1-touch{margin-left:8.33333%}.column.is-2-touch{flex:none;width:16.66667%}.column.is-offset-2-touch{margin-left:16.66667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333%}.column.is-offset-4-touch{margin-left:33.33333%}.column.is-5-touch{flex:none;width:41.66667%}.column.is-offset-5-touch{margin-left:41.66667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333%}.column.is-offset-7-touch{margin-left:58.33333%}.column.is-8-touch{flex:none;width:66.66667%}.column.is-offset-8-touch{margin-left:66.66667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333%}.column.is-offset-10-touch{margin-left:83.33333%}.column.is-11-touch{flex:none;width:91.66667%}.column.is-offset-11-touch{margin-left:91.66667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width:1024px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0}.column.is-1-desktop{flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width:1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0}.column.is-1-widescreen{flex:none;width:8.33333%}.column.is-offset-1-widescreen{margin-left:8.33333%}.column.is-2-widescreen{flex:none;width:16.66667%}.column.is-offset-2-widescreen{margin-left:16.66667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333%}.column.is-offset-4-widescreen{margin-left:33.33333%}.column.is-5-widescreen{flex:none;width:41.66667%}.column.is-offset-5-widescreen{margin-left:41.66667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333%}.column.is-offset-7-widescreen{margin-left:58.33333%}.column.is-8-widescreen{flex:none;width:66.66667%}.column.is-offset-8-widescreen{margin-left:66.66667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333%}.column.is-offset-10-widescreen{margin-left:83.33333%}.column.is-11-widescreen{flex:none;width:91.66667%}.column.is-offset-11-widescreen{margin-left:91.66667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width:1408px){.column.is-narrow-fullhd{flex:none;width:unset}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0}.column.is-1-fullhd{flex:none;width:8.33333%}.column.is-offset-1-fullhd{margin-left:8.33333%}.column.is-2-fullhd{flex:none;width:16.66667%}.column.is-offset-2-fullhd{margin-left:16.66667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333%}.column.is-offset-4-fullhd{margin-left:33.33333%}.column.is-5-fullhd{flex:none;width:41.66667%}.column.is-offset-5-fullhd{margin-left:41.66667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333%}.column.is-offset-7-fullhd{margin-left:58.33333%}.column.is-8-fullhd{flex:none;width:66.66667%}.column.is-offset-8-fullhd{margin-left:66.66667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333%}.column.is-offset-10-fullhd{margin-left:83.33333%}.column.is-11-fullhd{flex:none;width:91.66667%}.column.is-offset-11-fullhd{margin-left:91.66667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0!important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width:769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width:1024px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap:0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap:0rem}@media screen and (max-width:768px){.columns.is-variable.is-0-mobile{--columnGap:0rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-0-tablet{--columnGap:0rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-0-tablet-only{--columnGap:0rem}}@media screen and (max-width:1023px){.columns.is-variable.is-0-touch{--columnGap:0rem}}@media screen and (min-width:1024px){.columns.is-variable.is-0-desktop{--columnGap:0rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-0-desktop-only{--columnGap:0rem}}@media screen and (min-width:1216px){.columns.is-variable.is-0-widescreen{--columnGap:0rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-0-widescreen-only{--columnGap:0rem}}@media screen and (min-width:1408px){.columns.is-variable.is-0-fullhd{--columnGap:0rem}}.columns.is-variable.is-1{--columnGap:0.25rem}@media screen and (max-width:768px){.columns.is-variable.is-1-mobile{--columnGap:0.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-1-tablet{--columnGap:0.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-1-tablet-only{--columnGap:0.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-1-touch{--columnGap:0.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-1-desktop{--columnGap:0.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-1-desktop-only{--columnGap:0.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-1-widescreen{--columnGap:0.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-1-widescreen-only{--columnGap:0.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-1-fullhd{--columnGap:0.25rem}}.columns.is-variable.is-2{--columnGap:0.5rem}@media screen and (max-width:768px){.columns.is-variable.is-2-mobile{--columnGap:0.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-2-tablet{--columnGap:0.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-2-tablet-only{--columnGap:0.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-2-touch{--columnGap:0.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-2-desktop{--columnGap:0.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-2-desktop-only{--columnGap:0.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-2-widescreen{--columnGap:0.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-2-widescreen-only{--columnGap:0.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-2-fullhd{--columnGap:0.5rem}}.columns.is-variable.is-3{--columnGap:0.75rem}@media screen and (max-width:768px){.columns.is-variable.is-3-mobile{--columnGap:0.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-3-tablet{--columnGap:0.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-3-tablet-only{--columnGap:0.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-3-touch{--columnGap:0.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-3-desktop{--columnGap:0.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-3-desktop-only{--columnGap:0.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-3-widescreen{--columnGap:0.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-3-widescreen-only{--columnGap:0.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-3-fullhd{--columnGap:0.75rem}}.columns.is-variable.is-4{--columnGap:1rem}@media screen and (max-width:768px){.columns.is-variable.is-4-mobile{--columnGap:1rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-4-tablet{--columnGap:1rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-4-tablet-only{--columnGap:1rem}}@media screen and (max-width:1023px){.columns.is-variable.is-4-touch{--columnGap:1rem}}@media screen and (min-width:1024px){.columns.is-variable.is-4-desktop{--columnGap:1rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-4-desktop-only{--columnGap:1rem}}@media screen and (min-width:1216px){.columns.is-variable.is-4-widescreen{--columnGap:1rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-4-widescreen-only{--columnGap:1rem}}@media screen and (min-width:1408px){.columns.is-variable.is-4-fullhd{--columnGap:1rem}}.columns.is-variable.is-5{--columnGap:1.25rem}@media screen and (max-width:768px){.columns.is-variable.is-5-mobile{--columnGap:1.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-5-tablet{--columnGap:1.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-5-tablet-only{--columnGap:1.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-5-touch{--columnGap:1.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-5-desktop{--columnGap:1.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-5-desktop-only{--columnGap:1.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-5-widescreen{--columnGap:1.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-5-widescreen-only{--columnGap:1.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-5-fullhd{--columnGap:1.25rem}}.columns.is-variable.is-6{--columnGap:1.5rem}@media screen and (max-width:768px){.columns.is-variable.is-6-mobile{--columnGap:1.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-6-tablet{--columnGap:1.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-6-tablet-only{--columnGap:1.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-6-touch{--columnGap:1.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-6-desktop{--columnGap:1.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-6-desktop-only{--columnGap:1.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-6-widescreen{--columnGap:1.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-6-widescreen-only{--columnGap:1.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-6-fullhd{--columnGap:1.5rem}}.columns.is-variable.is-7{--columnGap:1.75rem}@media screen and (max-width:768px){.columns.is-variable.is-7-mobile{--columnGap:1.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-7-tablet{--columnGap:1.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-7-tablet-only{--columnGap:1.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-7-touch{--columnGap:1.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-7-desktop{--columnGap:1.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-7-desktop-only{--columnGap:1.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-7-widescreen{--columnGap:1.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-7-widescreen-only{--columnGap:1.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-7-fullhd{--columnGap:1.75rem}}.columns.is-variable.is-8{--columnGap:2rem}@media screen and (max-width:768px){.columns.is-variable.is-8-mobile{--columnGap:2rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-8-tablet{--columnGap:2rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-8-tablet-only{--columnGap:2rem}}@media screen and (max-width:1023px){.columns.is-variable.is-8-touch{--columnGap:2rem}}@media screen and (min-width:1024px){.columns.is-variable.is-8-desktop{--columnGap:2rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-8-desktop-only{--columnGap:2rem}}@media screen and (min-width:1216px){.columns.is-variable.is-8-widescreen{--columnGap:2rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-8-widescreen-only{--columnGap:2rem}}@media screen and (min-width:1408px){.columns.is-variable.is-8-fullhd{--columnGap:2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:-webkit-min-content;min-height:-moz-min-content;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0!important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem!important}@media screen and (min-width:769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333%}.tile.is-2{flex:none;width:16.66667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333%}.tile.is-5{flex:none;width:41.66667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333%}.tile.is-8{flex:none;width:66.66667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333%}.tile.is-11{flex:none;width:91.66667%}.tile.is-12{flex:none;width:100%}}.has-text-white{color:#fff!important}a.has-text-white:focus,a.has-text-white:hover{color:#e6e6e6!important}.has-background-white{background-color:#fff!important}.has-text-black{color:#0a0a0a!important}a.has-text-black:focus,a.has-text-black:hover{color:#000!important}.has-background-black{background-color:#0a0a0a!important}.has-text-light{color:#f5f5f5!important}a.has-text-light:focus,a.has-text-light:hover{color:#dbdbdb!important}.has-background-light{background-color:#f5f5f5!important}.has-text-dark{color:#363636!important}a.has-text-dark:focus,a.has-text-dark:hover{color:#1c1c1c!important}.has-background-dark{background-color:#363636!important}.has-text-primary{color:#00d1b2!important}a.has-text-primary:focus,a.has-text-primary:hover{color:#009e86!important}.has-background-primary{background-color:#00d1b2!important}.has-text-primary-light{color:#ebfffc!important}a.has-text-primary-light:focus,a.has-text-primary-light:hover{color:#b8fff4!important}.has-background-primary-light{background-color:#ebfffc!important}.has-text-primary-dark{color:#00947e!important}a.has-text-primary-dark:focus,a.has-text-primary-dark:hover{color:#00c7a9!important}.has-background-primary-dark{background-color:#00947e!important}.has-text-link{color:#485fc7!important}a.has-text-link:focus,a.has-text-link:hover{color:#3449a8!important}.has-background-link{background-color:#485fc7!important}.has-text-link-light{color:#eff1fa!important}a.has-text-link-light:focus,a.has-text-link-light:hover{color:#c8cfee!important}.has-background-link-light{background-color:#eff1fa!important}.has-text-link-dark{color:#3850b7!important}a.has-text-link-dark:focus,a.has-text-link-dark:hover{color:#576dcb!important}.has-background-link-dark{background-color:#3850b7!important}.has-text-info{color:#3e8ed0!important}a.has-text-info:focus,a.has-text-info:hover{color:#2b74b1!important}.has-background-info{background-color:#3e8ed0!important}.has-text-info-light{color:#eff5fb!important}a.has-text-info-light:focus,a.has-text-info-light:hover{color:#c6ddf1!important}.has-background-info-light{background-color:#eff5fb!important}.has-text-info-dark{color:#296fa8!important}a.has-text-info-dark:focus,a.has-text-info-dark:hover{color:#368ace!important}.has-background-info-dark{background-color:#296fa8!important}.has-text-success{color:#48c78e!important}a.has-text-success:focus,a.has-text-success:hover{color:#34a873!important}.has-background-success{background-color:#48c78e!important}.has-text-success-light{color:#effaf5!important}a.has-text-success-light:focus,a.has-text-success-light:hover{color:#c8eedd!important}.has-background-success-light{background-color:#effaf5!important}.has-text-success-dark{color:#257953!important}a.has-text-success-dark:focus,a.has-text-success-dark:hover{color:#31a06e!important}.has-background-success-dark{background-color:#257953!important}.has-text-warning{color:#ffe08a!important}a.has-text-warning:focus,a.has-text-warning:hover{color:#ffd257!important}.has-background-warning{background-color:#ffe08a!important}.has-text-warning-light{color:#fffaeb!important}a.has-text-warning-light:focus,a.has-text-warning-light:hover{color:#ffecb8!important}.has-background-warning-light{background-color:#fffaeb!important}.has-text-warning-dark{color:#946c00!important}a.has-text-warning-dark:focus,a.has-text-warning-dark:hover{color:#c79200!important}.has-background-warning-dark{background-color:#946c00!important}.has-text-danger{color:#f14668!important}a.has-text-danger:focus,a.has-text-danger:hover{color:#ee1742!important}.has-background-danger{background-color:#f14668!important}.has-text-danger-light{color:#feecf0!important}a.has-text-danger-light:focus,a.has-text-danger-light:hover{color:#fabdc9!important}.has-background-danger-light{background-color:#feecf0!important}.has-text-danger-dark{color:#cc0f35!important}a.has-text-danger-dark:focus,a.has-text-danger-dark:hover{color:#ee2049!important}.has-background-danger-dark{background-color:#cc0f35!important}.has-text-black-bis{color:#121212!important}.has-background-black-bis{background-color:#121212!important}.has-text-black-ter{color:#242424!important}.has-background-black-ter{background-color:#242424!important}.has-text-grey-darker{color:#363636!important}.has-background-grey-darker{background-color:#363636!important}.has-text-grey-dark{color:#4a4a4a!important}.has-background-grey-dark{background-color:#4a4a4a!important}.has-text-grey{color:#7a7a7a!important}.has-background-grey{background-color:#7a7a7a!important}.has-text-grey-light{color:#b5b5b5!important}.has-background-grey-light{background-color:#b5b5b5!important}.has-text-grey-lighter{color:#dbdbdb!important}.has-background-grey-lighter{background-color:#dbdbdb!important}.has-text-white-ter{color:#f5f5f5!important}.has-background-white-ter{background-color:#f5f5f5!important}.has-text-white-bis{color:#fafafa!important}.has-background-white-bis{background-color:#fafafa!important}.is-flex-direction-row{flex-direction:row!important}.is-flex-direction-row-reverse{flex-direction:row-reverse!important}.is-flex-direction-column{flex-direction:column!important}.is-flex-direction-column-reverse{flex-direction:column-reverse!important}.is-flex-wrap-nowrap{flex-wrap:nowrap!important}.is-flex-wrap-wrap{flex-wrap:wrap!important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse!important}.is-justify-content-flex-start{justify-content:flex-start!important}.is-justify-content-flex-end{justify-content:flex-end!important}.is-justify-content-center{justify-content:center!important}.is-justify-content-space-between{justify-content:space-between!important}.is-justify-content-space-around{justify-content:space-around!important}.is-justify-content-space-evenly{justify-content:space-evenly!important}.is-justify-content-start{justify-content:start!important}.is-justify-content-end{justify-content:end!important}.is-justify-content-left{justify-content:left!important}.is-justify-content-right{justify-content:right!important}.is-align-content-flex-start{align-content:flex-start!important}.is-align-content-flex-end{align-content:flex-end!important}.is-align-content-center{align-content:center!important}.is-align-content-space-between{align-content:space-between!important}.is-align-content-space-around{align-content:space-around!important}.is-align-content-space-evenly{align-content:space-evenly!important}.is-align-content-stretch{align-content:stretch!important}.is-align-content-start{align-content:start!important}.is-align-content-end{align-content:end!important}.is-align-content-baseline{align-content:baseline!important}.is-align-items-stretch{align-items:stretch!important}.is-align-items-flex-start{align-items:flex-start!important}.is-align-items-flex-end{align-items:flex-end!important}.is-align-items-center{align-items:center!important}.is-align-items-baseline{align-items:baseline!important}.is-align-items-start{align-items:start!important}.is-align-items-end{align-items:end!important}.is-align-items-self-start{align-items:self-start!important}.is-align-items-self-end{align-items:self-end!important}.is-align-self-auto{align-self:auto!important}.is-align-self-flex-start{align-self:flex-start!important}.is-align-self-flex-end{align-self:flex-end!important}.is-align-self-center{align-self:center!important}.is-align-self-baseline{align-self:baseline!important}.is-align-self-stretch{align-self:stretch!important}.is-flex-grow-0{flex-grow:0!important}.is-flex-grow-1{flex-grow:1!important}.is-flex-grow-2{flex-grow:2!important}.is-flex-grow-3{flex-grow:3!important}.is-flex-grow-4{flex-grow:4!important}.is-flex-grow-5{flex-grow:5!important}.is-flex-shrink-0{flex-shrink:0!important}.is-flex-shrink-1{flex-shrink:1!important}.is-flex-shrink-2{flex-shrink:2!important}.is-flex-shrink-3{flex-shrink:3!important}.is-flex-shrink-4{flex-shrink:4!important}.is-flex-shrink-5{flex-shrink:5!important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left!important}.is-pulled-right{float:right!important}.is-radiusless{border-radius:0!important}.is-shadowless{box-shadow:none!important}.is-clickable{cursor:pointer!important;pointer-events:all!important}.is-clipped{overflow:hidden!important}.is-relative{position:relative!important}.is-marginless{margin:0!important}.is-paddingless{padding:0!important}.m-0{margin:0!important}.mt-0{margin-top:0!important}.mr-0{margin-right:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-top:0!important;margin-bottom:0!important}.m-1{margin:.25rem!important}.mt-1{margin-top:.25rem!important}.mr-1{margin-right:.25rem!important}.mb-1{margin-bottom:.25rem!important}.ml-1{margin-left:.25rem!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.m-2{margin:.5rem!important}.mt-2{margin-top:.5rem!important}.mr-2{margin-right:.5rem!important}.mb-2{margin-bottom:.5rem!important}.ml-2{margin-left:.5rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.m-3{margin:.75rem!important}.mt-3{margin-top:.75rem!important}.mr-3{margin-right:.75rem!important}.mb-3{margin-bottom:.75rem!important}.ml-3{margin-left:.75rem!important}.mx-3{margin-left:.75rem!important;margin-right:.75rem!important}.my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.m-4{margin:1rem!important}.mt-4{margin-top:1rem!important}.mr-4{margin-right:1rem!important}.mb-4{margin-bottom:1rem!important}.ml-4{margin-left:1rem!important}.mx-4{margin-left:1rem!important;margin-right:1rem!important}.my-4{margin-top:1rem!important;margin-bottom:1rem!important}.m-5{margin:1.5rem!important}.mt-5{margin-top:1.5rem!important}.mr-5{margin-right:1.5rem!important}.mb-5{margin-bottom:1.5rem!important}.ml-5{margin-left:1.5rem!important}.mx-5{margin-left:1.5rem!important;margin-right:1.5rem!important}.my-5{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.m-6{margin:3rem!important}.mt-6{margin-top:3rem!important}.mr-6{margin-right:3rem!important}.mb-6{margin-bottom:3rem!important}.ml-6{margin-left:3rem!important}.mx-6{margin-left:3rem!important;margin-right:3rem!important}.my-6{margin-top:3rem!important;margin-bottom:3rem!important}.m-auto{margin:auto!important}.mt-auto{margin-top:auto!important}.mr-auto{margin-right:auto!important}.mb-auto{margin-bottom:auto!important}.ml-auto{margin-left:auto!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.p-0{padding:0!important}.pt-0{padding-top:0!important}.pr-0{padding-right:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-top:0!important;padding-bottom:0!important}.p-1{padding:.25rem!important}.pt-1{padding-top:.25rem!important}.pr-1{padding-right:.25rem!important}.pb-1{padding-bottom:.25rem!important}.pl-1{padding-left:.25rem!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.p-2{padding:.5rem!important}.pt-2{padding-top:.5rem!important}.pr-2{padding-right:.5rem!important}.pb-2{padding-bottom:.5rem!important}.pl-2{padding-left:.5rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.p-3{padding:.75rem!important}.pt-3{padding-top:.75rem!important}.pr-3{padding-right:.75rem!important}.pb-3{padding-bottom:.75rem!important}.pl-3{padding-left:.75rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.p-4{padding:1rem!important}.pt-4{padding-top:1rem!important}.pr-4{padding-right:1rem!important}.pb-4{padding-bottom:1rem!important}.pl-4{padding-left:1rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.p-5{padding:1.5rem!important}.pt-5{padding-top:1.5rem!important}.pr-5{padding-right:1.5rem!important}.pb-5{padding-bottom:1.5rem!important}.pl-5{padding-left:1.5rem!important}.px-5{padding-left:1.5rem!important;padding-right:1.5rem!important}.py-5{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.p-6{padding:3rem!important}.pt-6{padding-top:3rem!important}.pr-6{padding-right:3rem!important}.pb-6{padding-bottom:3rem!important}.pl-6{padding-left:3rem!important}.px-6{padding-left:3rem!important;padding-right:3rem!important}.py-6{padding-top:3rem!important;padding-bottom:3rem!important}.p-auto{padding:auto!important}.pt-auto{padding-top:auto!important}.pr-auto{padding-right:auto!important}.pb-auto{padding-bottom:auto!important}.pl-auto{padding-left:auto!important}.px-auto{padding-left:auto!important;padding-right:auto!important}.py-auto{padding-top:auto!important;padding-bottom:auto!important}.is-size-1{font-size:3rem!important}.is-size-2{font-size:2.5rem!important}.is-size-3{font-size:2rem!important}.is-size-4{font-size:1.5rem!important}.is-size-5{font-size:1.25rem!important}.is-size-6{font-size:1rem!important}.is-size-7{font-size:.75rem!important}@media screen and (max-width:768px){.is-size-1-mobile{font-size:3rem!important}.is-size-2-mobile{font-size:2.5rem!important}.is-size-3-mobile{font-size:2rem!important}.is-size-4-mobile{font-size:1.5rem!important}.is-size-5-mobile{font-size:1.25rem!important}.is-size-6-mobile{font-size:1rem!important}.is-size-7-mobile{font-size:.75rem!important}}@media screen and (min-width:769px),print{.is-size-1-tablet{font-size:3rem!important}.is-size-2-tablet{font-size:2.5rem!important}.is-size-3-tablet{font-size:2rem!important}.is-size-4-tablet{font-size:1.5rem!important}.is-size-5-tablet{font-size:1.25rem!important}.is-size-6-tablet{font-size:1rem!important}.is-size-7-tablet{font-size:.75rem!important}}@media screen and (max-width:1023px){.is-size-1-touch{font-size:3rem!important}.is-size-2-touch{font-size:2.5rem!important}.is-size-3-touch{font-size:2rem!important}.is-size-4-touch{font-size:1.5rem!important}.is-size-5-touch{font-size:1.25rem!important}.is-size-6-touch{font-size:1rem!important}.is-size-7-touch{font-size:.75rem!important}}@media screen and (min-width:1024px){.is-size-1-desktop{font-size:3rem!important}.is-size-2-desktop{font-size:2.5rem!important}.is-size-3-desktop{font-size:2rem!important}.is-size-4-desktop{font-size:1.5rem!important}.is-size-5-desktop{font-size:1.25rem!important}.is-size-6-desktop{font-size:1rem!important}.is-size-7-desktop{font-size:.75rem!important}}@media screen and (min-width:1216px){.is-size-1-widescreen{font-size:3rem!important}.is-size-2-widescreen{font-size:2.5rem!important}.is-size-3-widescreen{font-size:2rem!important}.is-size-4-widescreen{font-size:1.5rem!important}.is-size-5-widescreen{font-size:1.25rem!important}.is-size-6-widescreen{font-size:1rem!important}.is-size-7-widescreen{font-size:.75rem!important}}@media screen and (min-width:1408px){.is-size-1-fullhd{font-size:3rem!important}.is-size-2-fullhd{font-size:2.5rem!important}.is-size-3-fullhd{font-size:2rem!important}.is-size-4-fullhd{font-size:1.5rem!important}.is-size-5-fullhd{font-size:1.25rem!important}.is-size-6-fullhd{font-size:1rem!important}.is-size-7-fullhd{font-size:.75rem!important}}.has-text-centered{text-align:center!important}.has-text-justified{text-align:justify!important}.has-text-left{text-align:left!important}.has-text-right{text-align:right!important}@media screen and (max-width:768px){.has-text-centered-mobile{text-align:center!important}}@media screen and (min-width:769px),print{.has-text-centered-tablet{text-align:center!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-centered-tablet-only{text-align:center!important}}@media screen and (max-width:1023px){.has-text-centered-touch{text-align:center!important}}@media screen and (min-width:1024px){.has-text-centered-desktop{text-align:center!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-centered-desktop-only{text-align:center!important}}@media screen and (min-width:1216px){.has-text-centered-widescreen{text-align:center!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-centered-widescreen-only{text-align:center!important}}@media screen and (min-width:1408px){.has-text-centered-fullhd{text-align:center!important}}@media screen and (max-width:768px){.has-text-justified-mobile{text-align:justify!important}}@media screen and (min-width:769px),print{.has-text-justified-tablet{text-align:justify!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-justified-tablet-only{text-align:justify!important}}@media screen and (max-width:1023px){.has-text-justified-touch{text-align:justify!important}}@media screen and (min-width:1024px){.has-text-justified-desktop{text-align:justify!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-justified-desktop-only{text-align:justify!important}}@media screen and (min-width:1216px){.has-text-justified-widescreen{text-align:justify!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-justified-widescreen-only{text-align:justify!important}}@media screen and (min-width:1408px){.has-text-justified-fullhd{text-align:justify!important}}@media screen and (max-width:768px){.has-text-left-mobile{text-align:left!important}}@media screen and (min-width:769px),print{.has-text-left-tablet{text-align:left!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-left-tablet-only{text-align:left!important}}@media screen and (max-width:1023px){.has-text-left-touch{text-align:left!important}}@media screen and (min-width:1024px){.has-text-left-desktop{text-align:left!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-left-desktop-only{text-align:left!important}}@media screen and (min-width:1216px){.has-text-left-widescreen{text-align:left!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-left-widescreen-only{text-align:left!important}}@media screen and (min-width:1408px){.has-text-left-fullhd{text-align:left!important}}@media screen and (max-width:768px){.has-text-right-mobile{text-align:right!important}}@media screen and (min-width:769px),print{.has-text-right-tablet{text-align:right!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-right-tablet-only{text-align:right!important}}@media screen and (max-width:1023px){.has-text-right-touch{text-align:right!important}}@media screen and (min-width:1024px){.has-text-right-desktop{text-align:right!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-right-desktop-only{text-align:right!important}}@media screen and (min-width:1216px){.has-text-right-widescreen{text-align:right!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-right-widescreen-only{text-align:right!important}}@media screen and (min-width:1408px){.has-text-right-fullhd{text-align:right!important}}.is-capitalized{text-transform:capitalize!important}.is-lowercase{text-transform:lowercase!important}.is-uppercase{text-transform:uppercase!important}.is-italic{font-style:italic!important}.is-underlined{text-decoration:underline!important}.has-text-weight-light{font-weight:300!important}.has-text-weight-normal{font-weight:400!important}.has-text-weight-medium{font-weight:500!important}.has-text-weight-semibold{font-weight:600!important}.has-text-weight-bold{font-weight:700!important}.is-family-primary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-secondary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-sans-serif{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-monospace{font-family:monospace!important}.is-family-code{font-family:monospace!important}.is-block{display:block!important}@media screen and (max-width:768px){.is-block-mobile{display:block!important}}@media screen and (min-width:769px),print{.is-block-tablet{display:block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-block-tablet-only{display:block!important}}@media screen and (max-width:1023px){.is-block-touch{display:block!important}}@media screen and (min-width:1024px){.is-block-desktop{display:block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-block-desktop-only{display:block!important}}@media screen and (min-width:1216px){.is-block-widescreen{display:block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-block-widescreen-only{display:block!important}}@media screen and (min-width:1408px){.is-block-fullhd{display:block!important}}.is-flex{display:flex!important}@media screen and (max-width:768px){.is-flex-mobile{display:flex!important}}@media screen and (min-width:769px),print{.is-flex-tablet{display:flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-flex-tablet-only{display:flex!important}}@media screen and (max-width:1023px){.is-flex-touch{display:flex!important}}@media screen and (min-width:1024px){.is-flex-desktop{display:flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-flex-desktop-only{display:flex!important}}@media screen and (min-width:1216px){.is-flex-widescreen{display:flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-flex-widescreen-only{display:flex!important}}@media screen and (min-width:1408px){.is-flex-fullhd{display:flex!important}}.is-inline{display:inline!important}@media screen and (max-width:768px){.is-inline-mobile{display:inline!important}}@media screen and (min-width:769px),print{.is-inline-tablet{display:inline!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-tablet-only{display:inline!important}}@media screen and (max-width:1023px){.is-inline-touch{display:inline!important}}@media screen and (min-width:1024px){.is-inline-desktop{display:inline!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-desktop-only{display:inline!important}}@media screen and (min-width:1216px){.is-inline-widescreen{display:inline!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-widescreen-only{display:inline!important}}@media screen and (min-width:1408px){.is-inline-fullhd{display:inline!important}}.is-inline-block{display:inline-block!important}@media screen and (max-width:768px){.is-inline-block-mobile{display:inline-block!important}}@media screen and (min-width:769px),print{.is-inline-block-tablet{display:inline-block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-block-tablet-only{display:inline-block!important}}@media screen and (max-width:1023px){.is-inline-block-touch{display:inline-block!important}}@media screen and (min-width:1024px){.is-inline-block-desktop{display:inline-block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-block-desktop-only{display:inline-block!important}}@media screen and (min-width:1216px){.is-inline-block-widescreen{display:inline-block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-block-widescreen-only{display:inline-block!important}}@media screen and (min-width:1408px){.is-inline-block-fullhd{display:inline-block!important}}.is-inline-flex{display:inline-flex!important}@media screen and (max-width:768px){.is-inline-flex-mobile{display:inline-flex!important}}@media screen and (min-width:769px),print{.is-inline-flex-tablet{display:inline-flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-flex-tablet-only{display:inline-flex!important}}@media screen and (max-width:1023px){.is-inline-flex-touch{display:inline-flex!important}}@media screen and (min-width:1024px){.is-inline-flex-desktop{display:inline-flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-flex-desktop-only{display:inline-flex!important}}@media screen and (min-width:1216px){.is-inline-flex-widescreen{display:inline-flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-flex-widescreen-only{display:inline-flex!important}}@media screen and (min-width:1408px){.is-inline-flex-fullhd{display:inline-flex!important}}.is-hidden{display:none!important}.is-sr-only{border:none!important;clip:rect(0,0,0,0)!important;height:.01em!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:.01em!important}@media screen and (max-width:768px){.is-hidden-mobile{display:none!important}}@media screen and (min-width:769px),print{.is-hidden-tablet{display:none!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-hidden-tablet-only{display:none!important}}@media screen and (max-width:1023px){.is-hidden-touch{display:none!important}}@media screen and (min-width:1024px){.is-hidden-desktop{display:none!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-hidden-desktop-only{display:none!important}}@media screen and (min-width:1216px){.is-hidden-widescreen{display:none!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-hidden-widescreen-only{display:none!important}}@media screen and (min-width:1408px){.is-hidden-fullhd{display:none!important}}.is-invisible{visibility:hidden!important}@media screen and (max-width:768px){.is-invisible-mobile{visibility:hidden!important}}@media screen and (min-width:769px),print{.is-invisible-tablet{visibility:hidden!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-invisible-tablet-only{visibility:hidden!important}}@media screen and (max-width:1023px){.is-invisible-touch{visibility:hidden!important}}@media screen and (min-width:1024px){.is-invisible-desktop{visibility:hidden!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-invisible-desktop-only{visibility:hidden!important}}@media screen and (min-width:1216px){.is-invisible-widescreen{visibility:hidden!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-invisible-widescreen-only{visibility:hidden!important}}@media screen and (min-width:1408px){.is-invisible-fullhd{visibility:hidden!important}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:0 0}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width:1023px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,.7)}.hero.is-white .navbar-link.is-active,.hero.is-white .navbar-link:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff!important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,.7)}.hero.is-black .navbar-link.is-active,.hero.is-black .navbar-link:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black a.navbar-item:hover{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#0a0a0a!important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}@media screen and (max-width:768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,.7)}.hero.is-light .subtitle{color:rgba(0,0,0,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,.7)}.hero.is-light .navbar-link.is-active,.hero.is-light .navbar-link:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.hero.is-light .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5!important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}}.hero.is-dark{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#fff}.hero.is-dark .subtitle{color:rgba(255,255,255,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(255,255,255,.7)}.hero.is-dark .navbar-link.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark a.navbar-item:hover{background-color:#292929;color:#fff}.hero.is-dark .tabs a{color:#fff;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{color:#363636!important;opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}@media screen and (max-width:768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}}.hero.is-primary{background-color:#00d1b2;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-primary .navbar-menu{background-color:#00d1b2}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,.7)}.hero.is-primary .navbar-link.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary a.navbar-item:hover{background-color:#00b89c;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{color:#00d1b2!important;opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#00d1b2}.hero.is-primary.is-bold{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}@media screen and (max-width:768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}}.hero.is-link{background-color:#485fc7;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-link .navbar-menu{background-color:#485fc7}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,.7)}.hero.is-link .navbar-link.is-active,.hero.is-link .navbar-link:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link a.navbar-item:hover{background-color:#3a51bb;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#485fc7!important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#485fc7}.hero.is-link.is-bold{background-image:linear-gradient(141deg,#2959b3 0,#485fc7 71%,#5658d2 100%)}@media screen and (max-width:768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg,#2959b3 0,#485fc7 71%,#5658d2 100%)}}.hero.is-info{background-color:#3e8ed0;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-info .navbar-menu{background-color:#3e8ed0}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,.7)}.hero.is-info .navbar-link.is-active,.hero.is-info .navbar-link:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info a.navbar-item:hover{background-color:#3082c5;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#3e8ed0!important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3e8ed0}.hero.is-info.is-bold{background-image:linear-gradient(141deg,#208fbc 0,#3e8ed0 71%,#4d83db 100%)}@media screen and (max-width:768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg,#208fbc 0,#3e8ed0 71%,#4d83db 100%)}}.hero.is-success{background-color:#48c78e;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-success .navbar-menu{background-color:#48c78e}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,.7)}.hero.is-success .navbar-link.is-active,.hero.is-success .navbar-link:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success a.navbar-item:hover{background-color:#3abb81;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#48c78e!important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#48c78e}.hero.is-success.is-bold{background-image:linear-gradient(141deg,#29b35e 0,#48c78e 71%,#56d2af 100%)}@media screen and (max-width:768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg,#29b35e 0,#48c78e 71%,#56d2af 100%)}}.hero.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-warning .navbar-menu{background-color:#ffe08a}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,.7)}.hero.is-warning .navbar-link.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#ffe08a!important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffe08a}.hero.is-warning.is-bold{background-image:linear-gradient(141deg,#ffb657 0,#ffe08a 71%,#fff6a3 100%)}@media screen and (max-width:768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg,#ffb657 0,#ffe08a 71%,#fff6a3 100%)}}.hero.is-danger{background-color:#f14668;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-danger .navbar-menu{background-color:#f14668}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,.7)}.hero.is-danger .navbar-link.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger a.navbar-item:hover{background-color:#ef2e55;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#f14668!important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f14668}.hero.is-danger.is-bold{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}@media screen and (max-width:768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}}.hero.is-small .hero-body{padding:1.5rem}@media screen and (min-width:769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width:769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body,.hero.is-halfheight .hero-body{align-items:center;display:flex}.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container,.hero.is-halfheight .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%,-50%,0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width:768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width:768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width:769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-foot,.hero-head{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width:769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width:1024px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem} \ No newline at end of file diff --git a/py-kms/templates/base.html b/py-kms/templates/base.html new file mode 100644 index 0000000..f64588d --- /dev/null +++ b/py-kms/templates/base.html @@ -0,0 +1,56 @@ + + + + + py-kms {% block title %}{% endblock %} + + + + +
+ {% block content %}{% endblock %} + + {% if path != '/' %} + + {% endif %} +
+ +
+
+

+ py-kms is online since {{ start_time }}. + This instance was accessed {{ get_serve_count() }} times. View this softwares license here. + {% if version_info %} +
This instance is running version "{{ version_info['hash'] }}" from branch "{{ version_info['branch'] }}" of py-kms. + {% endif %} +

+
+
+ + + + \ No newline at end of file diff --git a/py-kms/templates/clients.html b/py-kms/templates/clients.html new file mode 100644 index 0000000..fb3340b --- /dev/null +++ b/py-kms/templates/clients.html @@ -0,0 +1,103 @@ +{% extends 'base.html' %} + +{% block title %}clients{% endblock %} + +{% block style %} +th { + white-space: nowrap; +} +{% endblock %} + +{% block content %} +{% if error %} +
+
+ Whoops! Something went wrong... +
+
+ {{ error }} +
+
+{% else %} + + +
+ +{% if clients %} + + + + + + + + + + + + + + + {% for client in clients %} + + + + + + + + + + + {% endfor %} + +
Client IDMachine NameApplication IDSKU IDLicense StatusLast SeenKMS EPIDSeen Count
{{ client.clientMachineId }}
+ {% if client.machineName | length > 16 %} + {{ client.machineName | truncate(16, True, '...') }} + {% else %} + {{ client.machineName }} + {% endif %} + {{ client.applicationId }}{{ client.skuId }}{{ client.licenseStatus }}{{ client.lastRequestTime }} + {% if client.kmsEpid | length > 16 %} + {{ client.kmsEpid | truncate(16, True, '...') }} + {% else %} + {{ client.kmsEpid }} + {% endif %} + {{ client.requestCount }}
+{% else %} +
+
+

Whoops?

+
+
+ This page seems to be empty, because no clients are available. Try to use the server with a compartible client to add it to the database. +
+
+{% endif %} +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/py-kms/templates/license.html b/py-kms/templates/license.html new file mode 100644 index 0000000..34ef0d7 --- /dev/null +++ b/py-kms/templates/license.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block title %}license{% endblock %} + +{% block content %} +
+
{{ license }}
+
+{% endblock %} \ No newline at end of file diff --git a/py-kms/templates/products.html b/py-kms/templates/products.html new file mode 100644 index 0000000..9665304 --- /dev/null +++ b/py-kms/templates/products.html @@ -0,0 +1,53 @@ +{% extends 'base.html' %} + +{% block title %}clients{% endblock %} + +{% block content %} + + +
+ + + + + + + + + + {% for name, gvlk in products | dictsort %} + {% if gvlk %} + + + + + {% endif %} + {% endfor %} + +
NameGVLK
{{ name }}
{{ gvlk }}
+{% endblock %} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 120000 index 0000000..bec651b --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +docker/docker-py3-kms/requirements.txt \ No newline at end of file